CVE信息:https://nvd.nist.gov/vuln/detail/CVE-2024-39226
官方固件下载网址:https://dl.gl-inet.cn/ (下载固件版本:GL-AX1800 Flint 4.5.16)
宿主机(Ubuntu 18.04.6)配置网卡(用于和虚拟机机通信)
sudo tunctl -t tap0
sudo ifconfig tap0 192.168.3.1/24 up
qemu系统模拟(这里-cpu cortex-a15必须加,默认不加可以,但是模拟机起来执行指令会报Illegal instruction的错误)
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
arm虚拟机模拟起来后,配置虚拟机网卡
ifconfig eth0 192.168.3.2/24 up
将获取到的固件文件系统打包,并传到arm虚拟机内解压。
tar -zcvf squashfs-root.gz squashfs-root
scp squashfs-root.gz root@192.168.3.2:/
tar -zxvf squashfs-root.gz
挂载固件文件系统中的proc目录和dev目录到chroot环境
mount -t proc /proc/ ./squashfs-root/proc/
mount -o bind /dev/ ./squashfs-root/dev/
chroot squashfs-root sh
在启动项中/etc/init.d/nginx可以看见web服务是如何启动的
/usr/sbin/nginx -c /etc/nginx/nginx.conf -g 'daemon off;'
在启动web服务时,报错提醒
回头在我们找到web服务启动参数的地方,可以看见有对/var/log/nginx目录的创建,下面的也有,然后就是报错没有的目录手动创建。
最终所需创建下列目录可以成功运行nginx服务
mkdir -p /var/log/nginx
mkdir -p /var/lib/nginx/body
mkdir -p /var/run
创建所需目录之后,启动web服务之后尝试访问,结果是404。
上网搜搜看有没有前辈做过,发现有运行80_nginx-oui脚本即可成功的,这边尝试一下,虽然访问到了welcome,但是界面并没有出来,不知道什么原因。(注意!我这里陷入了一个死胡同,其实漏洞复现不需要web界面起来,只要服务起来直接打也是可以通的)
然后再去查看启动项,在/etc/init.d/boot中可以看到一些初始化的内容,运行一下尝试看看能不能显示web页面
执行/etc/init.d/boot boot尝试,但最终一直在打印”Waiting for wireless generation ...“,去检索一下看看。在此处找到点,可以知道它是循环检查/etc/config/wireless是否为空。
再次检索/etc/config/wireless看看。
根据检索信息,在/etc/uci-defaults/network_gl中找到了echo "" > /etc/config/wireless,那么该脚本就是对应配置相关文件。
那么接下来尝试运行/etc/uci-defaults/network_gl以及/etc/init.d/boot boot
虽然报了一些错,但是执行完后启动nginx成功访问到web页面
接下来根据CVE信息去查看漏洞点 (s2s.so中的enable_echo_server函数):
从请求体中的json数据中获取port,下面对获取到的port的字符串进行了校验,但是并不严格。
atoi 函数会尝试将字符串中的字符转换为整数,直到遇到非数字字符为止。
此处仅仅校验了端口是否合法,导致可以拼接字符串触发命令执行。
查看poc信息:
curl -H 'glinet: 1' 127.0.0.1/rpc -d '{"method":"call", "params":["", "s2s", "enable_echo_server", {"port": "7 $(touch /root/test)"}]}'
不管三七二十一,ssh连上打一下试试看。
OK还是报错了。
检索错误信息“internal error",在rpc.lua中找到报错信息。
在error_response下紧接着便是访问的权限判断。
这里注意到,使用了ubus.call。
由于该固件是基于OpenWrt开发而来的,且根据OpenWrt官网内容,可以知道:ubus可以和ubusd服务器交互,因此前面报错,可能与ubusd服务未启动有关。
接着看:如果为本地请求且含义glinet标头才返回true
现在我们手动启动一些ubusd服务再打一次试试看(ubusd启动占用终端,再起一个ssh)。OK还是报错。
这里想起之前看到的一个gl.conf中有一些配置服务相关的信息,重新来查看一下。
将所有对 /index.html 的请求重定向到根目录 /。(permanent 表示这是一个永久性重定向)
且可以看见rpc相关请求是在/usr/share/gl-ngx/oui-rpc.lua中处理,那么后面可以去看看这块。
location /cgi-bin/{...} 这个 location 块匹配所有以 /cgi-bin/ 开头的请求 URL。
fastcgi_pass unix:/var/run/fcgiwrap.socket: 将请求转发给 FastCGI 进程,使用 Unix 域套接字 /var/run/fcgiwrap.socket。fcgiwrap 是一个用于处理 FastCGI 请求的简单 FastCGI 服务器,它通过这个套接字与 Nginx 进行通信。 (怀疑报错可能和这个FasrCGI有关,后面尝试启动它再打试试)
location ~.*\.(html|png|jpg|svg)${...} 配置了对特定文件类型的缓存控制,确保这些文件不会被缓存或在缓存过期后必须重新验证,以保证客户端始终获得最新的内容。
接下来看看oui-rpc.lua的内容
在rpc_method_call功能主要包括:
检查参数数量是否少于 3
验证 sid、object 和 method 是否都是字符串类型,验证 args 是否存在且为表(table)类型
验证sid,通过rpc.access判断是否有访问权限
最后使用rpc.call调用请求的函数并传递参数。
另外,在oui-rpc.lua开头
那么rpc.call其实就是加载oui文件下的rpc.lua里面的call函数。
再次返回rpc.lua中查看call函数。
该函数的功能是根据 object 和 method 动态调用方法。如果 object 的方法还未加载,它会尝试从指定路径加载相应的 Lua 脚本,并将脚本中的函数缓存到 objects 表中。之后,它会尝试从缓存中调用指定的方法。如果方法不存在,则调用 glc_call 进行处理。
再看glc_call函数:该函数的主要作用是通过 HTTP POST 请求将对象、方法和参数传递给指定的 CGI 脚本 /cgi-bin/glc,并处理该脚本返回的结果。
这里如果直接访问/cgi-bin/glc不报错的话,或许可以直接远程命令执行。后面再看,先解决本地执行的问题。
OK,大概先分析到这,接下来根据前面的分析去启动fcgiwrap服务再次尝试。
首先去查看一下参数:
-f:将 CGI 脚本的标准错误输出(stderr)通过 FastCGI 发送。
-c <number>:指定要预先分叉(prefork)的进程数量。预分叉是一种在多进程环境中常用的技术,用于提前创建多个子进程,以便更快地响应请求。
-s <socket_url>:指定要绑定的套接字 URL。
-h:显示帮助消息并退出。
-p <path>:限制执行到指定的脚本。
由于nginx服务是依靠多进程进行通信的,感觉运行fcgiwrap是需要-c和-s指定参数。
接下来启动fcgiwrap服务并重启nginx服务然后打poc先试试看。
ok,成功了。
那么回到之前分析的glc_call函数函数中 /cgi-bin/glc这个点,先访问一下试试,看看是否会报错,发现没有,可能有点搞头。
改为cgi-bin/glc打试试看,ok,可以看见成功命令执行。