首页 > 其他分享 >cisco rv34x漏洞复现(利用失败)(求助帖)

cisco rv34x漏洞复现(利用失败)(求助帖)

时间:2023-03-19 20:11:24浏览次数:45  
标签:cisco get int object char json 复现 r0 rv34x

前言:

某公司技术面时提示我在漏洞利用方向上经验不足,并且需要提交一份无poc的漏洞报告,才有了今天的这篇文章记录

在翻找漏洞的时候看到这个漏洞

d

 

 

 看到是缓冲区溢出类型的漏洞想着去复现一下

漏洞版本区间

 

其中CVE-2022-20827CVE-2022-20841为命令注入攻击,通过中间人的攻击去进行利用并且已经有了相关了漏洞信息和复现,所以我们的目标就盯上了CVE-2022-20842,猜测是缓冲区溢出攻击的漏洞

所以我下载了1.0.0.3.26和1.0.03.28版本

下载链接:https://software.cisco.com/download/home/286287791/type/282465789/release/1.0.03.29?catid=268437899

固件提取及环境搭建:

本人ubuntu环境是22.04  binwalk对ubuntu18以上的版本不是很有好,经过尝试binwalk并不能提取出openwrt-comcerto2000-hgw-rootfs-ubi_nand.img里的文件系统,所以我这里选择使用了unblob这个工具(还可以避免binwalk将var重定向到/dev/null的这个问题)

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

unblob最初由ONEKEY开发和维护,再加上binwalk的更新频率在逐渐减少,所以我个人挺推荐这个工具的(最主要的是它对高版本ubuntu友好)

安装顺序如下:

git clone https://github.com/onekey-sec/unblob.git
cd unblob
poetry install --no-dev
sudo apt install e2fsprogs p7zip-full unar zlib1g-dev liblzo2-dev lzop lziprecover img2simg libhyperscan-dev zstd
poetry run unblob --show-external-dependencies

 

 安装成功后我们就可以看到所以的依赖组件是否安装成功

 

 

 工具介绍及下载链接Installation - unblob - extract everything!(有多种下载方式)

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

然后我们使用 unblob去进行文件提取

poetry run unblob RV34X-v1.0.03.26-2022-01-06-02-47-46-AM.img

 

 

 接下来就是固件模拟

 

 

 该款设备是 arm32位 具体的模拟过程看下图

这是我改写的sh脚本(别忘了下载依赖的文件)

#!/bin/bash
sudo tunctl -t tap0 -u `whoami`
sudo ifconfig tap0 192.168.2.1/24
qemu-system-arm -M vexpress-a9 -kernel vmlinuz-3.2.0-4-vexpress -initrd initrd.img-3.2.0-4-vexpress -drive if=sd,file=debian_wheezy_armhf_standard.qcow2 -append "root=/dev/mmcblk0p2 console=tty0" -net nic -net tap,ifname=tap0,script=no,downscript=no -nographicz

运行 sudo start-armhf.sh

在qemu中设置ip属性

ifconfig eth0 192.168.2.2/24

qemu跟虚拟机能ping通即可

 

 

 

然后在虚拟机中

首先将文件系统压缩(避免scp传文件时扰乱文件中的软连接)
tar zcvf 1.tar rootfs/
然后将文件系统上传至qemu
sudo scp -r 1.tar [email protected]:/root/

回到qemu中

tar zxvf 1.tar
chmod -R 777 rootfs
cd rootfs
mount --bind /proc proc mount --bind /dev dev chroot . sh

 

 

 这样的话我们就完成了基础的环境模拟,接下来就是运行起nginx的过程

完整的探索过程我就不放了,这里我直接放上启动服务的步骤,如果想自己一步一步的开始乐意直接启动nginx服务然后根据报错去向上逆推

/etc/init.d/boot boot   //初始化环境的创建
generate_default_cert   //生成ssl证书文件
/etc/init.d/confd start //启动confd服务
/etc/init.d/nginx start //启动nginx服务

然后访问192.168.2.2

 

 

 可以看到web服务能成功运行

漏洞复现:

在漏洞diff之前,又出现了一个致命问题,burp不能抓取到该款路由器的数据包,一旦开启burp,页面会报错,不能进行下一步操作

 

 

 在相关文章中看到作者说删除掉lan.http.conf文件的最后一行,就可以自由的进行抓包,

 

 

 也就是return 301 这行,但经过测试依旧是抓不到数据包,后来偶然看到cyberangel师傅的文章说是burp版本过高导致的(本人burp版本2021.12),所以将jdk版本降级到8,重新下载了低版本的burp(又是一天多时间的浪费,我真的是,别忘了保存快照,复现完返回去)

 

 

 成功抓到了数据包

