GL-iNet 路由器 CVE-2024-39226 漏洞分析
2024-9-6 16:14:0 Author: www.freebuf.com(查看原文) 阅读量:11 收藏

1 前言

8月5日网上披露了 CVE-2024-399226[1],影响多款GL-iNet路由器,随后开始漏洞应急。起初对GL-iNet路由器不了解导致踩了很多坑、浪费了不少时间,因此在做完应急后对这次漏洞分析和固件仿真进行记录。

2 产品介绍

GL.iNet是一家专注于智能路由器和网络设备开发的科技公司。成立于 2009 年,总部位于中国,该公司的产品以OpenWrt操作系统为基础,提供高度的可定制性和灵活性。公司致力于为家庭、企业以及工业物联网环境提供可靠的网络解决方案。GL.iNet的设备以其开源特性、强大的功能和优秀的用户体验而受到开发者、网络安全专家和高级用户的青睐。

OpenWrt是一个基于 Linux 的开源嵌入式操作系统,专为网络设备(如路由器、网关和接入点)设计。与传统的路由器固件不同,OpenWrt不是单一的、不可变的固件,而是一个完整且可扩展的操作系统,允许自定义以适应任何应用程序。

OpenResty是一个基于Nginx的高性能Web平台,它将Lua脚本引擎嵌入到Nginx中,使开发者可以通过Lua脚本编写高度可定制的Web服务,用来处理复杂的web逻辑和API请求。OpenResty通常用于高并发、低延迟的Web应用程序开发,特别是在需要处理复杂逻辑或与外部服务交互时。

这种组合使得GL.iNet路由器不仅仅是一个网络设备,还可以作为一个小型的Web服务器或应用平台。

##3 环境模拟

3.1 固件提取

GL.iNet官网提供历史固件下载[2]。

固件版本:GL-AX1800 Flint 4.5.16

sysupgrade-glinet_ax1800文件夹下存在root文件。

$ file root 
root: Squashfs filesystem, little endian, version 4.0, 44613986 bytes, 4754 inodes, blocksize: 262144 bytes, created: Thu Mar 21 13:28:00 2024

使用binwalk,从root中提取Squashfs文件系统。

$ binwalk -Me root

查看bin/busybox得知是32位arm架构。

$ file squashfs-root/bin/busybox 
squashfs-root/bin/busybox: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-arm.so.1, stripped

###3.2 QEMU模拟

使用qemu-system-arm从系统角度进行模拟,此时需要一个arm架构的内核镜像和文件系统,可以在这个网站下载[3]。

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

启动虚拟环境。

$ 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

//默认可以不指定 cpu 模型,我在模拟过程中遇到报错所以指定了 cpu。

Illegal instruction

启动后用户名和密码都是root即可登录模拟的系统。

接下来在宿主机创建一个网卡,使qemu内能和宿主机通信。

宿主机安装依赖。

$ sudo apt-get install bridge-utils uml-utilities

将如下代码保存为net.sh并运行即可。

#!/bin/bash

# Enable IP forwarding
sudo sysctl -w net.ipv4.ip_forward=1

# Reset iptables
sudo iptables -t nat -F
sudo iptables -t nat -X
sudo iptables -P FORWARD ACCEPT

# Set up NAT
sudo iptables -t nat -A POSTROUTING -o ens33 -j MASQUERADE

# Accept traffic on tap0
sudo iptables -I FORWARD -i tap0 -j ACCEPT
sudo iptables -I FORWARD -o tap0 -m state --state RELATED,ESTABLISHED -j ACCEPT

# Create and configure tap0
sudo ip tuntap add dev tap0 mode tap
sudo ifconfig tap0 192.168.100.254 netmask 255.255.255.0 up

然后配置qemu虚拟系统的路由,在qemu虚拟系统中运行net.sh并运行。

#!/bin/sh
ifconfig eth0 192.168.100.2 netmask 255.255.255.0
route add default gw 192.168.100.254

//虚拟系统可能没有vimnano,使用echo一行一行写。

这样宿主机和模拟环境互通,使用scpsquashfs-root文件夹上传到qemu系统中的/root路径下。

