type
status
date
slug
summary
tags
category
icon
password
这是我复现的第二个漏洞,其实也是命令执行的漏洞,但这个漏洞原始的cve里是内部执行的代码,虽然我在网上也找到了其他师傅开发的远程执行的poc,不过原理其实差不多。
到现在为止复现的这两的虽然架构不一样,不过都是命令执行的算是web端的漏洞,都是比较简单的洞。
这个漏洞主要影响的是
GL-iNet
路由器,简单介绍一下这个路由器的基本信息产品介绍
GL.iNet
是一家专注于开发智能路由器和网络设备的公司。该公司的产品通常基于 OpenWrt
操作系统,广泛应用于家庭、企业和工业物联网场景。GL.iNet
路由器以其开源、可定制性和强大的功能而著称,特别适合开发者、网络安全研究人员和高级用户。OpenWrt
是一个基于 Linux 的开源嵌入式操作系统,专为网络设备(如路由器、网关和接入点)设计。与传统的路由器固件不同,OpenWrt
不是单一的、不可变的固件,而是一个完整且可扩展的操作系统,允许自定义以适应任何应用程序。OpenResty
是一个基于 Nginx
的高性能 Web
平台,它将 Lua
脚本引擎嵌入到 Nginx
中,使开发者可以通过 Lua
脚本编写高度可定制的 Web
服务,用来处理复杂的 web
逻辑和 API
请求。OpenResty
通常用于高并发、低延迟的 Web
应用程序开发,特别是在需要处理复杂逻辑或与外部服务交互时。这种组合使得
GL.iNet
路由器不仅仅是一个网络设备,还可以作为一个小型的 Web
服务器或应用平台。这里我们后面会用到很多有关这个平台的一些基础信息,这里后面会介绍。有的iot的洞是基于固定的平台,很多时候就要去了解这个平台的基础配置才方便寻找过程。
环境模拟
固件提取
先提取一下固件,直接可以再这里下载,
GL
-
AX1800 Flint 4.5.16
,这个
下载之后解压,有3个文件

很明显具体的内容再root里面,解压一下,用binwalk(这里我已经解压过了所以报错)

看一看里面
bin/busybox
文件,可以知道是32位的arm架构
我们上一个漏洞的固件用到的是
32位mips
,于是我们使用的了FirmAE
模拟固件,而现在我们用到是32位arm
,很明显这个架构是不适合用FirmAE
模拟,于是我们就要用到qemu
对这种架构进行模拟。qemu模拟
这里我用的虚拟机是ubuntu22.04,并且这个过程没发生什么报错就比较滋润。
这里直接使用大佬的镜像,下载armhf-virt的链接下载

这里直接下载之后解压,里面包含这些文件,

这里可以看一看readme.txt里面有具体的启动命令
这里我直接写成了个脚本,直接执行这个脚本就行
启动之后可以输入root/root登录。
网卡搭建
这里为了让我们能在Ubuntu里直接访问qemu虚拟机,所以我要搭建一个网卡用于这两个的通讯
宿主机:
这里直接写一个net.sh,方便用
这里不用改,直接用就行
然后配置
qemu
虚拟系统的路由,在qemu
虚拟系统中运行net.sh
并运行。这里有可能再
qemu
虚拟系统没有这两个命令,这里可以去网上搜一下qemu
虚拟系统拓荒,简答下载一下,方便后面使用,这里具体的顺序就是,先再宿主机里

然后

启动
qemu
虚拟系统,输入root/root
登录。在
qemu
虚拟系统中
做完这些之后可以试着宿主机和qemu机之间互ping

如果能ping通就没问题了,就可以开始将固件上传qemu机
使用scp将squashfs-root文件夹上传到qemu系统中的/root路径下
挂载文件并启动环境
在使用 QEMU 进行跨架构模拟后,挂载
/proc
和 /dev
文件系统是必要的,因为它们提供了与内核和硬件设备交互的关键接口。/proc
是一个虚拟文件系统,包含关于系统内核、进程和系统状态的动态信息,许多系统工具和应用程序依赖于它来获取系统信息和配置参数,此外,运行在模拟环境中的进程也需要通过 /proc
进行管理和监控。如果不挂载 /proc
,相关工具可能无法正常工作或获取必要的信息。另一方面,/dev
目录包含设备特殊文件,代表系统中的硬件设备和虚拟设备,用户空间程序通过它们访问和控制硬件设备,如磁盘驱动器、终端和网络接口等。许多应用程序需要与这些设备交互才能正常运行,例如图形应用需要访问图形设备,编译器可能需要访问终端设备。同时,系统服务如 udev
依赖于 /dev
动态管理设备节点。缺少对 /dev
的挂载,模拟环境中的应用程序将无法访问必要的设备,导致功能受限或无法运行。因此,正确挂载 /proc
和 /dev
确保了模拟环境具备必要的系统接口和设备访问能力,使得各种应用程序和系统工具能够正常运行。
这样我们其实就已经启动这个固件了,但我们如果要访问那个网站,会发现并不能成功,这里我们还有启动一些东西。
具体可以执行这些命令,可以实现在宿主机访问
192.168.100.2
进入路由器管理页面,可完成初始密码设置并登录进入管理面板过程,想实现更多功能的启用就要摸索更多配置了,不过我们分析复现授权相关过程已经够用了。具体为什么,这里我也不是很明白,推荐去看看网上的大师傅们的具体分析文章(我真的站在巨人的肩膀上看的高)

