漏洞编号: CVE-2023-20073
CVSS3 评分: 9.8
标签: #漏洞复现
关键字: 未授权文件上传
引用链接:
AVD
armel和armhf区别
一步步教你:如何用Qemu来模拟ARM系统
为什么ext4 rootfs会被挂载成只读模式
linux: running self compiled kernel in qemu: VFS: Unable to mount root fs on unknown wn-block(0,0)
Linux下关闭和开启IPv6的方法
Vexpress device tree blob
从零开始复现 CVE-2023-20073
⚠️ 影响设备:
Cisco rv340w_firmware
Cisco rv340_firmware
Cisco rv345p_firmware
补充:笔者的操作系统环境
Windows11+WSL2
如果您的apt工具可以下载到低版本的gcc↓
本篇我们需要用到的架构是armhf
,所以需要安装gcc-arm-linux-gnueabihf
由于笔者的内核版本较高,最低只能下载到gcc-11
,所以需要手动下载编译工具交叉编译工具
具体的GCC
版本请根据您要编译的Linux内核进行区分(检查文件linux-x.x.x/include/linux/compiler-gccX.h
)
本篇使用的Linux内核版本是3.2.1
,如有需要请自行下载其它版本
内核源码中针对不同架构和开发板型号已经提供了不同的模板配置文件(arch/${arch}/configs
),我们可选择一项先生成一份.config
,这里我们选择的处理器型号是vexpress
针对本篇的固件模拟,编译内核前我们需要对配置文件进行一些修改
由于v3.2.1
的内核不支持通过启用配置项CONFIG_ATAGS
来从命令行中获取内核启动参数,所以我们需要在配置文件中通过字段CONFIG_CMDLINE
来提前设置(使用qemu -append参数无效)
这是笔者的参数设置
如果不指定此项在启动服务时系统会因为内存溢出而崩溃
提前声明,下述解决办法并不一定能100%让您的内核被成功编译并且启动,如遇报错,请第一时间检查您的GCC版本与内核版本是否匹配
编译完成后我们得到了内核文件(arch/${arch}/boot/zImage
),另外我们还需要设备树文件,Linux源码中提供了部分这样的文件(arch/${arch}/boot/dts
),若未找到可自行下载
这里我们使用从固件中解包得到的文件系统,首先创建一个磁盘镜像
然后将其格式化,这里注意您的内核版本支持的文件系统格式,笔者使用的是3.2.1
版本内核且编译时没有开启ext4
文件系统支持,所以使用ext3
创建一个临时文件夹作为挂载点,并将解包得到的文件系统复制进其中
本篇的固件文件系统默认启动了telnet以供外部进行访问,若需要从内向外的ssh连接操作则可能遇见以下问题
解决(主机):
重启ssh服务
解决(主机):
重启ssh服务
注意看到最后一行PnP Agent is starting!
,它启动了另一个服务,前提是您的操作系统支持IPv6
,否则最后会启动失败,这就是为什么在内核编译的时候我强调了要开启IPv6
的配置选项
nginx最后会根据几个配置文件来启动uwsgi
来负责监听几个端口以处理数据
访问成功
已知是未授权的文件上传漏洞
这里最后启动了uwsgi-launcher
请求路径/upload
会查找token文件进行授权验证,失败返回403,成功则保存临时文件并转发给form-file-upload
处理
请求路径/api/operations/ciscosb-file:form-file-upload
仅通过请求头字段Authorization
存在与否判断是否转发给form-file-upload
处理,此处nginx的授权验证可轻松绕过
这里直接贴我在IDA里分析过的伪代码
通过上述伪代码可发现,授权验证部分在移动上传文件的后面,意味着不论验证通过与否,文件都将从临时文件中被转移并保存
本函数负责了处理移动文件的操作,部分伪代码如下所示
通过此处可知若能控制pathParam=Portal
则能将文件保存目录控制在/tmp/www/
下,而此处正是访问时路由页面的存放位置
这个函数初始化了一个结构体,该结构体结构示意图如下
最后该结构体被传递到multipart_parser_execute
进行进一步处理
笔者在最初调试cgi的时候尝试通过qemu-user+IDA的来进行调试,所以遇到了一些问题,便分析了这个函数,众嗦粥知 :),cgi读取请求数据是通过stdin
进行读取的,所以我尝试通过这种形式来直接模拟请求输入数据
也既直接在终端中输入,但不论如何都没有数据被成功解析,最后发现问题出在这个函数中,该函数通过结构体的boundary
作为一个判断,index
作为数据光标位置和operateCode
记录当前所处位置数据所属(既数据还是头)
而其中判断operateCode
的重要决定条件就是换行符\r\n
,其它的头部判断则通过:
,;
等符号进行
这就是为什么上述偷懒方法不生效的原因
上传文件处理程序upload.cgi
中将文件保存操作提前至授权检查前,使其只需被调用且参数正确即可将文件保存至不同目录下。其中保存目录部分可控,保存文件名可控,保存目录可控中可利用项有/var/www/
目录,可通过覆盖其主路由页面实施存储型XSS攻击
需要控制的字段(参数):
由于上述的偷懒调试法因为解析函数的原因无效(或许有其它的数据输入法,但是我没有探究了)。于是需要对upload.cgi
进行patch后调试,因为cgi程序的特性,一次请求一次执行瞬间完成,既无法可控的通过调试器启动也无法在正常情况下当其执行时附加它,所以需要进行patch
通过上文的upload.cgi
main
函数伪代码可知其通过调用fread
对stdin
进行读取,所以可以patch它的读取长度参数使其等待
将原长度R6
修改为R6+1
即可,现在我们发送测试报文如下
此时BurpSuite进入了等待状态,我们查看虚拟机的后台进程
从主机传入 gdb-server 对其进行附加调试,gdb-multiarch连接后可见其停在了SYS_read
调用处等待,我们直接修改PC
寄存器让其跳过并恢复正常流程
将断点设置在参数获取的位置并放行
可见参数pathparam
成功的控制了,而filename是生成的,这个是临时文件的保存目录,不影响所以无需控制。
已知需要控制内容如下
eh。花了一周多的时间,终于把本篇内容完成并把笔记和文章写完了,最后文章总结下来其实内容不多(当然我也不擅长表达和总结),多少有点删减,只挑了我觉得重要的地方指出来和关键的部分记录下来,这是我做的第四个,hum算第三个吧,因为的第二个DIR823G其实和第一个DIR815本质上没区别,第二个做的是TOTOLINK的命令注入漏洞,那个也没什么很复杂的逻辑,
hum因为我比较倔,最开始其实4.12.12
的内核成功编译了并且跑起来了环境(没有IPV6和启动参数)的问题,不过我就是想用3.2.1
的:)。但是同样的编译通过,使用3.2.1
内核的虚拟机却是各种起不来,我各种参数都调了一边,比如文件系统格式支持等。最开始的报错信息如下
我在配置文件中启用了ext4
的支持但依旧如此,最后发现是由于内核启动参数没有生效导致,必须在编译的时候指定CONFIG_CMDLINE
上文说过在启动confd
服务的时候要看见最后的PnP Agent is starting!
才算成功,但是我最开始一直失败,报错如下
最后在配置文件里找到了IPV6
的选项并启用了
只要涉及写操作都会报错
然后发现系统根目录挂载的是ro
的,但是我也没法通过指令重新挂载,这个问题不论在4.12.12
版本的内核还是3.2.1
版本的内核都出现了,最后在一篇文章中找到答案,在配置文件中设置CONFIG_LBDAF=y
这个问题在上文中我也简述过了,在3.2.1
版本的内核中出现,解决方法是在启动参数中添加mem=128M
有读者可能会好奇我上文怎么不用hackbar去发请求而是选择直接在bp中发,当我在 hackbar 中写入如下数据
乍一看好像其实没什么问题,但是注意在数据段头和数据之间的换行,那有一个空格,这导致最后发出去的包是这个样子的
这个问题我还没有找到原因,推测是数据段长度不够(但这只是现象),因为我各种测试之后只得出了这个结论,结合上一个问题错上加错之下让我碰壁相当之久
例如当我发送数据
nginx将这个临时文件保存了,但是没有成功转发给uwsgi
(我附加调试了它),但是nginx本身似乎也在等待回复,所以临时文件既没有被清除,nginx也没有回复我的请求。加上上一个错误之后,我发送了错误的(我自己不知道)报文如下
它被Nginx转发了,但是自然而然的upload.cgi
无法解析了
在准备环境,既内核那一部分,加上踩坑我花了两天半来解决,在hackbar的那个部分遇见问题到弄清楚原因,我花了两天半的时间来解决,其它大大小小的问题也是一天多,差不多一周,感觉还是挺,容易进坑的我 :)
到最后还有一个遗留问题就是nginx为什么没有转发请求到uwsgi
,蹲一个路过大神回复解答一手
┌──(root㉿W1sh)
-
[~]
└─
No LSB modules are available.
Distributor
ID
: Kali
Description: Kali GNU
/
Linux Rolling
Release:
2024.1
Codename: kali
-
rolling
┌──(root㉿W1sh)
-
[~]
└─
Linux W1sh
5.15
.
146.1
-
microsoft
-
standard
-
WSL2
┌──(root㉿W1sh)
-
[~]
└─
No LSB modules are available.
Distributor
ID
: Kali
Description: Kali GNU
/
Linux Rolling
Release:
2024.1
Codename: kali
-
rolling
┌──(root㉿W1sh)
-
[~]
└─
Linux W1sh
5.15
.
146.1
-
microsoft
-
standard
-
WSL2
apt install gcc
-
arm
-
linux
-
gnueabi
apt install gcc
-
arm
-
linux
-
gnueabihf
apt install gcc
-
arm
-
linux
-
gnueabi
apt install gcc
-
arm
-
linux
-
gnueabihf
make CROSS_COMPILE
=
arm
-
linux
-
gnueabihf
-
ARCH
=
arm vexpress_defconfig
make CROSS_COMPILE
=
arm
-
linux
-
gnueabihf
-
ARCH
=
arm vexpress_defconfig
CONFIG_CMDLINE
=
"root=/dev/mmcblk0p2 rw console=ttyAMA0 mem=128M"
CONFIG_IPV6
=
y
CONFIG_LBDAF
=
y
CONFIG_CMDLINE
=
"root=/dev/mmcblk0p2 rw console=ttyAMA0 mem=128M"
CONFIG_IPV6
=
y
CONFIG_LBDAF
=
y
make CROSS_COMPILE
=
arm
-
linux
-
gnueabi
-
ARCH
=
arm
make CROSS_COMPILE
=
arm
-
linux
-
gnueabi
-
ARCH
=
arm
apt
-
get install zlib1g
apt
-
get install zlib1g:i386
apt
-
get install libc6
-
i386 lib32stdc
+
+
6
lib32gcc1 lib32ncurses5
apt
-
get install zlib1g
apt
-
get install zlib1g:i386
apt
-
get install libc6
-
i386 lib32stdc
+
+
6
lib32gcc1 lib32ncurses5
@arch
/
arm
/
include
/
asm
/
ftrace.
-
extern inline void
*
return_address(unsigned
int
level)
+
static inline void
*
return_address(unsigned
int
level)
@arch
/
arm
/
include
/
asm
/
ftrace.
-
extern inline void
*
return_address(unsigned
int
level)
+
static inline void
*
return_address(unsigned
int
level)
@arch
/
arm
/
kernel
/
return_address.c
-
void
*
return_address(unsigned
int
level)
-
{
-
return
NULL;
-
}
@arch
/
arm
/
kernel
/
return_address.c
-
void
*
return_address(unsigned
int
level)
-
{
-
return
NULL;
-
}
@kernel
/
timeconst.pl
-
if
(!defined(@val))
+
if
(!@val)
@kernel
/
timeconst.pl
-
if
(!defined(@val))
+
if
(!@val)
qemu
-
img create
-
f raw disk.img
512M
qemu
-
img create
-
f raw disk.img
512M
mkfs
-
t ext3 .
/
disk.img
mkdir tmpfs
sudo mount
-
o loop .
/
disk.img tmpfs
/
sudo cp
-
r rootfs
/
*
tmpfs
/
sudo umount tmpfs
mkdir tmpfs
sudo mount
-
o loop .
/
disk.img tmpfs
/
sudo cp
-
r rootfs
/
*
tmpfs
/
sudo umount tmpfs
sudo qemu
-
system
-
arm \
-
M
"vexpress-a9"
\
-
kernel
"/kernel/linux/3.1.2/arch/armhf/zImage"
\
-
dtb
"/kernel/dtb/vexpress-v2p-ca9.dtb"
\
-
sd
"./disk.img"
\
-
net nic \
-
net tap,ifname
=
tap0,script
=
no,downscript
=
no \
-
nographic \
-
smp
4
sudo qemu
-
system
-
arm \
-
M
"vexpress-a9"
\
-
kernel
"/kernel/linux/3.1.2/arch/armhf/zImage"
\
-
dtb
"/kernel/dtb/vexpress-v2p-ca9.dtb"
\
-
sd
"./disk.img"
\
-
net nic \
-
net tap,ifname
=
tap0,script
=
no,downscript
=
no \
-
nographic \
-
smp
4
ifconfig tap0
192.168
.
11.1
/
24
up
ifconfig tap0
192.168
.
11.1
/
24
up
ifconfig lo
127.0
.
0.1
up
ifconfig eth0
192.168
.
11.2
up
ifconfig lo
127.0
.
0.1
up
ifconfig eth0
192.168
.
11.2
up
echo
"KexAlgorithms diffie-hellman-group1-sha1"
>>
/
etc
/
ssh
/
sshd_config
echo
"KexAlgorithms diffie-hellman-group1-sha1"
>>
/
etc
/
ssh
/
sshd_config
echo
"HostKeyAlgorithms +ssh-rsa"
>>
/
etc
/
ssh
/
sshd_config
echo
"HostKeyAlgorithms +ssh-rsa"
>>
/
etc
/
ssh
/
sshd_config
NGINX_BIN
=
/
usr
/
sbin
/
nginx
UPLOAD
=
/
var
/
upload
NOTIFYD
=
/
usr
/
bin
/
notifyd
UWSGI
=
/
usr
/
bin
/
uwsgi
-
launcher
start(){
$NGINX_BIN
$NOTIFYD
-
i
127.0
.
0.1
&
$UWSGI start
}
NGINX_BIN
=
/
usr
/
sbin
/
nginx
UPLOAD
=
/
var
/
upload
NOTIFYD
=
/
usr
/
bin
/
notifyd
UWSGI
=
/
usr
/
bin
/
uwsgi
-
launcher
start(){
$NGINX_BIN
$NOTIFYD
-
i
127.0
.
0.1
&
$UWSGI start
}
start() {
uwsgi
-
m
-
-
ini
/
etc
/
uwsgi
/
jsonrpc.ini &
uwsgi
-
m
-
-
ini
/
etc
/
uwsgi
/
blockpage.ini &
uwsgi
-
m
-
-
ini
/
etc
/
uwsgi
/
upload.ini &
}
start() {
uwsgi
-
m
-
-
ini
/
etc
/
uwsgi
/
jsonrpc.ini &
uwsgi
-
m
-
-
ini
/
etc
/
uwsgi
/
blockpage.ini &
uwsgi
-
m
-
-
ini
/
etc
/
uwsgi
/
upload.ini &
}
[uwsgi]
plugins
=
cgi
workers
=
1
master
=
1
uid
=
www
-
data
gid
=
www
-
data
socket
=
127.0
.
0.1
:
9003
buffer
-
size
=
4096
cgi
=
/
www
/
cgi
-
bin
/
upload.cgi
cgi
-
allowed
-
ext
=
.cgi
cgi
-
allowed
-
ext
=
.pl
cgi
-
timeout
=
300
ignore
-
sigpipe
=
true
[uwsgi]
plugins
=
cgi
workers
=
1
master
=
1
uid
=
www
-
data
gid
=
www
-
data
socket
=
127.0
.
0.1
:
9003
buffer
-
size
=
4096
cgi
=
/
www
/
cgi
-
bin
/
upload.cgi
cgi
-
allowed
-
ext
=
.cgi
cgi
-
allowed
-
ext
=
.pl
cgi
-
timeout
=
300
ignore
-
sigpipe
=
true
location
/
form
-
file
-
upload {
include uwsgi_params;
proxy_buffering off;
uwsgi_modifier1
9
;
uwsgi_pass
127.0
.
0.1
:
9003
;
uwsgi_read_timeout
3600
;
uwsgi_send_timeout
3600
;
}
location
/
upload {
set
$deny
0
;
if
(
-
f
/
tmp
/
websession
/
token
/
$cookie_sessionid) {
set
$deny
"${deny}1"
;
}
if
($cookie_sessionid ~
*
"^[a-f0-9]{64}"
) {
set
$deny
"${deny}2"
;
}
if
($deny !
=
"012"
) {
return
403
;
}
upload_pass
/
form
-
file
-
upload;
upload_store
/
tmp
/
upload;
upload_store_access user:rw group:rw
all
:rw;
upload_set_form_field $upload_field_name.name
"$upload_file_name"
;
upload_set_form_field $upload_field_name.content_type
"$upload_content_type"
;
upload_set_form_field $upload_field_name.path
"$upload_tmp_path"
;
upload_aggregate_form_field
"$upload_field_name.md5"
"$upload_file_md5"
;
upload_aggregate_form_field
"$upload_field_name.size"
"$upload_file_size"
;
upload_pass_form_field
"^.*$"
;
upload_cleanup
400
404
499
500
-
505
;
upload_resumable on;
}
location
/
form
-
file
-
upload {
include uwsgi_params;
proxy_buffering off;
uwsgi_modifier1
9
;
uwsgi_pass
127.0
.
0.1
:
9003
;
uwsgi_read_timeout
3600
;
uwsgi_send_timeout
3600
;
}
location
/
upload {
set
$deny
0
;
if
(
-
f
/
tmp
/
websession
/
token
/
$cookie_sessionid) {
set
$deny
"${deny}1"
;
}
if
($cookie_sessionid ~
*
"^[a-f0-9]{64}"
) {
set
$deny
"${deny}2"
;
}
if
($deny !
=
"012"
) {
return
403
;
}
upload_pass
/
form
-
file
-
upload;
upload_store
/
tmp
/
upload;
upload_store_access user:rw group:rw
all
:rw;
upload_set_form_field $upload_field_name.name
"$upload_file_name"
;
upload_set_form_field $upload_field_name.content_type
"$upload_content_type"
;
upload_set_form_field $upload_field_name.path
"$upload_tmp_path"
;
upload_aggregate_form_field
"$upload_field_name.md5"
"$upload_file_md5"
;
upload_aggregate_form_field
"$upload_field_name.size"
"$upload_file_size"
;
upload_pass_form_field
"^.*$"
;
upload_cleanup
400
404
499
500
-
505
;
upload_resumable on;
}
location
/
api
/
operations
/
ciscosb
-
file
:form
-
file
-
upload {
set
$deny
1
;
if
($http_authorization !
=
"") {
set
$deny
"0"
;
}
if
($deny
=
"1"
) {
return
403
;
}
upload_pass
/
form
-
file
-
upload;
upload_store
/
tmp
/
upload;
upload_store_access user:rw group:rw
all
:rw;
upload_set_form_field $upload_field_name.name
"$upload_file_name"
;
upload_set_form_field $upload_field_name.content_type
"$upload_content_type"
;
upload_set_form_field $upload_field_name.path
"$upload_tmp_path"
;
upload_aggregate_form_field
"$upload_field_name.md5"
"$upload_file_md5"
;
upload_aggregate_form_field
"$upload_field_name.size"
"$upload_file_size"
;
upload_pass_form_field
"^.*$"
;
upload_cleanup
400
404
499
500
-
505
;
upload_resumable on;
}
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2024-4-1 16:58
被LeaMov编辑
,原因: 标题