关于php弱类型的小总结

文章最后更新时间为:2019年07月08日 22:19:46

之前的一篇笔记,发到blog上。

PHP,俗称世界上最好的语言,也是CTF中WEB方向最常见的语言,其中利用PHP弱类型特性来绕过限制的情况也比较多,这里结合实例总结一下常见的考察点。

0x01 强制类型转换

先来看题目

<?php
show_source(__FILE__);
$flag = "xxxx";
if(isset($_GET['time'])){ 
        if(!is_numeric($_GET['time'])){ 
                echo 'The time must be number.'; 
        }else if($_GET['time'] < 60 * 60 * 24 * 30 * 2){ 
                        echo 'This time is too short.'; 
        }else if($_GET['time'] > 60 * 60 * 24 * 30 * 3){ 
                        echo 'This time is too long.'; 
        }else{ 
                sleep((int)$_GET['time']); 
                echo $flag; 
        } 
                echo '<hr>'; 
}
?>

乍一看,传入time参数在(5184000,7776000)范围内不就好了。但是要sleep这么长时间才给flag,那不就真到天荒地老了。

我们输入的GET参数肯定是作为字符串保存了,is_numeric()支持普通数字型字符串、科学记数法型字符串、部分支持十六进制0x型字符串,但强制类型转换int,却不能正确的转换十六进制型字符串、科学计数法型字符串(部分)。

比如:

<?php
echo (int)"0x8888"; //输出为0
echo (int)"8e22"; //输出为8

所以上面我们只要输入科学计数法形式或者16进制形式的字符串就可以绕过漫长的sleep了。0x5b8d80/6e6

这题涉及到了int强制类型转换。正在进行的ISCC中web题有个小点大概如下:

if (intval($password) < 2333 && intval($password + 1) > 2333){
    echo $flag;
}

通过以上分析,很容易可知,可以用科学计数法表示password,绕过限制。

0x02 弱类型比较

弱类型比较最常见了,其实上面的题目有弱类型比较的步骤,$_GET['time'] < 60 * 60 * 24 * 30 * 2就是字符串和数字对比,当字符串和数字进行对比时,通常将字符串转化为数字,再进行数字之间的对比。

字符串转化为数字时,因为会去掉后面的字母,比如12fda==12,我们可以通过这种方式绕过某些限制,比如下面这题:

<?php
error_reporting(0);
include 'flag.php';
highlight_file(__FILE__);


if (!$_COOKIE['admin']) {
    exit("\nNot authenticated.\n");
}

if (!preg_match('/^{"hash": [\w\"]+}$/', $_COOKIE['admin'])){
    exit("还看不懂正则表达式? 还不快去百度! \n");
}
$session_data = json_decode($_COOKIE['admin'], true);

if ($session_data['hash'] != strtoupper(MD5($flag))) {
    echo("给你个提示吧 \n");
    for ($i = 0; $i < 32; $i++) {
        echo(ord(MD5($flag)[$i]) >> 6);
    }
    exit("\n");
}

class WHUCTF {
    protected $stu;

    function __construct() {
        $this->stu = new Study();
    }
 
    function __destruct() {
        $this->stu->action();
    }
}
 
class Study {
    function action() {
        echo "CTF 真好玩~";
    }
}
 
class Evil {
    function action() {
        system('cat ./flag.php');
    }
}

echo "这么明显了,你懂我意思吧";
unserialize($_GET['whuctf']);

在绕过$session_data['hash'] != strtoupper(MD5($flag)的时候,我们就可以利用弱类型比较来爆破。

关于字符串还有个Tips,"0e123456"这类的字符串会被识别为科学技术法的数字,而0的无论多少次方都是零。常见的根据这个点来出的题目:

<?php
highlight_file(__FILE__);
include('flag.php');
$str1 = @$_GET['str1'];
$str2 = @$_GET['str2'];
if( $str1 == $str2 ){
    die('str1 OR Sstr2 no no no');
}
if( md5($str1) != md5($str2) ){
    die('step 1 fail');
}
//这里删去一部分考察其他知识点的内容
echo $flag;

两个字符串不相等,但是md5却相等,我们怎么找这样的字符串呢,但这题只需要使两个字符串的md5都以0e开头即可。在php中如果是0e开头的字符串进行==比较,会认为是科学记数法0e,0的几次方,所以结果自然是0,这样就达到了字符串比较不相等,但是md5值相等的绕过方法。

关于md5绕过的题目基本就三种:

  • ==比较用0e绕过
  • !==利用数组绕过(和下面的例子类似)
  • 找出md5值真正相等的三个不同字符串,可以用文件生成,github已有工具

具体可以学习https://xz.aliyun.com/t/4741#toc-7

0x03 利用不合法(数组)格式绕过限制

<?php
if (isset($_GET['name']) and isset($_GET['password'])) {
    if ($_GET['name'] == $_GET['password'])
        echo '<p>Your password can not be your name!</p>';
    else if (sha1($_GET['name']) === sha1($_GET['password']))
      die('Flag: '.$flag);
    else
        echo '<p>Invalid password.</p>';
}
else{
    echo '<p>Login first!</p>';
?>

输入的name和password不能一样,之后的sha1比较用了===,不存在弱类型问题。但sha1不能处理数组,当我们传入name[]=1&password[]=2时,会造成sha1(Array) === sha1(Array),即NULL===NULL,从而get flag。

其实利用数组来绕过限制的时候有很多,不仅sha1函数不能处理数组,md5函数,token_get_all函数等都不能,程序基本都没有检查数据类型,比如下面这题,前几天校赛中的一题:

<?php
$file = @$_GET["f"];
A:
try{
$tokens = token_get_all($file,TOKEN_PARSE);

foreach ($tokens as $tt) {

    if ($tt[0] == 379) 
    {
        $file = str_replace($tt[1],"",$file);
        goto A;
    }
    if($tt[0] == 380)
    {
        $file = str_replace($tt[1],"",$file);
        goto A;
    }
    if($tt[0] == 381)
    {
        $file =str_replace($tt[1],"",$file);
        goto A;
    }
}
}
catch(\Throwable $e){
   pass;
}
$filename = md5((string)time().$_SERVER['REMOTE_ADDR'].(string)rand(999,2000));
file_put_contents("/tmp/".$filename,$file);
include '/tmp/'.$filename;
unlink('/tmp/'.$filename);
highlight_file(__FILE__);
?>

总之是要include我们传入的php文件,但是这个A函数很讨厌,利用token_get_all递归过滤了<?php<?=等关键字,没有这些关键字,include进来也没用啊,最终注意到这题用了try catch结构,那么能不能让上面的A函数直接奔溃,于是传入f参数为数组,token_get_all接收到数组直接报错,绕过了限制。

总之多尝试传入数组,总是没错的。。

1 + 6 =
快来做第一个评论的人吧~