如何在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