Western Digital My Cloud Pro系列PR4100 NAS认证前RCE漏洞分析与利用

sakuraの从零开始のIoT漏洞挖掘系列(一): Western Digital My Cloud Pro系列PR4100 NAS认证前RCE漏洞分析与利用

简述

本文主要是对crowdstrike团队的pwn2own-tale-of-a-bug-found-and-lost-again文章进行学习,并梳理漏洞模式和探究漏洞利用方法,因为笔者手上没有这款固件,如果有人手上有或者用qemu仿真出来了,可以自己调试一下。

FIRMWARE

首先下载有漏洞的固件,该漏洞从2.31.204版本开始,一直在5.04.114版本修复,跨度长达一年,还是十分值得学习的。
https://downloads.wdc.com/gpl/WDMyCloud_PR4100_GPL_v2.40.155_20200713.tar.gz

攻击面枚举

因为是从零开始的IoT漏洞挖掘,从本篇开始我们首先讲述一下,在开始挖掘漏洞之前,我们需要做什么。第一件事就是要枚举攻击面,即这个目标它起了哪些服务,然后哪些服务是从外网可以访问。
一般可以用Netstat来看这些东西。

  • netstat -tulpn
    • -t tcp
    • -u udp
    • -l listening, Show only listening sockets.
    • -n Show numerical addresses instead of trying to determine symbolic host, port or user names.
    • -p Show the PID and name of the program to which each socket belongs.
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
root@MyCloudPR4100 root # netstat -tulpn
Active Internet connections (only servers)
Proto Local Address Foreign Address State PID/Program name
tcp 0.0.0.0:443 0.0.0.0:* LISTEN 3320/httpd
tcp 127.0.0.1:4700 0.0.0.0:* LISTEN 4131/cnid_metad
tcp 0.0.0.0:445 0.0.0.0:* LISTEN 4073/smbd
tcp 192.168.178.31:49152 0.0.0.0:* LISTEN 3746/upnp_nas_devic
tcp 0.0.0.0:548 0.0.0.0:* LISTEN 4130/afpd
tcp 0.0.0.0:3306 0.0.0.0:* LISTEN 3941/mysqld
tcp 0.0.0.0:139 0.0.0.0:* LISTEN 4073/smbd
tcp 0.0.0.0:80 0.0.0.0:* LISTEN 3320/httpd
tcp 0.0.0.0:8181 0.0.0.0:* LISTEN 1609/restsdk-server
tcp 0.0.0.0:22 0.0.0.0:* LISTEN 2761/sshd
tcp6 :::445 :::* LISTEN 4073/smbd
tcp6 :::139 :::* LISTEN 4073/smbd
tcp6 :::22 :::* LISTEN 2761/sshd
udp 0.0.0.0:1900 0.0.0.0:* 3746/upnp_nas_devic
udp 0.0.0.0:24629 0.0.0.0:* 2076/mserver
udp 172.17.255.255:137 0.0.0.0:* 4077/nmbd
udp 172.17.42.1:137 0.0.0.0:* 4077/nmbd
udp 192.168.178.255:137 0.0.0.0:* 4077/nmbd
udp 192.168.178.31:137 0.0.0.0:* 4077/nmbd
udp 0.0.0.0:137 0.0.0.0:* 4077/nmbd
udp 172.17.255.255:138 0.0.0.0:* 4077/nmbd
udp 172.17.42.1:138 0.0.0.0:* 4077/nmbd
udp 192.168.178.255:138 0.0.0.0:* 4077/nmbd
udp 192.168.178.31:138 0.0.0.0:* 4077/nmbd
udp 0.0.0.0:138 0.0.0.0:* 4077/nmbd
udp 0.0.0.0:30958 0.0.0.0:* 3808/apkg
udp 0.0.0.0:514 0.0.0.0:* 1958/syslogd
udp 127.0.0.1:23457 0.0.0.0:* 3985/wdmcserver
udp 127.0.0.1:46058 0.0.0.0:* 3746/upnp_nas_devic
udp 0.0.0.0:48299 0.0.0.0:* 2481/avahi-daemon:
udp 0.0.0.0:5353 0.0.0.0:* 2481/avahi-daemon:

一般看到httpd就可以确定这可能是使用了apache来做的服务端,所以再搜一下conf配置文件,一般以我的习惯会把每个conf文件都读一下,不过这里我们主要关注一下alias.confrewrite.conf

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
sakura@sakuradeMacBook-Pro:~/Desktop/WDMyCloud_PR4100_GPL_v2.40.155_20200713$ find . -name "*.conf"
./firmware/ramdisk/root/etc/mdev.conf
./firmware/ramdisk/root/etc/ez-ipupdate.conf
./firmware/ramdisk/root/etc/alert_email.conf
./firmware/ramdisk/root/etc/ld.so.conf
./firmware/ramdisk/root/etc/avahi/avahi-daemon.conf
./firmware/ramdisk/root/etc/netatalk/extmap.conf
./firmware/ramdisk/root/etc/nsswitch.conf
./firmware/module/crfs/web/config/default_lighttpd.conf
./firmware/module/crfs/web/config/php-fpm.conf
./firmware/module/crfs/web/apache2_dav/conf/httpd.conf
./firmware/module/crfs/web/apache2_dav/conf/extra/httpd-languages.conf
./firmware/module/crfs/web/apache2_dav/conf/extra/httpd-dav.conf
./firmware/module/crfs/web/apache2_dav/conf/extra/httpd-autoindex.conf
./firmware/module/crfs/web/apache2_dav/conf/extra/httpd-manual.conf
./firmware/module/crfs/web/apache2_dav/conf/extra/httpd-multilang-errordoc.conf
./firmware/module/crfs/web/apache2_dav/conf/extra/httpd-vhosts.conf
./firmware/module/crfs/web/apache2_dav/conf/extra/httpd-userdir.conf
./firmware/module/crfs/web/apache2_dav/conf/extra/httpd-info.conf
./firmware/module/crfs/web/apache2_dav/conf/extra/httpd-ssl.conf
./firmware/module/crfs/web/apache2_dav/conf/extra/httpd-default.conf
./firmware/module/crfs/web/apache2_dav/conf/extra/httpd-mpm.conf
./firmware/module/crfs/web/apache2/certconf/wdnas-rest-api.conf
./firmware/module/crfs/web/apache2/certconf/wdnas-rest-api-trusted.conf
./firmware/module/crfs/web/apache2/conf/sites-enabled/restsdk.conf
./firmware/module/crfs/web/apache2/conf/sites-enabled/wdnas-ui.conf
./firmware/module/crfs/web/apache2/conf/httpd.conf
./firmware/module/crfs/web/apache2/conf/extra/available/httpd-languages.conf
./firmware/module/crfs/web/apache2/conf/extra/available/httpd-dav.conf
./firmware/module/crfs/web/apache2/conf/extra/available/httpd-autoindex.conf
./firmware/module/crfs/web/apache2/conf/extra/available/httpd-manual.conf
./firmware/module/crfs/web/apache2/conf/extra/available/httpd-multilang-errordoc.conf
./firmware/module/crfs/web/apache2/conf/extra/available/httpd-vhosts.conf
./firmware/module/crfs/web/apache2/conf/extra/available/httpd-userdir.conf
./firmware/module/crfs/web/apache2/conf/extra/available/httpd-info.conf
./firmware/module/crfs/web/apache2/conf/extra/ports.conf
./firmware/module/crfs/web/apache2/conf/extra/httpd-default.conf
./firmware/module/crfs/web/apache2/conf/extra/wdapp_web.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/mime.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/flvx.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/env.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/dav_fs.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/unixd.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/autoindex.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/mime_magic.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/log_config.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/dir.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/rewrite.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/alpha_custom.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/security2.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/actions.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/cgi.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/deflate.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/alias.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/mpm_prefork.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/negotiation.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/logio.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/setenvif.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/ssl.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/headers.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/php5.conf
./firmware/module/crfs/web/apache2/conf/mods-enabled/xsendfile.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/modsecurity.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-942-APPLICATION-ATTACK-SQLI.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-943-APPLICATION-ATTACK-SESSION-FIXATION.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/RESPONSE-951-DATA-LEAKAGES-SQL.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-930-APPLICATION-ATTACK-LFI.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-931-APPLICATION-ATTACK-RFI.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-949-BLOCKING-EVALUATION.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-911-METHOD-ENFORCEMENT.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/RESPONSE-954-DATA-LEAKAGES-IIS.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-903.9002-WORDPRESS-EXCLUSION-RULES.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-933-APPLICATION-ATTACK-PHP.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-910-IP-REPUTATION.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-901-INITIALIZATION.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/RESPONSE-959-BLOCKING-EVALUATION.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/RESPONSE-952-DATA-LEAKAGES-JAVA.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/RESPONSE-953-DATA-LEAKAGES-PHP.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/RESPONSE-950-DATA-LEAKAGES.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-905-COMMON-EXCEPTIONS.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-921-PROTOCOL-ATTACK.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-932-APPLICATION-ATTACK-RCE.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-913-SCANNER-DETECTION.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-912-DOS-PROTECTION.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-903.9001-DRUPAL-EXCLUSION-RULES.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/RESPONSE-980-CORRELATION.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf
./firmware/module/crfs/web/apache2/conf/modsecurity/crs-setup.conf
./firmware/module/crfs/dbus-1/system.d/avahi-dbus.conf
./firmware/module/crfs/dbus-1/system.conf
./firmware/module/crfs/etc/smtp.conf
./firmware/module/crfs/etc/nas/wdnotifier.conf
./firmware/module/crfs/etc/nas/notify.d/wdmcserver.conf
./firmware/module/crfs/etc/nas/notify.d/wddispatcher.conf
./firmware/module/crfs/etc/apache2/sites-available/wdnas-rest-api.conf
./firmware/module/crfs/etc/apache2/sites-available/wdnas-ui.conf
./firmware/module/crfs/etc/apache2/sites-available/wdnas-rest-api-trusted.conf
./firmware/module/crfs/etc/apache2/conf.d/orionversion.conf
./firmware/module/crfs/etc/rsyslog.d/wdlog.conf
./firmware/module/crfs/etc/rsyslog.d/wddispatcher.conf
./firmware/module/crfs/default/syslog.conf
./firmware/module/crfs/default/mt-daapd.conf
./firmware/module/crfs/default/wdlog.conf
./firmware/module/crfs/default/dhcp6c.conf
./firmware/module/crfs/default/udhcpd.conf
./firmware/module/crfs/default/resolv.conf
./firmware/module/crfs/default/routeap.conf
./firmware/module/crfs/default/s3.conf
./firmware/module/crfs/default/snmpd.conf
./firmware/module/crfs/default/gogoc.conf
./firmware/module/crfs/apache2/sites-available/wdnas-rest-api.conf
./firmware/module/crfs/apache2/sites-available/wdnas-ui.conf
./firmware/module/crfs/apache2/conf.d/orionversion.conf
./firmware/module/crfs/files/ups/upsd.conf
./firmware/module/crfs/files/ups/upssched.conf
./firmware/module/crfs/files/ups/upsmon.conf
./firmware/module/crfs/files/ups/ups.conf
./firmware/module/crfs/files/syslog_rotate.conf
./firmware/module/crfs/files/mke2fs.conf
./firmware/module/crfs/files/syslog_dai

