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