就这样,这个固件就算是模拟成功,接下来就可以看看具体的漏洞流程。
漏洞分析
poc
这是官方的poc,这个poc,怎么说呢,有点鸡肋。这里我们要在我们模拟的qemu系统内部执行这个命令,才能达成命令执行的漏洞,简单来说,这是一个内部漏洞。前面的
-H 'glinet: 1'
就是为了表明这是一个内部的传参,这个命令的执行结果就是在root
文件夹里新建一个text
文件,执行一下。
这里我的环境运气比较好,既没有任何的报错直接达到了目的。
因为这就是在固件的内部执行的命令,所以
127.0.0.1
其实就是这个固件的web页面,我们现在开始简单分析一下这个漏洞的调用,最后如何达到命令执行的地址,首先是一开始我们这里就行了一个
/rpc
请求,这里我们在前面提到,这个路由器的web是由OpenResty启动,这个的特点就是将Lua
脚本引擎嵌入到Nginx
中,开发者可以通过Lua
脚本编写高度可定制的Web
服务。所以这里我们的请求可以在 Nginx中找到,在系统中
/etc/nginx/conf.d/
是 Nginx 约定的配置目录。我们可以在其中对应的Lua
脚本中找到/rpc
请求对应得作用。这里说一下,我们从网上下载的是一个压缩后的文件,我们在哪在Ubuntu中就行解压,还原系统,这里还原后的系统,如果我们还想把它导出到windows里面看,这里有个问题,就是像
Lua
脚本这类文件有的权限很高,
就像这里,我们知道它是
Lua
脚本,但权限太高导致如果直接导出来是不能的,所以这里我们可以来一个比较奇妙的办法,先进入root模式,然后把整个系统文件,打包为一个tar包

在给这个包加权限,在拿出来解压,这里注意一定是要在root模式下打包,这样才能把文件全部打包,不减少文件,同时不改变文件性质。
于是更具上面说的我们就可以在系统的如下文件中找到
/rpc
的具体作用。再
/etc/nginx/conf.d/gl.conf