scp -r squashfs-root/ [email protected]:/root

然后挂载procdev,最后chroot即可。

root@debian-armhf:~# mount -t proc /proc ./squashfs-root/proc
root@debian-armhf:~# mount -o bind /dev ./squashfs-root/dev
root@debian-armhf:~# chroot ./squashfs-root/ sh


BusyBox v1.33.2 (2024-03-21 13:28:00 UTC) built-in shell (ash)

/ # ls
bin      etc      lib      overlay  rom      sbin     tmp      var
dev      init     mnt      proc     root     sys      usr      www

4 漏洞复现

启动web服务,前文已经介绍过GL.iNet路由器利用OpenResty来增强其web管理界面和API的功能。而OpenResty是基于Nginxweb平台,内置Lua脚本支持,所以首先启动Nginx服务。

尝试运行/etc/init.dnginx脚本(/etc/init.d目录通常包含系统启动和管理各种服务的脚本,如果需要启动某个服务,通常可以在该目录中找到相应的脚本)。

查看/etc/init.d/nginx如何手动启动nginx

image

图1 nginx 脚本源码
image

图2 启动 nginx 报错
创建缺少的文件夹再次启动nginx

image

图3 再次启动 nginx
看样子nginx好像起来了,访问web却是404

image

图4 web 访问 404
这个时候已经没什么头绪了,find一下所有nginx相关文件试试。

image

图5 查找 nginx 相关文件
每个文件都看看,发现/etc/uci-defaults/80_nginx-oui脚本。

/etc/uci-defaults/80_nginx-oui脚本的主要作用是配置和调整Nginx的相关文件,确保Web服务能够正常运行。

尝试运行/etc/uci-defaults/80_nginx-oui看看是否能修复404问题。

image

图6 运行 /etc/uci-defaults/80_nginx-oui
image

图7 web 访问成功
成功修复,接下来尝试漏洞复现,先看一下披露的PoC

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

PoC来看好像只能通过127.0.0.1利用,先使用192.168.100.2试试。

image

图8 PoC 远程利用
拒绝访问,再使用127.0.0.1(之前的会话因为启动nginx,需要ssh再创建一个会话)。

image

图9 PoC 本地利用
这次报错为内部错误。继续找问题。根据PoC可知请求的路径是rpc,在/etc/nginx下的nginx配置文件中查找rpc相关信息。

/etc/nginx/conf.d/gl.conf找到请求rpc路径的处理方法。

image

图10 /etc/nginx/conf.d/gl.conf 源码
跟进到/usr/share/gl-ngx/oui-rpc.lua

代码来看/usr/share/gl-ngx/oui-rpc.lua是处理HTTP POST请求jSON-RPC调用的。

image

图11 /usr/share/gl-ngx/oui-rpc.lua 源码开头部分
以上代码实现模块导入、请求方式验证和读取请求体。

要注意ubus服务是OpenWrt系统中一个进程间通信框架,需要启动。

HTTP请求仅允许POST,拒绝其他方式访问。

/usr/share/gl-ngx/oui-rpc.lua定义了多个处理函数,每个函数对应不同的rpc方法(因为PoC通过call方法调用s2s.enable_echo_server进行攻击,所以只截取rpc_method_call方法代码)。

image

image

图12 /usr/share/gl-ngx/oui-rpc.lua 源码核心部分
rpc_method_call进行参数校验、会话检查和Ubus调用:

  1. 确保params中至少三个元素且元素类型正确。

  2. 检查sid是否有效,并通过rpc.access验证访问权限。

  3. 如果上述判断均通过,使用rpc.call执行指定的Ubus对象和方法。

继续跟进/usr/lib/lua/oui/rpc.lua查看rpc.accessrpc.call实现。

image

图13 M.seesion 和 M.access 函数源码
access通过is_local判断是否本地请求。对于本地请求和glinet标头的请求,总是允许访问(确定了只能本地利用)。

image

