当前位置: 首页 > 代码审计, 网络安全 > 正文

【三个白帽之三】PHP类型与逻辑+fuzz与源代码审计

本题考察了PHP类型与变量的特点,与参赛选手对于一个『不明白』的问题的解决方案(fuzz或阅读源码)。
源码如下

<?php
if(isset($_GET['source'])){
    highlight_file(__FILE__);
    exit;
}
include_once("flag.php");
 /*
    shougong check if the $number is a palindrome number(hui wen shu)
 */
functionis_palindrome_number($number) {
    $number= strval($number);
    $i= 0;
    $j= strlen($number) - 1;
    while($i< $j) {
        if($number[$i] !== $number[$j]) {
            returnfalse;
        }
        $i++;
        $j--;
    }
    returntrue;
}
ini_set("display_error", false);
error_reporting(0);
$info= "";
$req= [];
foreach([$_GET, $_POST] as$global_var) {
    foreach($global_varas$key=> $value) {
        $value= trim($value);
        is_string($value) && is_numeric($value) && $req[$key] = addslashes($value);
    }
}   
$n1= intval($req["number"]);
$n2= intval(strrev($req["number"]));
if($n1&& $n2) {
    if($req["number"] != intval($req["number"])) {
        $info= "number must be integer!";
    } elseif($req["number"][0] == "+"|| $req["number"][0] == "-") {
        $info= "no symbol";
    } elseif($n1!= $n2) { //first check
        $info= "no, this is not a palindrome number!";
    } else{ //second check
        if(is_palindrome_number($req["number"])) {
            $info= "nice! {$n1} is a palindrome number!";
        } else{
            if(strpos($req["number"], ".") === false && $n1< 2147483646) {
                $info= "find another strange dongxi: ". FLAG2;
            } else{
                $info= "find a strange dongxi: ". FLAG;
            }
        }
    }
} else{
    $info= "no number input~";
}
?>

在题目上线前,我已经让部分人测试过,当时大家找到了一些解决方法。
之前没有这句话$req["number"] != intval($req["number"]),所以大家有很多方法可以解决这个问题,比如1x10、01.1
于是我加了上面这句判断,这样就可以限制这些解法。现在说一下最终得到的三种解决方案。

0x01 利用整数溢出绕过

这是最简单的方法,用的是php的整数上限。借用下 @蓝加白 写的writeup(条理清晰,思路很好)。
首先,看一下源代码。发现要找到FLAG,必须要满足以下三个条件:

  1. number = intval(number)
  2. intval(number) = intval(strrev(number))
  3. not a palindorme number

貌似第二个条件和第三个条件冲突了,但是我们可以利用intval函数的限制:
http://php.net/manual/zh/function.intval.php
看一下解释:最大的值取决于操作系统。 32 位系统最大带符号的 integer 范围是 -2147483648 到 2147483647。举例,在这样的系统上, intval('1000000000000') 会返回 2147483647。64 位系统上,最大带符号的 integer 值是 9223372036854775807。
从上面我们可以知道,intval函数还依赖操作系统,很明显测试的环境系统是64位,所以应该选:9223372036854775807。
但有个问题,它的回文数明显小于64位系统的限制,所以我们想到前面加个0;
最终payload: http://f2ed13418097d206c.jie.sangebaimao.com/?number=09223372036854775807

0x02 利用浮点数精度绕过

这是 @玉林嘎 提出来的解决方案。
我来说一下原理。首先在电脑上测试下面的php代码:

【三个白帽之三】PHP类型与逻辑+fuzz与源代码审计|PHP类型 fuzz|熊猫博客

可见,在小数小于某个值10^-16以后,再比较的时候就分不清大小了,这与php内部储存浮点数的机制有关。
在计算机里,是不能精确表示某个浮点数的。比如1.0,通常情况下储存在计算机里的数值是1.000000000000xxx,是一个十分接近1.0的数。
所以,我们在执行这个if语句的时候if ($req["number"] != intval($req["number"])),会先将右值转换成整数,再与左值比较。而左值是一个浮点数(1.000000000000001),所以右值又会被隐式地强制转换成浮点数1.0
那么1.0和1.000000000000001究竟是否相等呢?
因为我前面说的特性,1.0其实也不是精准的1.0,所以php在比较的时候是不能精准比较浮点数的,所以它会『忽略』比10的-16次方更小的部分,然后就会认为左值和右值相等。

回到CTF中,利用这个特性,我们构造1000000000000000.00000000000000010,即可绕过第一个if语句,并且拿到flag。

0x03 函数特性导致绕过

这个特性涉及到php『数字类』函数的一个特性。什么函数?包括is_numeric和intval等包含数字判断及转换的函数。
is_numeric为例,我们先来看他的源代码:

【三个白帽之三】PHP类型与逻辑+fuzz与源代码审计|PHP类型 fuzz|熊猫博客

可见我画框的部分,is_numeric函数在开始判断前,会先跳过所有空白字符。这是一个特性。
也就是说,is_numeirc(" \r\n \t 1.2")是会返回true的。
同理,intval(" \r\n \t 12"),也会正常返回12。
这就完成了一半。但有的同学又问了,题目获取$req['number']的时候明明使用trim过滤了空白字符的呀?
我们再看到trim的源码:

【三个白帽之三】PHP类型与逻辑+fuzz与源代码审计|PHP类型 fuzz|熊猫博客

掰指头算一下,这里过滤的空白字符和之前跳过的空白字符有什么区别?
少了一个"\f",嘿嘿。
于是我们可以引入\f(也就是%0c)在数字前面,来绕过最后那个is_palindrome_number函数,而对于前面的数字判断,因为intval和is_numeric都会忽略这个字符,所以不会影响。
最后通过payload: http://f2ed13418097d206c.jie.sangebaimao.com/?number=%0c121 拿到第二个flag:

【三个白帽之三】PHP类型与逻辑+fuzz与源代码审计|PHP类型 fuzz|熊猫博客


本文固定链接: http://www.chnpanda.com/961.html | 熊猫博客 | 转载请注明出处,谢谢合作!

本文关键字: ,

【三个白帽之三】PHP类型与逻辑+fuzz与源代码审计:等您坐沙发呢!

发表评论

亲,不支持纯字母、符号评论哦~