对于rewrite.conf,主要读懂RewriteCond和RewriteRule两个关键字的含义就行了。

RewriteCond起到的是过滤作用
RewriteCond %{REMOTE_ADDR} !^127\.0\.0\.1$这句为例,如果%{REMOTE_ADDR}!^127\.0\.0\.1$正则匹配,即REMOTE_ADDR不是来自localhost的话,就使用紧邻着的下一句RewriteRule来重定向web请求。
RewriteRule ^(\w*).cgi$ /web/cgi_api.php?cgi_name=$1&%{QUERY_STRING} [L]这句为例,就是把所有访问xx.cgi文件的请求,都重定向到/web/cgi_api.php?cgi_name=xxx,即用cgi_api.php来分发请求,如果鉴权不通过,就不能访问该cgi文件。

这里的鉴权主要指的就是攻击者是否有普通用户登录的权限,也就是一般说的pre-auth和after-auth了。

我们主要关注的都是pre-auth的rce,所以从这个配置文件和从cgi_api.php里的逻辑可以看出,认证前能够访问的cgi文件只有webpipe.cgilogin_mgr.cgi,而前者内部也有鉴权,所以主要关注login_mgr.cgi

至此为止我们就分析出了攻击者易达的攻击面,如果要深挖的话还需要再读一下其他的配置文件,和ps -ef看看还开了哪些进程,能否通过httpd路由到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<IfModule rewrite_module>
RewriteEngine on
RewriteCond expr "%{REQUEST_URI} != '/xml/english.xml'"
RewriteCond expr "%{REQUEST_URI} != '/xml/lang.xml'"
RewriteRule ^/xml/(.*) /cgi-bin/webpipe.cgi
#RewriteRule /api/[0-9.]+/rest/(.*)\?(.*)$ /htdocs/api/rest/index.php?$2
#RewriteRule /api/[0-9.]+/rest/(.*) /htdocs/api/rest/index.php