图14 M.call 函数代码
M.call函数是核心的rpc调用处理器,执行以下步骤:

  1. 检查请求的对象是否已加载,如果未加载,则尝试从/usr/lib/oui-httpd/rpc/目录下加载脚本文件。

  2. 如果脚本文件存在且加载成功,将对象的方法注册到objects表中。

  3. 如果无法从/usr/lib/oui-httpd/rpc/目录下加载脚本文件或者找不到对象或方法,则调用glc_call执行。

查看/usr/lib/oui-httpd/rpc/目录下是二进制s2s.so文件,无法直接加载,则通过glc_call调用/cgi-bin/glc执行 C 程序实现的 RPC 方法。

继续跟进/www/cgi-bin/glc文件。

image

图15 /www/cgi-bin/glc 反编译源码
大致实现逻辑与/usr/share/gl-ngx/oui-rpc.lua类似,请求方式验证和读取请求体后动态加载并调用函数,区别在于/www/cgi-bin/glc使用dlopen动态加载对应的共享库(.so文件)。

如此看来PoC满足/usr/share/gl-ngx/oui-rpc.lua/usr/lib/lua/oui/rpc.lua两段代码逻辑。

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

请求发送一个POST请求到rpc路径,携带JSON数据:

{
    "method": "call",
    "params": ["", "s2s", "enable_echo_server", {"port": "7 $(touch /root/test)"}]
}

权限校验参数检查均通过但是报内部错误,打印nginx日志看看。

修改/etc/nginx/nginx.conf并重启nginx

image

图16 修改 /etc/nginx/nginx.conf 配置文件
再用PoC测试一次查看日志。

image

图17 nginx 日志信息
报错信息显示ubus-proxyfcgiwrap未启动或未正常配置,尝试启动ubus``fcgiwrap

ubus: /sbin/ubusd
fcgiwrap: /etc/init.d/fcgiwrap

image
image
image

图18 PoC 本地利用成功
终于复现成功了。

5 漏洞分析

漏洞只能本地利用未免有些太鸡肋了,继续进行漏洞分析,再尝试寻找远程利用的方法。

通过PoC可知漏洞通过s2s``API传递恶意shell命令,分析一下/usr/lib/oui-httpd/rpc/s2s.so

漏洞出现在s2s.enable_echo_server检查并启动echo_server过程中:

image

图19 /usr/lib/oui-httpd/rpc/s2s.so 反编译源码
虽然代码中检查了port参数是否为有效的数字,但没有严格限制其内容,仅验证了其是否为正数且小于 65535。然而,在字符串形式下,它仍然允许嵌入特殊字符,如$(),这些字符可以被 shell 解释器解析为命令。

v16(v27, 128, "%s -p %s -f", "/usr/bin/echo_server", v9);中,port参数 (v9) 被直接传递给snprintf函数,生成的命令字符串随后通过system(v27);执行。

由于v9可以包含类似7 $(touch /root/test)的字符串,shell 会执行其中的命令touch /root/test,导致命令注入。

漏洞成因分析起来还是比较容易的,最后一个问题,如何通过远程实现漏洞利用。公开的PoC是通过rpc路径调用call方法触发s2s.enable_echo_server漏洞。然而,由于会在rpc.access阶段进行权限校验,因此需要找到一个不需要权限校验的路径来执行call方法。

回过头再看一眼/usr/lib/lua/oui/rpc.luaglc_call方法。

image

图20 glc_call 函数源码
如果直接请求/cgi-bin/glc路径,将会调用glc_call函数。glc_call函数会向另一个内部路径(/cgi-bin/glc)发起一个内部 HTTP POST 请求,并传递方法名称、参数等信息。执行call方法并且跳过之前的权限校验,修改PoC尝试远程利用。

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

image
image

图21 PoC 远程利用成功

6 总结

漏洞应急最烦环境弄不好,这次就因为一开始不了解GL-iNet路由器导致纯在瞎折腾浪费时间,最后在大佬的指点下搭建好环境。记录过程中尽量复刻了当时工作的操作顺序,逻辑上有很多地方其实有更简单的解决方法不用绕这么多弯,诸君见笑。

7 相关链接

  1. 漏洞详情

  2. 固件下载

  3. 内核镜像和文件系统下载


文章来源: https://www.freebuf.com/articles/web/410443.html
如有侵权请联系:admin#unsafe.sh