强网杯线下Router-路由器漏洞挖掘
2019-06-26 09:26:00 Author: xz.aliyun.com(查看原文) 阅读量:130 收藏

组委会说这个漏洞是刚报官方还没有披露,所以这里就不说具体的设备厂商和型号了,仅针对题目进行一个分析。

binwalk&firmware-mod-kit解压

之前有过解压bin文件的经验所以选择了直接用binwak解压
binwalk -Me ./xxx

但是发现解压出来的文件中比较有用的squashfs-root文件夹竟然是空的。于是赶紧去搜一下资料,发现同目录下一个以.squashfs的文件可以被解压为文件系统。就下载来试试,利用的是其中的一个.sh脚本。

unsquashfs_all.sh ./xxx.squashfs

这样一个完整的文件系统就被解压出来了。其中包含的文件htdocs是主要的挖掘对象。

漏洞搜寻

这里对文件夹中的cgbin的二进制文件进行分析,用的是之前NASA开源的工具。

看起来像是一个服务解析然后针对服务进行一个函数的调用。
这里我当时所用的技巧是直接定位system函数的位置,因为自己太菜了只知道可能有命令注入。。。

命令注入分析

captchacgi_main

undefined4 captchacgi_main(void)

{
  char *__s1;
  int iVar1;
  code *pcVar2;
  undefined4 uVar3;
  char acStack648 [256];
  undefined auStack392 [216];
  undefined auStack176 [164];

  __s1 = getenv("REQUEST_METHOD");
  if (__s1 == (char *)0x0) {
    __s1 = "no REQUEST";
LAB_0040b694:
    cgibin_print_http_status(400,0x420554,__s1);
    uVar3 = 0xffffffff;
  }
  else {
    iVar1 = strcasecmp(__s1,"GET");
    if (iVar1 == 0) {
      pcVar2 = FUN_0040b7e8;
      uVar3 = 0x40;
    }
    else {
      iVar1 = strcasecmp(__s1,"POST");
      if (iVar1 != 0) {
        __s1 = "unsupported HTTP request";
        goto LAB_0040b694;
      }
      pcVar2 = FUN_0040b734;
      uVar3 = 0x400;
    }
    cgibin_parse_request(pcVar2,0,uVar3);
    iVar1 = sess_generate_captcha(auStack392);
    if (iVar1 == 0) {
      printf(
            "HTTP/1.1 200 OK\r\nContent-Type: text/xml\r\n\r\n<?xml version=\"1.0\" encoding=\"utf-8\"?><captcha>\n\t<result>FAIL</result><message>NO SESSION</message>\n</captcha>"
            );
      uVar3 = 0;
    }
    else {
      sprintf(acStack648,
              "rndimage -f /htdocs/web/docs/captcha_%d.jpeg -p /usr/sbin/fonts -w 180 -t 40 %s",
              iVar1,auStack176);
      uVar3 = 0;
      system(acStack648);
      printf(
             "HTTP/1.1 200 OK\r\nContent-Type: text/xml\r\n\r\n<?xml version=\"1.0\" encoding=\"utf-8\"?><captcha><result>OK</result><message>/docs/captcha_%d.jpeg</message></captcha>"
             ,iVar1);
    }
  }
  FUN_0040b4e0();
  return uVar3;
}

这里反编译有一点奇怪,就是在sprintf函数这个位置的austack176并没有赋值,但是通过汇编的分析这个值是固定没有办法进行命令注入。。

lxmldbc_system

void lxmldbc_system(char *pcParm1,undefined4 uParm2,undefined4 uParm3,undefined4 uParm4)

{
  undefined4 local_res4;
  undefined4 local_res8;
  undefined4 local_resc;
  char acStack1036 [1028];

  local_res4 = uParm2;
  local_res8 = uParm3;
  local_resc = uParm4;
  vsnprintf(acStack1036,0x400,pcParm1,&local_res4);
  system(acStack1036);
  return;
}

这里的system参数的开放性很大可以仔细看一下,接着对这个函数进行一个交叉引用的查看。可以找到一个ssdpcgi_main

undefined4 ssdpcgi_main(int iParm1)

{
  undefined4 uVar1;
  char *__s1;
  char *pcVar2;
  char *pcVar3;
  char *pcVar4;
  int iVar5;
  char *pcVar6;

  uVar1 = 0xffffffff;
  if (iParm1 == 2) {
    __s1 = getenv("HTTP_ST");
    pcVar2 = getenv("REMOTE_ADDR");
    pcVar3 = getenv("REMOTE_PORT");
    pcVar4 = getenv("SERVER_ID");
    if ((((__s1 == (char *)0x0) || (pcVar2 == (char *)0x0)) || (pcVar3 == (char *)0x0)) ||
       (pcVar4 == (char *)0x0)) {
      uVar1 = 0xffffffff;
    }
    else {
      iVar5 = strncmp(__s1,"ssdp:all",8);
      if (iVar5 == 0) {
        __s1 = "%s ssdpall %s:%s %s &";
      }
      else {
        iVar5 = strncmp(__s1,"upnp:rootdevice",0xf);
        if (iVar5 == 0) {
          __s1 = "%s rootdevice %s:%s %s &";
        }
        else {
          iVar5 = strncmp(__s1,"uuid:",5);
          if (iVar5 == 0) {
            __s1 = "%s uuid %s:%s %s %s &";
          }
          else {
            iVar5 = strncmp(__s1,"urn:",4);
            if (iVar5 != 0) {
              return 0;
            }
            pcVar6 = strstr(__s1,":device:");
            if (pcVar6 == (char *)0x0) {
              __s1 = strstr(__s1,":service:");
              if (__s1 == (char *)0x0) {
                return 0;
              }
              __s1 = "%s services %s:%s %s %s &";
            }
            else {
              __s1 = "%s devices %s:%s %s %s &";
            }
          }
        }
      }
      lxmldbc_system(__s1,"/etc/scripts/upnp/M-SEARCH.sh",pcVar2,pcVar3,pcVar4);
      uVar1 = 0;
    }
  }
  return uVar1;
}

