CTFshow系列——PHP特性Web105-108

发布于:2025-09-14 ⋅ 阅读:(16) ⋅ 点赞:(0)

今天讲解的是PHP的Web105-108题目解析讲解


Web105(新题型)

好样子,先看代码:

<?php

highlight_file(__FILE__);
include('flag.php');
error_reporting(0);
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
foreach($_GET as $key => $value){
    if($key==='error'){
        die("what are you doing?!");
    }
    $$key=$$value;
}foreach($_POST as $key => $value){
    if($value==='flag'){
        die("what are you doing?!");
    }
    $$key=$$value;
}
if(!($_POST['flag']==$flag)){
    die($error);
}
echo "your are good".$flag."\n";
die($suces);

?>
你还想要flag嘛?

从代码我们可以看到,与上一关的题型又不一样了:

分析代码

  1. 初始化:定义了两个字符串变量 $error$suces
  2. $_GET 循环:
    • 首先,它检查 GET 参数的键($key)是否等于字符串 ‘error’。如果是,程序会立即终止。
    • 然后,它使用了一个非常关键的技巧:可变变量 ( $$key = $$value)。这意味着,以 $key 的值为名字的那个变量,它的值将被设置为以 $value 的值为名字的那个变量的值。例如,如果你传入 GET 参数 a=b,代码就会执行 $$a = $$b,等同于 $a = $b。
  3. $_POST 循环:
    • 它检查 POST 参数的值($value)是否等于字符串 ‘flag’。如果是,程序会立即终止。
    • GET 循环一样,它也使用了可变变量:$$key = $$value
  4. 最终检查:
    • 代码执行 if (!($_POST['flag'] == $flag)):
    • 这个条件是核心:只有当 POST 参数中名为 flag 的值 等于 $flag 变量的值时,条件才为假,程序才能继续执行,最终打印出 flag。

构造Payload思路:

  1. 假设 flag.php 内有:
$flag = "FLAG{123}";
$error = "你还想要flag嘛?"; // 初始值

我们发送:

  • GET: ?a=flag
  • POST body: error=a

脚本按顺序执行:
2. 处理 $_GET循环:GET 有 a => 'flag',所以执行 $$key = $$value 就是:

$a = $flag;   // 把真实 flag 的值复制到 $a

现在:

$a = "FLAG{123}"
$flag = "FLAG{123}"
$error = "你还想要flag嘛?"
$_POST = array('error' => 'a') // 由 PHP 填充的 POST 初始数组
  1. 处理 $_POST循环:POST 有一项 key='error', value='a'。先检查 if($value===‘flag’) —— 值是 a,不是 flag,不会 die。然后 $$key = $$value 即:
$error = $a;

也就是把 $a 的值(flag)赋给 $error

$error = "FLAG{123}"   // 注意:$error 已经被覆盖成 flag
  1. 到比较 if(!($_POST[‘flag’] == f l a g ) ) d i e ( flag)) die( flag))die(error);
  • $_POST['flag']—— 在我们的 POST 中并不存在 flag 这个字段,所以它是 NULL / 未定义(与 $flag 字符串不同)。
  • 因此条件为真(比较不等),脚本执行 die($error)
  • 但是 $error 已在上一步被赋值成了 flag,因此 die($error) 会把 真实 flag 输出到响应,并退出。

最终payload:

在这里插入图片描述


Web106

<?php

highlight_file(__FILE__);
include("flag.php");

if(isset($_POST['v1']) && isset($_GET['v2'])){
    $v1 = $_POST['v1'];
    $v2 = $_GET['v2'];
    if(sha1($v1)==sha1($v2) && $v1!=$v2){
        echo $flag;
    }
}

?>

嗯?看了一下代码,怎么有种似曾相识的感觉?
这不是Web104的题型吗,不过对比了一下,还是有点小区别的:

在这里插入图片描述

多了对$v1!=$v2的比较;

老样子,直接用数组绕过,payload甚至都是一样的:

在这里插入图片描述

# GET参数
?v2[]=1

# POST参数
v1[]=2

没什么好说的,直接下一关;

Web107

代码还是有点变化的:

<?php

highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

if(isset($_POST['v1'])){
    $v1 = $_POST['v1'];
    $v3 = $_GET['v3'];
       parse_str($v1,$v2);
       if($v2['flag']==md5($v3)){
           echo $flag;
       }
}

?>

代码分析

