如何在DIR 882中拿到一个方便调试的shell
0x10 前言
对前一段时间发现的几个命令注入漏洞做下记录:CVE-2022-28571 && CVE-2022-28572 CVE-2022-28571 这个漏洞的产生是因为Telnetd参数过滤不完整导致的,由于需要知道密码所以比较鸡肋,类似于授权后的命令注入,发现的过程很有意思,这篇文章主要介绍这个漏洞。CVE-2022-28572 这个漏洞是Tenda AX18 系列的一个命令注入漏洞,其实应该还有两个点可以触发这个漏洞,这里我交了一个比较好构造的一个。CVE-2022-28573 是一个没水平Dlink 823 pro的命令注入(太经典了)
0x20 漏洞分析 (28571)
起因是我想复现D-link DIR882 的那几个漏洞 比如:CVE-2021-45998, 但当我抓包以后发现DIR882 对包的检查很严格,每个请求的AUTH好像只能请求一次,试了一下发现漏洞确实存在,但当我想弹个shell回来的时候出了点问题,我用老一套命令
telnetd -l /bin/sh -p 2333
来反弹shell的时候,发现反弹不回来,然后我就想,是不是DIR882的Telnet实现有点不太一样,于是我找了一下DIR882的Telnet
发现Telnet存在lighttpd中,这就意味着他可以开启Telnetd(通过HTTP请求),我们在lighttpd
中找一下这个字符串
我们可以发现/start_telnet
这个路径,以及貌似是开启参数的telnetd -b 0.0.0.0
拿着我200块淘的真机一试,发现这个请求是不过认证的,一个GET请求就可以开启,尽管显示的是404,但Dlink882又可以地方会打印系统信息出来到一个文件里返回给用户(登录后,系统调试处),可以看到命令是执行了的,而且此时23端口也是打开了的。
这个时候漏洞复现的兴趣就没有了,开始漏洞挖掘,这里做一个假设,假设这个Telnet的密码是硬编码写入的,我们只要找到这个编码就可以拿到shell了(后面发现不是硬编码) 下面我们只要想办法找到密码就行了,通过对telnet尝试连接,我们发现
这里有一个dlinkrouter login
, 根据找tenda Telnet的经验,我查了这个字符串的引用,发现没找到,没找到?说明这个dlinkrouter 字符串应该是系统生成的,而telnetd 是被链接在 busybox里的,我们逆向busybox
通过login字符串的交叉引用,我们发现
我们可以发现sub_465960获取了uname, 这说明系统的用户名是dlinkrouter
加上后面的引用,我们就可以找到telnetd的调用过程了
login(); // readusername do { if ( *(_DWORD *)(_stdin + 72) ) { v11 = *(unsigned __int8 **)(_stdin + 16); if ( (unsigned int)v11 < *(_DWORD *)(_stdin + 24) ) { v12 = *v11; *(_DWORD *)(_stdin + 16) = v11 + 1; goto LABEL_27; } v13 = (int (*)(void))&_fgetc_unlocked; } else { v13 = (int (*)(void))&fgetc; } v12 = v13(); if ( v12 == -1 ) goto LABEL_67; LABEL_27: if ( v12 == 10 ) { if ( !--v10 ) goto LABEL_67; goto LABEL_20; } } while ( isspace(v12) ); username[0] = v12; if ( !fgets(&username[1], 30, stdin) || (v14 = &username[1], !strchr(&username[1], 10)) ) LABEL_67: exit(1); while ( isgraph((unsigned __int8)*v14) ) ++v14; *v14 = 0; LABEL_37: user_struct = getpwnam(username); // 从密码文件中取得指定账号的数据 // #include // #include // struct passwd // { // char *pw_name; /* 用户登录名 */ // char *pw_passwd; /* 密码(加密后) */ // __uid_t pw_uid; /* 用户ID */ // __gid_t pw_gid; /* 组ID */ // char *pw_gecos; /* 详细用户名 */ // char *pw_dir; /* 用户目录 */ // char *pw_shell; /* Shell程序名 */ // }; // pw_name = user_struct; if ( !user_struct ) { strcpy(username, "UNKNOWN"); goto LABEL_49; } v17 = **(unsigned __int8 **)(user_struct + 4); if ( v17 != 0x21 && v17 != 0x2A ) { if ( (v5 & 1) != 0 ) goto LABEL_53; if ( *(_DWORD *)(pw_name + 8) ) goto LABEL_82; v26 = "/etc/securetty"; v18 = (_DWORD *)sub_463D20("/etc/securetty", sub_4063C8); while ( sub_463DE4(v18, &v26, 459009, "# \t") && strcmp(v26, dword_485294) ) v26 = 0; sub_463D74(v18); if ( v26 ) { LABEL_82: if ( **(_BYTE **)(pw_name + 4) ) // pw_passwd { LABEL_49: if ( !sub_45D554(pw_name) ) goto LABEL_50; } LABEL_53: alarm(0); if ( v4 || access("/etc/nologin", 0) ) { fchown(0, *(_DWORD *)(pw_name + 8), *(_DWORD *)(pw_name + 12)); fchmod(0, 384); sub_45C9AC(pw_name); v22 = *(const char **)(pw_name + 24); if ( !v22 || !*v22 ) v22 = "/bin/sh"; sub_465A64(v22, (v5 & 4) == 0, 1, pw_name); v23 = open("/etc/motd", 0); if ( v23 >= 0 ) { fflush(stdout); sub_407824(v23, 1); close(v23); } if ( !*(_DWORD *)(pw_name + 8) ) syslog(6, "root login%s", v8); signal(14, 0); signal(2, 0); if ( v3 ) v24 = *(const char **)(pw_name + 24); else v24 = "/usr/bin/cli"; sub_465810(v24, 1, 0, 0); } v19 = (_DWORD *)sub_4063C8((int)"/etc/nologin"); if ( !v19 ) { puts("\rSystem closed for routine maintenance\r"); goto LABEL_67; } while ( 2 ) { if ( v19[18] ) { v21 = (unsigned __int8 *)v19[4]; if ( (unsigned int)v21 < v19[6] ) { v20 = *v21; v19[4] = v21 + 1; LABEL_57: if ( v20 == 10 ) LOBYTE(v20) = 13; sub_407048(v20); continue; } v20 = ((int (__fastcall *)(_DWORD *))_fgetc_unlocked)(v19); } else { v20 = ((int (__fastcall *)(_DWORD *))fgetc)(v19); } break; } if ( v20 == -1 ) { fflush(stdout); fclose(v19); goto LABEL_67; } goto LABEL_57; } } BOOL __fastcall sub_45D554(int a1) { const char *pw_passwd; // $s2 BOOL v2; // $s0 int v3; // $s1 int v4; // $s3 int v5; // $v0 if ( a1 ) { pw_passwd = *(const char **)(a1 + 4); v2 = 1; if ( !*pw_passwd ) return v2; } else { pw_passwd = "aa"; } v3 = sub_468334(0, (int)"Password: "); v2 = 0; if ( v3 ) { v4 = sub_46521C(); // v4 = enc(password,slat) // slat= "aa" || user->uid v2 = strcmp(v4, pw_passwd) == 0; free(v4); v5 = strlen(v3); memset(v3, 0, v5); } return v2; }
通过对关键函数的分析,我们弄清了Dlink882 Telnet的登录流程为:
- 输入用户名
- 系统根据用户名去
/etc/passwd
里面找密码 - 对比加盐后的密码值 那么我们就要去找到这个加盐后的密码值了,通过对主程序
prog.cgi
的逆向我们发现/etc/passwd
里的密码是由登录密码拼上@twsz2018
组成的
使用登录密码拼上那串字符我们就可以登录进Dlink 882的Telnet,进去之后发现是一个cli. 分析busybox我们发现
if ( !*(_DWORD *)(pw_name + 8) ) syslog(6, "root login%s", v8); signal(14, 0); signal(2, 0); if ( v3 ) v24 = *(const char **)(pw_name + 24); else v24 = "/usr/bin/cli"; sub_465810(v24, 1, 0, 0); }
是/usr/bin/cli
,上了个大当。我们继续分析cli
int __fastcall cmd_ping(int a1, const char *a2, _DWORD *a3, int a4) { int v8; // $s0 int v9; // $s1 char v11[128]; // [sp+18h] [-80h] BYREF memset(v11, 0, sizeof(v11)); v8 = snprintf(v11, 128, "%s ", a2); if ( a4 > 0 ) { v9 = 0; do { ++v9; v8 += snprintf(&v11[v8], 128 - v8, "%s ", *a3++); } while ( v9 != a4 ); } systemCmd(a1, (int)v11); return 0; } int __fastcall systemCmd(int a1, int a2) { int v4; // $s2 int v5; // $s0 char v7[256]; // [sp+18h] [-100h] BYREF v4 = -1; memset(v7, 0, sizeof(v7)); if ( a2 ) { v5 = popen(a2, "r"); if ( v5 ) { while ( fgets(v7, 256, v5) ) cli_bufprint(a1, "%s", v7); v4 = 0; cli_print(a1, " ", v7); pclose(v5); } } return v4; }
输入直接被丢给了popen,真是柳岸花明又一春,直接拼命令就可以注入了
后面发现如果要用telnetd反弹shell的话,在Dlink中命令要这样拼
telnetd -l /bin/sh -p 2333 -b 0.0.0.0
0x30 时间线
2022年3月8日,将漏洞上报给CVE 2022年4月1日,由于未添加版本信息被退回 2022年4月1日,重新上报给CVE 2022年5月2日,获得编号CVE-2022-28571
end