这里就有一个命令注入的风险,因为输入的参数是我们可以控制的,这样就发现了第一个漏洞,接下来继续逆一波。

offby\n

当时发现的时候感觉可以更改用户组的权限来达到执行更高指令的目的。

int phpcgi_main(int iParm1,int iParm2,int *piParm3)

{
  char *__s1;
  FILE *__stream;
  undefined4 uVar1;
  code *pcVar2;
  int iVar3;
  int iVar4;
  char acStack40 [24];

  if (iParm1 < 2) {
    iVar3 = -1;
    iVar4 = 0;
    goto LAB_004060e8;
  }
  iVar4 = sobj_new();
  if (iVar4 != 0) {
    sobj_add_string(iVar4,*(undefined4 *)(iParm2 + 4));
    sobj_add_char(iVar4,10);
    while (*piParm3 != 0) {
      sobj_add_string(iVar4,"_SERVER_");
      iVar3 = *piParm3;
      piParm3 = piParm3 + 1;
      sobj_add_string(iVar4,iVar3);
      sobj_add_char(iVar4,10);
    }
    __s1 = getenv("REQUEST_METHOD");
    if (__s1 != (char *)0x0) {
      iVar3 = strcasecmp(__s1,"HEAD");
      if ((iVar3 == 0) || (iVar3 = strcasecmp(__s1,"GET"), iVar3 == 0)) {
        pcVar2 = FUN_00405cdc;
      }
      else {
        iVar3 = strcasecmp(__s1,"POST");
        if (iVar3 != 0) goto LAB_004060e4;
        pcVar2 = FUN_00405aa0;
      }
      iVar3 = cgibin_parse_request(pcVar2,iVar4,0x80000);
      if (iVar3 < 0) {
        if (iVar3 == -100) {
          __stream = fopen("/htdocs/web/info.php","r");
          if (__stream != (FILE *)0x0) {
            fclose(__stream);
            cgibin_print_http_resp(1,"/info.php",&DAT_00420ca4,"ERR_REQ_TOO_LONG",0,0x420554);
          }
        }
        else {
          cgibin_print_http_status(400,"unsupported HTTP request","unsupported HTTP request");
        }
      }
      else {
        uVar1 = sess_validate();
        sprintf(acStack40,"AUTHORIZED_GROUP=%d",uVar1);
        sobj_add_string(iVar4,acStack40);
        sobj_add_char(iVar4,10);
        sobj_add_string(iVar4,"SESSION_UID=");
        sess_get_uid(iVar4);
        sobj_add_char(iVar4,10);
        uVar1 = sobj_get_string(iVar4);
        iVar3 = xmldbc_ephp(0,0,uVar1,stdout);
      }
      goto LAB_004060e8;
    }
  }
LAB_004060e4:
  iVar3 = -1;
LAB_004060e8:
  cgibin_clean_tempfiles();
  if (iVar4 != 0) {
    sobj_del(iVar4);
  }
  return iVar3;
}

信息泄漏漏洞

这个漏洞是一个老洞,但是厂商的修复方法很暴力,只是吧信息泄漏的信息改了,但其实可以leak别的地方的信息。
htdocs/web/getcfg.php

在这个php文件中,读者可以自己进行一个分析,网上也有对应的文章,这里选择leak的文件RUNTIME.WPS.WLAN-1.xml.php其中就可以读到需要的账户和密码,然后利用一些方法就可以get。因为这个方法是偏web个人的能力实在有限贴出链接:
https://blog.csdn.net/qq_33850304/article/details/92395201

关于路由调试

环境上这里是用qemu搭建的,调试工具利用的是ida。

qemu语句

- E  var=value 设置环境变量
- g  port      开启调试模式等待attach
chroot ./qemu-mips 指定根目录运行qemu-mips

完整的语句
echo $INPUT | chroot . ./qemu-mips -g $PORT -E HTTP_ST=$ST -E REMOTE_ADDR=1.1.1.1 -E REMOTE_PORT=1111 -E SERVER_ID=1 -E HOST="239.255.255.250:1900" /ssdpcgi ssdpcgi

ida调试

环境上这里是用qemu搭建的,调试工具利用的是ida。

qemu语句

- E  var=value 设置环境变量
- g  port      开启调试模式等待attach
chroot ./qemu-mips 指定根目录运行qemu-mips

完整的语句
echo $INPUT | chroot . ./qemu-mips -g $PORT -E HTTP_ST=$ST -E REMOTE_ADDR=1.1.1.1 -E REMOTE_PORT=1111 -E SERVER_ID=1 -E HOST="239.255.255.250:1900" /ssdpcgi ssdpcgi

ida调试

先设置remote debug选项

在设置debug选项

接着设置两个debug-options即可

这里主要是选择一下调试的类型,如果你打开了文件就基本不用设置,如果只是attach需要进行一下设置。

总结

因为一些原因这里就不贴出利用的poc了,已知漏洞下这些利用还是比较简单的。这次比赛发现了iot这个大世界的丰富精彩啊。。


文章来源: http://xz.aliyun.com/t/5468
如有侵权请联系:admin#unsafe.sh