这段 PHP 代码的目的是让用户通过一个特定的条件来获取 flag。它的逻辑步骤如下:

  1. 错误报告与文件引入: 脚本首先禁用所有错误报告 (error_reporting(0)),然后通过 include("flag.php") 引入包含 $flag 变量的文件。

  2. 变量赋值:

    • $v1 被赋值为 POST 变量 v1 的值。
    • $v3 被赋值为 GET 变量 v3 的值。
  3. parse_str() 函数:

    • parse_str() 函数会将 $v1 (一个字符串) 解析成 URL 查询字符串的格式,并将解析出的变量存储到一个名为 $v2 的新数组中。
    • 例如,如果 $v1 的值是 “name=John&age=30”,那么 parse_str($v1, $v2) 会创建一个 $v2数组,其内容为 [‘name’ => ‘John’, ‘age’ => ‘30’]
  4. 脚本执行 if($v2['flag']==md5($v3))
    * $v2['flag'] 是从 POST变量 v1 提供的字符串中解析出来的 flag 键对应的值。
    * 脚本将我们提供的 flag 值与 GET参数$v3 字符串的 MD5 哈希值进行比较。


方法一:PHP弱类型比较

  • 要获取 flag,只需要需要找到一个 $v3 的值,使得 md5($v3) 的结果与 v2['flag'] 相等。

    • 直接让 md5($v3) 等于字符串 'flag' 是不可能的。
  • 这里的漏洞在于 PHP 的 松散比较 (==) 和 类型转换。当 PHP 使用 == 运算符比较一个 MD5 哈希(一个十六进制字符串)和一个普通字符串时,它会尝试将它们转换为同一类型。

思路:如果一个 MD5 哈希字符串以 '0e' 开头,后面跟着纯数字,PHP 在进行比较时会将其视为科学计数法表示的数字,其值为 0

利用这个漏洞,我们可以构造出 payload:

# GET参数
?v3=s878926199a

# POST参数
v1=flag=0

目的:为了通过PHP弱类型比较,使v3解析为0,从而与v1相等;

方法二:直接使v3的md5值等于v1

大家看到标题应该就知道我们应该怎么做了:

具体操作:

  1. 先选 v3 = abc,md5(“abc”) = 900150983cd24fb0d6963f7d28e17f72。
  2. POST 的 v1 写成 flag=900150983cd24fb0d6963f7d28e17f72

在这里插入图片描述

嗯,对的就是这么朴实无华;

Web108

<?php


highlight_file(__FILE__);
error_reporting(0);
include("flag.php");

if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE)  {
    die('error');

}
//只有36d的人才能看到flag
if(intval(strrev($_GET['c']))==0x36d){
    echo $flag;
}

?>
error

话不多说,直接分析代码:
好 — 我把这段 PHP 代码逐行分析并给出可直接尝试的 payload(含 curl 示例)和备选绕过方法。

代码要点(精简)

if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE) { die('error'); }
if (intval(strrev($_GET['c']))==0x36d) {
    echo $flag;
}
  1. ereg("^[a-zA-Z]+$", $_GET['c']):字符串必须从头到尾 (^ 和 $),由一个或多个 (+) 大写或小写的英文字母 ([a-zA-Z]) 组成。
  2. strrev($_GET['c']):把 c 字符串反转。
  3. intval(...)==0x36d:把反转后的字符串转换为整数,并与十六进制 0x36d(十进制是 877)比较。
  4. 如果相等就输出 flag。

利用思路

0x36d 的字符串表示是 "0x36d" —— 如果 strrev(c) === "0x36d",那么 intval("0x36d") 在 PHP 中会把它当作 16 进制数解析(常见 PHP 行为),结果就是 0x36d(等于 877),比较成立,输出 flag。

因此需要:

  • strrev(c) == "0x36d",即 c == strrev("0x36d") == "d63x0"

于是最直接的 payload 是把 c 设为 d63x0,请求如下:

c=d63x0

解释:d63x0 反转后是 0x36dintval("0x36d") → 0x36d (877),等于 0x36d,通过判断,输出 flag。


最终payload

尝试了一下?c=d63x0,失败了。。。
在这里插入图片描述

之后我又去网上查询资料:

老版本的 ereg() 函数存在 空字节注入 漏洞。

  • 如果你在输入字符串中包含一个空字节(\0),ereg() 会在遇到空字节时停止处理,导致它无法匹配到字符串的结尾,因此返回 FALSE,条件成立。
  • 也就是说:ereg()在处理字符串时,如果遇到 %00会提前终止匹配,导致验证绕过

那么我们是不是可以利用%00截断来绕过?

?c=778%00

没成功。。看了一下网上WP

# 最终payload
?c=a%00778

在这里插入图片描述

解释

  • 开头写个a是为了匹配正则表达式
  • 然后%00截断后面的匹配。然后经过strrev以后变成了877%00a,
  • 再经过intval就变成了877。
  • 而877就是0x36d的十进制,所以相等。

总结

无总结。