RewriteCond %{HTTP_HOST} ^(.*)\.(:\d+)?$
RewriteRule ^(.*)$ http://%1%2$1 [L,R=301]

<Directory "/var/www/cgi-bin/">
RewriteCond %{REMOTE_ADDR} !^127\.0\.0\.1$
RewriteCond $1 !^abFiles$
RewriteRule ^(\w*).cgi$ /web/cgi_api.php?cgi_name=$1&%{QUERY_STRING} [L]
</Directory>
</IfModule>

漏洞分析

首先抓包看一下正常的请求包是什么样的,可以看出用户输入的密码其实是被base64之后再发往server端处理的

1
2
3
4
POST /cgi-bin/login_mgr.cgi HTTP/1.1
...
Cookie: PHPSESSID=058d44781ddc0be98f15233c8853476f; local_login=1
cmd=wd_login&username=admin&pwd=YWRtaW4%3D&port=

入口函数在cgiMain,该函数根据post请求里的cmd参数来选择使用哪个函数,这里我们主要看的就是wd_login函数

cgiMain

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
__int64 cgiMain()
{
bool v0; // zf
const char *v1; // rdi
signed __int64 v2; // rcx
char *v3; // rsi
const char *v4; // rdi
signed __int64 v5; // rcx
char *v6; // rsi
bool v7; // zf
const char *v8; // rdi
signed __int64 v9; // rcx
char *v10; // rsi
const char *v11; // rdi
signed __int64 v12; // rcx
char *v13; // rsi
const char *v14; // rdi
signed __int64 v15; // rcx
char *v16; // rsi
__int64 result; // rax
char v18; // [rsp+0h] [rbp-28h]

cgiFormString("cmd", &v18, 32LL);
v0 = memcmp(&v18, "wd_login", 9uLL) == 0;
if ( v0 )
{
wd_login();
result = 0LL;
}
else
{
v1 = "ui_check_wto";
v2 = 13LL;
v3 = &v18;
do
{
if ( !v2 )
break;
v0 = *v3++ == *v1++;
--v2;

wd_login

在我简单的处理了一下符号之后的伪代码如下。

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
int wd_login()
{
char *pos_dbl_slash; // r14
char *v1; // rsi
char *v2; // rdx
unsigned int login_successful; // er15
FILE *v4; // rax
FILE *v5; // r14
int v6; // ecx
unsigned int v7; // eax
bool v8; // zf
__int64 v9; // r14
char *v10; // rsi
FILE *v11; // rax
FILE *v12; // r13
__int64 v13; // r12
__int64 v14; // rdx
signed int v15; // er13
unsigned int v16; // er12
FILE *v17; // rax
FILE *v18; // r12
_BOOL4 v19; // ST10_4
_BOOL4 v20; // ST08_4
FILE *v21; // rbp
_BOOL4 v22; // er8
_BOOL4 v23; // er9
struct passwd *v24; // rax
signed __int64 v25; // rdx
__int64 v26; // rdx
FILE *v27; // rbp
struct passwd *v28; // r14
int v29; // er14
FILE *v30; // r12
FILE *v31; // rdi
time_t v33; // [rsp+8h] [rbp-1200h]
_BOOL4 v34; // [rsp+8h] [rbp-1200h]
__int64 v35; // [rsp+10h] [rbp-11F8h]
_BOOL4 v36; // [rsp+10h] [rbp-11F8h]
__int64 v37; // [rsp+18h] [rbp-11F0h]
__int64 v38; // [rsp+20h] [rbp-11E8h]
__int64 v39; // [rsp+28h] [rbp-11E0h]
char src[8]; // [rsp+30h] [rbp-11D8h]
char dest[8]; // [rsp+40h] [rbp-11C8h]
char username[8]; // [rsp+50h] [rbp-11B8h]
__int64 v43; // [rsp+58h] [rbp-11B0h]
__int64 v44; // [rsp+60h] [rbp-11A8h]
__int64 v45; // [rsp+68h] [rbp-11A0h]
__int64 v46; // [rsp+70h] [rbp-1198h]
__int64 v47; // [rsp+78h] [rbp-1190h]
__int64 v48; // [rsp+80h] [rbp-1188h]
__int64 v49; // [rsp+88h] [rbp-1180h]
char pwd_decoded[64]; // [rsp+90h] [rbp-1178h]
char pwd_b64[256]; // [rsp+D0h] [rbp-1138h]
char v52; // [rsp+1D0h] [rbp-1038h]
int v53; // [rsp+260h] [rbp-FA8h]
char v54; // [rsp+264h] [rbp-FA4h]
char v55; // [rsp+3CFh] [rbp-E39h]
char v56; // [rsp+3D0h] [rbp-E38h]
char v57; // [rsp+5CFh] [rbp-C39h]
char v58; // [rsp+5D0h] [rbp-C38h]
char v59; // [rsp+7CFh] [rbp-A39h]
char v60; // [rsp+7D0h] [rbp-A38h]
char v61; // [rsp+9D0h] [rbp-838h]
char v62; // [rsp+BD0h] [rbp-638h]
char v63; // [rsp+DCFh] [rbp-439h]
char s; // [rsp+DD0h] [rbp-438h]
char v65; // [rsp+FCFh] [rbp-239h]
char v66; // [rsp+FD0h] [rbp-238h]

memset(pwd_b64, 0, sizeof(pwd_b64));
memset(pwd_decoded, 0, sizeof(pwd_decoded));
memset(&v52, 0, 0x200uLL);
memset(&v56, 0, 0x200uLL);
memset(&v58, 0, 0x200uLL);
*(_QWORD *)username = 0LL;
memset(&v60, 0, 0x200uLL);
memset(&v61, 0, 0x200uLL);
memset(&v62, 0, 0x200uLL);
memset(&s, 0, 0x200uLL);
memset(&v66, 0, 0x200uLL);
v43 = 0LL;
v44 = 0LL;
v45 = 0LL;
*(_QWORD *)src = 0LL;
*(_QWORD *)dest = 0LL;
v46 = 0LL;
v47 = 0LL;
v48 = 0LL;
v49 = 0LL;
v33 = time(0LL);
cgiFormString("username", username, 32LL);
cgiFormString("pwd", pwd_b64, 256LL);
base64decode((u_char *)pwd_decoded, pwd_b64, 256);
pos_dbl_slash = index(username, '\\');
if ( !pos_dbl_slash )
{
if ( (unsigned int)is_username_allowed(username) )
{
login_successful = check_login(username, pwd_decoded);
v15 = 0;
v16 = 0;
}
  • 首先读取用户输入的username到username数组里,最大读取32个字节,读取pwd到pwd_b64数组里,最大读取256个字节
  • base64decode解密pwd_b64,将结果保存在pwd_decoded数组里,最大写入256个字节,但问题是pwd_decoded数组的size是64字节,所以会越界写入到pwd_b64数组里,但在这里不会影响程序的逻辑,因为pwd_b64在解密后就不会被用到。
  • is_username_allowed校验输入的用户名是否合法,该函数先将用户名里的大写字母转成小写,然后和一个全局字符串数组里的每个字符串比较,如果有任何一个匹配就返回0,代表非法,否则返回1,代表合法。
    • 之所以这样比较是因为它将所有注册的用户的账号密码都写入到了/etc/shadow文件里,而这个文件里的root, anonymous...等用户是linux系统使用的,而不是给注册用户使用的。
  • 然后将被溢出的数组pwd_decoded传给check_login函数。

check_login

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
__int64 __fastcall check_login(const char *username, const char *pwd_decoded)
{
FILE *v2; // rbp
struct passwd *v3; // rax
struct passwd *v4; // rbx
const char *v6; // rax
char password_copy_shadow[80]; // [rsp+0h] [rbp-C8h]
char password_copy_input[88]; // [rsp+50h] [rbp-78h]

v2 = fopen64("/etc/shadow", "r");
while ( 1 )
{
v3 = fgetpwent(v2);
v4 = v3;
if ( !v3 )
break;
if ( !strcmp(v3->pw_name, username) )
{
strcpy(password_copy_shadow, v4->pw_passwd);
fclose(v2);
strcpy(password_copy_input, pwd_decoded);
  • 按行读取/etc/shadow里的数据,并解析成passwd结构体。
  • 拷贝pw_passwd字段到栈上变量password_copy_shadow数组里
  • 拷贝pwd_decoded到栈上变量password_copy_input数组里,因为pwd_decoded是一个写入溢出的字符串,其长度最大是192字节(base64算法,最大解密出来就是输入字符串的3/4长度),而password_copy_input数组的size是88,所以在这个栈布局里就可以溢出到返回地址了。

如下是ida的stack layout视图,r代表返回地址,如图可以看到从password_copy_input数组到返回地址,一共是120个字节,而我们可以写入192个字节,所以可以劫持返回地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
-00000000000000C8 ; D/A/*   : change type (data/ascii/array)
-00000000000000C8 ; N : rename
-00000000000000C8 ; U : undefine
-00000000000000C8 ; Use data definition commands to create local variables and function arguments.
-00000000000000C8 ; Two special fields " r" and " s" represent return address and saved registers.
-00000000000000C8 ; Frame size: C8; Saved regs: 0; Purge: 0
-00000000000000C8 ;
-00000000000000C8
-00000000000000C8 password_copy_shadow db 80 dup(?)
-0000000000000078 password_copy_input db 120 dup(?)
+0000000000000000 r db 8 dup(?)
+0000000000000008
+0000000000000008 ; end of stack variables

漏洞模式

这个漏洞的模式就是写入的数据超出了数组本身的大小导致的写入越界,但实际造成栈溢出的地方是在更后面的strcpy的地方,相对来说其实比较隐蔽,strcpy这个函数会从源地址向目的地址拷贝数据,一直到遇到\0停止。

正常来说在往字符数组写入一个字符串的时候,都会把最后一个字节设置\0,但因为写入的越界,导致\0出现在了数组越界后的位置。

最终导致前面base64decode函数造成的写入越界向后传播,最终在某次strcpy的时候造成了栈溢出。

漏洞利用

正常来说栈溢出的漏洞利用只需要rop构造gadaget即可,但是对于64位架构的栈溢出来说,因为程序的装载基地址是0x400000,所以不考虑return to libc等情况,直接在程序体内来找合适的gadaget地址的话,不可避免的在写入地址的时候会遇到\x00,比如0000000000401D00这个地址,它的高位都是0。

所以在strcpy的时候,遇到高位的\x00就会被截断,所以在溢出的时候,最多就只能覆盖到返回地址,写入一个想到劫持到的地址,不能向后继续写入了。

如图可以看出,尽管我们溢出password_copy_input由于截断只能写到返回地址那个位置,进行一次gadaget。

但是我们可以寻找lea rsp, [rsp+??] ; retn这样的gadaget来抬升栈,通过stack pivot来将rsp指到wd_login栈上的pwd_decoded字符串里,而这个字符串的值显然是我们可以任意控制,并且不受\x00截断影响,它是base64解出来的。

所以到这里我们就可以进行多次gadaget了。

即我们要让pwd_decoded字符串里的内容形如,即可

1
AAAAA * ? + p64(gadaget_addr1) + 需要的pop的寄存器值 + p64(gadaget_addr2) + 需要的pop的寄存器值 + p64(gadaget_addr3)...

然后由于一般的cgi程序里其实都会调很多system函数,所以我们只要再通过多次gadaget传递我们需要的命令到调用system函数的地方,最终执行该代码就可以反弹shell了。

但这个cgi程序里有个非常有趣的地方,就是00000000004039B7这个地址,它既有栈抬升,又有call system。

所以我们需要的payload就是A * 120 + p64(0x4039B7) + system_cmd_str即可。

解释一下,在溢出覆盖返回地址后,会跳到00000000004039B7去call一次无效的system命令,然后lea rsp, [rsp+108h]栈抬升,此时rsp指向我们在pwd_decoded里的p64(0x4039B7) + system_cmd_str字符串。

然后再retn,弹出p64返回地址,再次跳回到00000000004039B7执行,此时rsp指向的就是要执行的反弹shell字符串,并传给rdi,作为system的参数执行,此时就成功的反弹shell了。

1
2
3
4
5
.text:00000000004039B7                 lea     rdi, [rsp]
.text:00000000004039BB call _system
.text:00000000004039C0 xor eax, eax
.text:00000000004039C2 lea rsp, [rsp+108h]
.text:00000000004039CA retn

具体的调试就留给读者权做练习了。

总结一下,iot的栈溢出,找gadaget的要点就是

  • 栈抬升
    • lea rsp, [rsp+?]
  • 找system,传参劫持过去。

参考链接