通过对数据包的简单分析,再对文件上传的类型的数据由/cgi-bin/upload.cgi去进行处理,而对数据修改和信息定义则是由/cgi-bin/jsonrpc.cgi中进行处理,因为漏洞信息提示由于web输入导致所有我们则对俩版本中的jsonrpc.cgi进行diff判断

diff界面如下:

 

 

 0.66差别的一般都是函数的修改,比如这样的:

 

 

 而补丁的话则是在原代码逻辑中加上响应的长度或者输入判断,所有代码的差异不会特别大,这里我们着重看0.96-0.81这四个栏

其中在函数sub_12C1C中有了明显的一个修补

 

到1.28版本去进行查看代码逻辑如下

 

 

 相较于1.26没有^[A-Fa-f0-9]{64}$这块正则处理

在追随上层逻辑的时候可以看出来

nt __fastcall sub_13500(int *a1)
{
  int v2; // r4
  char *v3; // r8
  int v4; // r1
  char *v5; // r2
  char *v6; // r0
  char *v7; // r5
  int v8; // r7
  int v9; // r0
  const char *v10; // r2
  int v11; // r11
  int v12; // r6
  int v13; // r9
  int v14; // r0
  int v15; // r0
  int v16; // r0
  const char *v17; // r1
  const char *v18; // r2
  int v19; // r0
  int v20; // r0
  int v21; // r0
  int v22; // r0
  int result; // r0
  char s[296]; // [sp+8h] [bp-128h] BYREF

  v2 = json_object_new_object();
  v3 = getenv("REMOTE_ADDR");
  v6 = sub_12C1C((int)v3, v4, v5);
  v7 = v6;
  if ( v6 )
  {
    v8 = StrBufCreate((int)v6);
    if ( is_file_exist() )
    {
      v9 = json_object_from_file("/tmp/websession/session");
      v11 = v9;
      if ( v9 )
      {
        v12 = *(_DWORD *)(json_object_get_object(v9) + 32);
        while ( v12 )
        {
          v13 = *(_DWORD *)(v12 + 4);
          v12 = *(_DWORD *)(v12 + 8);
          if ( json_object_object_get_ex() )
          {
            memset(s, 0, 0x100u);
            if ( json_object_object_get_ex() )
            {
              v14 = json_object_get_string(0);
              StrBufSetStr(v8, v14);
            }
            sprintf(s, "%s/%s", "/tmp/websession/token", v7);
            remove(s);
            json_object_object_del(v13, v7);
          }
        }
        json_object_to_file_ext("/tmp/websession/session", v11, 2);
      }
      else
      {
        error((int)"(%d)session file access error. ", 775, v10);
      }
    }
    if ( StrBufToStr(v8) )
    {
      v15 = json_object_new_int(0);
      json_object_object_add(v2, "code", v15);
      v16 = json_object_new_string("remove sessionid success");
      json_object_object_add(v2, "errstr", v16);
      v17 = (const char *)StrBufToStr(v8);
      v18 = "";

 

 

 

 这段逻辑是在用户退出的时候用于对session值得删除,一开始以为是个任意文件删除,试了半天后也没成功才开始正式得看正则,

^[A-Fa-f0-9]{64}$这段正则得意识是从头到尾匹配64个字符串同时不区分大小写,后来猜测是对cve-2022-20705得统一修复(也有可能是本人过于菜没有复现出来...) 但是并没有找到相关了缓冲区溢出得补丁,故怀疑是不是diff错了地方,所以重新去分析了数据包,

当我们在web端设置数据得时候都是get_什么什么得形式所以我们在jsonrpc.cgi进行搜索

程序会判断method后得值来根据以此跳转程序逻辑

 

 

 但是程序只给了"set_" 并没有给出后续得内容 所以继续跟进sub_13044 和sub13e7c

其中sub_13044函数

 

 它是用来判断session值是否合法

而sub_13e7c函数

 

 则是由jsonrpc_set_config函数进行处理,处理后得返回值再依次判断,看来函数判断得主战场并不在这里,根据这个函数去进行搜索

 

所以转到libjsess.so文件去进行代码审计,搜索关键词“set"可以看到所以得set类型以及json 参数定义

比如我们设置log页面得时候,它有这些参数

 

 而设置一些页面得时候他的程序逻辑如下

 

 其中setpre_什么什么是程序得一个分支处理程序,diff完整截图如下

 

 其中setpre_snmp引起了我得注意

 

 很明显得一个长度限制得补丁,猜测是CVE-2022-20842,后来再查阅官方得版本更新文档里面才发现是CVE-2022-20753,我擦那20842这个洞在哪呢

关键代码逻辑如下:

int __fastcall setpre__snmp(int a1, int a2, _DWORD *a3)
{
  int v4; // r7
  int v5; // r6
  const char *v6; // r4
  int v7; // r10
  int v8; // r5
  int v9; // r1
  const char *v10; // r9
  int v11; // r1
  const char *v12; // r9
  int v13; // r0
  int v14; // r9
  int v15; // r0
  const char *v16; // r3
  unsigned __int8 *v17; // r1
  char *v18; // r3
  int v19; // r2
  int v20; // t1
  bool v21; // zf
  int v22; // r3
  const char *v23; // r2
  int v24; // r3
  const char *v25; // r3
  bool v26; // zf
  int v27; // r0
  char *v28; // r0
  char *v29; // r9
  int v30; // r1
  int v31; // r0
  char *v32; // r0
  char *v33; // r9
  int v34; // r1
  int v35; // r0
  int v36; // r0
  int v37; // r2
  int v38; // r0
  int v39; // r2
  int v40; // r0
  int v41; // r0
  int v42; // r0
  const char *v43; // r2
  bool v44; // zf
  int v45; // r5
  int i; // r4
  int v47; // r8
  int v48; // r0
  int result; // r0
  const char *v50; // [sp+10h] [bp-468h]
  const char *v51; // [sp+14h] [bp-464h]
  char *s1; // [sp+18h] [bp-460h]
  int v53; // [sp+1Ch] [bp-45Ch]
  FILE *stream; // [sp+20h] [bp-458h]
  int v55; // [sp+24h] [bp-454h]
  int v56; // [sp+28h] [bp-450h]
  int v57; // [sp+30h] [bp-448h]
  int v58; // [sp+34h] [bp-444h]
  int v60; // [sp+40h] [bp-438h] BYREF
  int v61; // [sp+44h] [bp-434h] BYREF
  int v62; // [sp+48h] [bp-430h] BYREF
  char *v63; // [sp+4Ch] [bp-42Ch] BYREF
  char v64[128]; // [sp+50h] [bp-428h] BYREF
  char s[128]; // [sp+D0h] [bp-3A8h] BYREF
  char v66[11]; // [sp+150h] [bp-328h] BYREF
  char v67[245]; // [sp+15Bh] [bp-31Dh] BYREF
  char str[552]; // [sp+250h] [bp-228h] BYREF

  v4 = jsess_get_sess_sock(g_h_sess_db);
  v5 = jsess_get_sess_tid(g_h_sess_db);
  v56 = jsonutil_get(a2, "SNMP-USER-BASED-SM-MIB");
  if ( v56 )
  {
    if ( maapi_exists(v4, v5, "/SNMP-USER-BASED-SM-MIB/usmUserTable/usmUserEntry") )
      maapi_delete(v4, v5, "/SNMP-USER-BASED-SM-MIB/usmUserTable/usmUserEntry");
    v6 = 0;
    v7 = 0;
    v51 = 0;
    v50 = 0;
    v53 = 0;
    s1 = 0;
    v55 = 0;
    while ( v7 < json_object_array_length(v56) )
    {
      v8 = json_object_array_get_idx(v56, v7);
      if ( json_object_object_get_ex(v8, "usmUserEngineID", &v60) )
      {
        v9 = json_object_get_string(v60);
        if ( v9 )
        {
          if ( !match_regex("(^(([0-9a-fA-F]){2}(:([0-9a-fA-F]){2})*)?$)", v9) )
            v55 = json_object_get_string(v60);
        }
      }
      if ( json_object_object_get_ex(v8, "usmUserSecurityName", &v60) )
        s1 = (char *)json_object_get_string(v60);
      if ( json_object_object_get_ex(v8, "usmUserPrivProtocol", &v60) )
      {
        v10 = (const char *)json_object_get_string(v60);
        if ( strcmp(v10, "1.3.6.1.6.3.10.1.2.1") )
        {
          if ( !strcmp(v10, "1.3.6.1.6.3.10.1.2.2") )
          {
            v6 = "des";
          }
          else if ( !strcmp(v10, "1.3.6.1.6.3.10.1.2.4") )
          {
            v6 = "aes";
          }
        }
      }
      if ( json_object_object_get_ex(v8, "usmUserPrivKey", &v60) )
      {
        v11 = json_object_get_string(v60);
        if ( v11 )
        {
          if ( !match_regex("(^[^%&\\\"'\\s]*$)", v11) )
            v53 = json_object_get_string(v60);
        }
      }
      if ( json_object_object_get_ex(v8, "usmUserAuthProtocol", &v60) )
      {
        v12 = (const char *)json_object_get_string(v60);
        if ( strcmp(v12, "1.3.6.1.6.3.10.1.1.1") )
        {
          if ( !strcmp(v12, "1.3.6.1.6.3.10.1.1.2") )
          {
            v51 = "md5";
          }
          else if ( !strcmp(v12, "1.3.6.1.6.3.10.1.1.3") )
          {
            v51 = "sha";
          }
        }
      }
      if ( json_object_object_get_ex(v8, "usmUserAuthKey", &v60) )
      {
        v13 = json_object_get_string(v60);
        v14 = v13;
        if ( v13 )
        {
          v15 = match_regex("(^[^%&\\\"'\\s]*$)", v13);
          v16 = v50;
          if ( !v15 )
            v16 = (const char *)v14;
          v50 = v16;
        }
      }
      if ( v55 )
      {
        v61 = 0;
        v62 = 0;
        memset(v64, 0, sizeof(v64));
        memset(s, 0, sizeof(s));
        v17 = (unsigned __int8 *)v55;
        v18 = v64;
        do
        {
          v20 = *v17++;
          v19 = v20;
          if ( !v20 )
            break;
          if ( v19 != ':' )
            *v18++ = v19;
        }
        while ( v17 );
        *v18 = 0;
        v21 = v50 == 0;
        if ( v50 )
          v21 = v51 == 0;
        v22 = !v21;
        v57 = v22;
        if ( v21 )
        {
          v41 = json_object_new_string((int)"");
          json_object_object_add(v8, "usmUserAuthKey", v41);
          v42 = json_object_new_string((int)"");
          json_object_object_add(v8, "usmUserPrivKey", v42);
        }
        else
        {
          if ( v6 )
            v23 = v6;
          else
            v23 = "";
          v24 = (int)v6;
          if ( v6 )
            v24 = 1;
          v58 = v24;
          v25 = (const char *)v53;
          v26 = v53 == 0;
          if ( v53 )
            v26 = v6 == 0;
          if ( v26 )
            v25 = "";
          v27 = sprintf(str, "perl /usr/bin/snmpkey %s %s %s %s %s", v51, v50, v64, v23, v25);
          v61 = StrBufCreate(v27);
          v62 = StrBufCreate(v61);
          stream = (FILE *)popen(str, "r");
          if ( stream )
          {

可以看到在接收usmUserPrivKey和usmUserPrivKey这俩参数的时候没有限制长度之间sprintf 复制进了str缓冲区,其中的正则表达是修复了cve-2021-1414这个命令注入漏洞

 

没办法,只能尝试复现这个漏洞,这个漏洞发生在snmp界面得设置处,我们即可抓包进行溢出尝试

第二个问题又来了,jsonrpc.cgi是以uwsgi的子进程开始的,,每次请求单独出现一个进程,请求结束进程结束,如果我们直接调试jsonrpc.cgi,那么可能会缺少uwsgi传递的一些关键数据和环境变量,那么调试的问题要如何解决

这里采用的方法是将jsonrpc.cgi patch,从而死循环,然后在gdb中修改程序正常运行从而调试

查看__libc_start_main 运行的第一个函数

 

然后再sub_1214c中将第一个bl修改为跳转自身,这样我们就实现了一个死循环

 

 

 

 然后开启gdb-multiarch ,设置好小端序和arm属性,

发起服务,然后查看进程

 

 然后使用响应的gdbserver去开启端口

./gdbserver-armel-static-8.0.1 192.168.2.2:4444 --attach 28903

在gdb中

target remote 192.168.2.2:4444

 

 程序成功卡在bl,0x12158

利用gdb修改内存数据的功能,将程序改回正常逻辑

set {int}0x12158=0xebfffed8 //数据内容根据ida的数据来,版本之间有些差别

 

 程序就会回到一个正常的流程当中,但是这种调试方法需要吐槽的是,不知道断点该如何设置,因为漏洞发生在.so文件当中,所以不能直接一步到位的去设置断点,需要运行到响应的函数处,才可以之间去设置漏洞前处断点

 

 

 

 程序卡在了这步,在sprintf(str, "perl /usr/bin/snmpkey %s %s %s %s %s", v51, v50, v64, v23, v25); 执行完后成功的溢出了,但是致命的几个问题来了,

1当用五千杂乱字符串时,程序不能成功卡住,但是当使用五千个a时,即可成功卡在该页面,中间也没有正则或者其它字符判断,

2程序无法覆盖pc寄存器,经过测试,3000多字符即可溢出,但是输入五六千个字符也不能溢出pc, 所以也就无法劫持程序流

 

猜测原因如下,因为溢出点发生在libjsess.so中, 无法覆盖jsonrpc.cgi的pc寄存器????

漏洞信息如下,应该是可以成功shell的

 

 本人较为愚钝,希望有成功复现的师傅可以指点迷津

 

标签:cisco,get,int,object,char,json,复现,r0,rv34x
From: https://www.cnblogs.com/zhuangzhouQAQ/p/17234085.html

相关文章