Skip to content

Add GL.iNet plugin handler authenticated RCE (CVE-2025-67089) - #21819

Open
AleksaZatezalo wants to merge 1 commit into
rapid7:masterfrom
AleksaZatezalo:glinet
Open

Add GL.iNet plugin handler authenticated RCE (CVE-2025-67089)#21819
AleksaZatezalo wants to merge 1 commit into
rapid7:masterfrom
AleksaZatezalo:glinet

Conversation

@AleksaZatezalo

Copy link
Copy Markdown
Contributor

Description

Adds exploit/linux/http/glinet_plugin_handler_rce, a module exploiting
CVE-2025-67089 — an authenticated OS command injection vulnerability in
the plugins install_package RPC method of GL.iNet routers running
firmware < 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:

  • CVE-2025-67089 (this module) — authenticated command injection
  • CVE-2025-67090 — LuCI rate-limiting bypass, allowing credential
    brute-forcing (companion module: auxiliary/scanner/http/glinet_login)
  • CVE-2025-67091 — TOCTOU privilege escalation

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

  • msftidy passes with no warnings or errors
  • rubocop passes with no offenses
  • Tested against a vulnerable GL.iNet AXT1800, firmware 4.6.5, with
    valid admin credentials — authenticates, injects cmd/unix/reverse_netcat,
    and opens a root shell session
  • Tested with invalid credentials — fails cleanly with Failure::NoAccess
    ("Authentication failed - check credentials"), no session created
  • Tested against patched firmware (>= 4.8.2)
  • Tested against an unreachable/unresponsive target

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

Screenshot_2026-08-25_08-18-21

@dledda-r7

dledda-r7 commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

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.

@dledda-r7 dledda-r7 self-assigned this Aug 27, 2026
@AleksaZatezalo

AleksaZatezalo commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

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 glinet_login. Happy to add a note in the description clarifying the intended chain if that helps.

@dledda-r7

Copy link
Copy Markdown
Contributor

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 glinet_login. Happy to add a note in the description clarifying the intended chain if that helps.

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?

@dledda-r7 dledda-r7 added the group-review PRs flagged to get a group review during our weekly module hacking meeting. label Sep 3, 2026
}
]
],
'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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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']),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is root always a valid username?

'jsonrpc' => '2.0',
'id' => 0,
'method' => 'challenge',
'params' => { 'username' => 'root' }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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}")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
print_status("Target: #{rhost}:#{rport}")

'cookie' => "Admin-Token=#{@session_id}",
'data' => {
'jsonrpc' => '2.0',
'id' => 99,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the purpose of these ids?

'method' => 'call',
'params' => [@session_id, 'session', 'destroy', {}]
}.to_json
}, 5)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to specify the timeout here? can we leave it off?

@dwelch-r7 dwelch-r7 added rn-modules release notes for new or majorly enhanced modules group-reviewed Reviewed by the council of elders and removed group-review PRs flagged to get a group review during our weekly module hacking meeting. labels Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

group-reviewed Reviewed by the council of elders rn-modules release notes for new or majorly enhanced modules

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

3 participants