很明显,这里程序在接收到这个请教之后就会到
/usr/share/gl-ngx/oui-rpc.lua
中执行打开
/usr/share/gl-ngx/oui-rpc.lua
这里再看一眼poc的内容
可以看到我们传入
/usr/share/gl-ngx/oui-rpc.lua
的内容为"method":"call", "params":["", "s2s", "enable_echo_server", {"port": "7 $(touch /root/test)"
这里我们看这
/oui-rpc.lua
的内容,其中就有一个
这里用
"method"
定义了4个函数,而我们很明显就是用到了其中的rpc_method_call
函数,在看这个函数的具体内容。call定义
rpc_method_call
函数很明显这个我们用
"params":["", "s2s", "enable_echo_server", {"port": "7 $(touch /root/test)"}]
,传如4个参数,这个函数的最开始就是先验证是否有4个参数。这里就行权限检查分两步:
rpc.is_no_auth(object, method):
检查该 object.method 是否属于**免认证(no auth)**的接口(例如公开的读取信息接口可能不需要登录)。如果返回 true,跳过访问控制。
若不免认证,则调用
rpc.access("rpc", object .. "." .. method)
做实际的访问控制判断(可能检查 ngx.ctx.sid、cookie、ACL、角色等)。如果
rpc.access
返回 false,表示当前请求无权访问该方法,则返回 JSON-RPC 的“访问被拒绝”错误(rpc.ERROR_CODE_ACCESS)并退出。这段代码负责把 API 的权限策略强制执行到每次 call 请求。
这里就是用来判断是否为内部命令的重要函数,
之后就是这个函数最重要的
这里程序会调用
rpc.call
函数,对参数就行处理,这里我们就要再次寻找这个rpc.call
函数在哪,rpc_method_call
进行参数校验、会话检查和Ubus
调用:- 确保
params
中至少三个元素且元素类型正确。
- 检查
sid
是否有效,并通过rpc.access
验证访问权限。
- 如果上述判断均通过,使用
rpc.call
执行指定的Ubus
对象和方法。
这里回到文件头
那么rpc.call其实就是加载oui文件下的rpc.lua里面的call函数。
在 Lua 中:
require "oui.rpc"
会去加载一个叫
oui/rpc.lua
的模块(路径可能是 /usr/lib/lua/oui/rpc.lua
或 /usr/share/lua/...
)。- 返回的内容(通常是一个 table,包含多个函数)会赋值给局部变量
rpc
。
- 所以后续
rpc.call(...)
、rpc.error_response(...)
、rpc.is_no_auth(...)
都是调用这个模块里的函数。
local rpc = require "oui.rpc"
→ 加载 oui/rpc.lua
,把返回的 table 存到 rpc
变量里。rpc.call
→ 就是 oui.rpc
里定义的 M.call
,用来动态加载 /usr/lib/oui-httpd/rpc/<object>
文件并调用对应函数。在
rpc_method_call
里,这个机制相当于通过 object + method 动态调用后端的 Lua 模块函数。通过寻找我们就可以在程序中找到
/usr/lib/lua/oui/rpc.lua
文件,这就是我们要找的下一个目标,这则里面既可以查看rpc.access
和rpc.call
实现。

在这里我们重点关注
这就是上面的
rpc.access
函数的具体内容access
通过is_local
判断是否本地请求。对于本地请求和glinet
标头的请求,总是允许访问(确定了只能本地利用)。程序的重点在于
这个就是我们重点关注的函数,这里调用了我们poc的后面三个参数,
M.call
函数是核心的rpc
调用处理器,执行以下步骤:- 检查请求的对象是否已加载,如果未加载,则尝试从
/usr/lib/oui-httpd/rpc/
目录下加载脚本文件。
- 如果脚本文件存在且加载成功,将对象的方法注册到
objects
表中。
- 如果无法从
/usr/lib/oui-httpd/rpc/
目录下加载脚本文件或者找不到对象或方法,则调用glc_call
执行。
于是这里我们就可以到
/usr/lib/oui-httpd/rpc/
文件夹中寻找,有没有我们传入的s2s
文件,有这个文件,这个文件是一个可执行程序,我们用iad看一下其实到这里,漏洞就已经清晰了,这里程序调用了
/usr/lib/oui-httpd/rpc/s2s.so
文件中的enable_echo_server
函数,传入的"port": "7 $(touch /root/test)"}]
参数,达到了命令执行的效果,但这里我们可以看一看内部的具体流程
这里在
enable_echo_server
函数中,使用了get
传参,先验证,变量名port
,然后把具体的参数传入v5之中之后虽然有对v5的验证
用
atoi(v9)
把字符串转成整数,检查端口范围 >0 && <=65534
。但是这里,重要行为:
atoi
只解析字符串前面的数字,遇到第一个非数字字符就停止并返回已解析的数字。例如:atoi("7;touch") == 7
、atoi("7 $(touch)") == 7
。这就意味着只要字符串前缀是合法数字,检查就会通过——即便后面包含恶意 shell 元字符。于是之后就会来到漏洞点
关键两行(漏洞点):
snprintf(v27,128, "%s -p %s -f", "/usr/bin/echo_server", v9)
; —— 把用户提供的 v9 直接拼进命令字符串,结果像:
/usr/bin/echo_server -p <v9> -f
v18 = system(v27);
—— 把拼好的字符串交给 system()
执行(system()
会调用 shell:/bin/sh -c "<命令字符串>"),因此 shell 会解析命令字符串里的任何元字符(;, &&, |, $(), ...
, > 等),并执行它们。于是就会发生
这里v9的值在poc中被我们改成了
7 $(touch /root/test)
于是就想到与执行
这里我们给
touch /root/test
加上$
,在shell中这个符号代表优先执行,所以这里这个命令,程序就会优先执行我们的输入命令,从达到命令执行的效果。其实这个cve到这里就分析完具体的流程了,这里程序先通过
/rpc
请求\etc\nginx\conf.d\gl.conf
在之中打开\usr\share\gl-ngx\oui-rpc.lua
在通过这里面的rpc_method_call
函数传入4个参数,程序通过rpc.access
验证为本地请求,通过rpc.call
处理后面3个参数,打开\usr\lib\lua\oui\rpc.lua
文件,通过其中的M.call
,寻找/usr/lib/oui-httpd/rpc/
文件夹中是否有我们传入的第二个参数的名字的文件,于是就打开/usr/lib/oui-httpd/rpc/s2s.so
文件,中的enable_echo_server
函数,向其中传入"7 $(touch /root/test)"
命令,程序拼接到达以为
$
,优先执行touch /root/test
命令,达到我们的目的,到这里就是这个poc的具体分析,但我们可以看出来,这里太鸡肋了,只能本地访问,那有没有什么办法绕过
rpc.access
达到远程访问呢,这里我们再来看看。回过头再看一眼
/usr/lib/lua/oui/rpc.lua
的 glc_call
方法

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

这样我们就可以绕过之前的内部检测,再次利用漏洞点,不过这次达到了远程命令执行的目的。
这个漏洞,其实整体看起来还是比较简单的,依然是简单的漏洞命令执行,不过调用的过程多了一点,整体来说还是简单的,不过要注意的一点就是,iot的漏洞无论是成因还是调用过程都与系统平台有关,以后可以多关注看看不同平台的特点于配置文件的位置,或许更好理解调用的过程。
下一个CVE打算看看栈溢出一类,更具之前和学长的的聊天,可以看出后面可以多看看与api和协议有关的cve,多多复现吧。
老规矩,放几篇大佬的文章,感谢分享
- 作者:wgiegie
- 链接:https://tangly1024.com/article/24a3ecc9-5160-80cd-9b05-e2304587e4d5
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。