[BJDCTF2020]Mark loves cat (WriteUp)

php 复制代码
<?php
include 'flag.php';
###初始化
$yds = "dog";
$is = "cat";
$handsome = 'yds';

###遍历所有POST/GET提交的数据
#把每个字段名变成变量名,字段值变成变量值
foreach($_POST as $x => $y){
    $$x = $y;
}
foreach($_GET as $x => $y){
    $$x = $$y;
}

foreach($_GET as $x => $y){
    if($_GET['flag'] === $x && $x !== 'flag'){
        exit($handsome);
    }
}

if(!isset($_GET['flag']) && !isset($_POST['flag'])){
    exit($yds);
}

if($_POST['flag'] === 'flag'  || $_GET['flag'] === 'flag'){
    exit($is);
}

echo "the flag is: ".$flag;

这是一道非常经典的 PHP变量覆盖 漏洞题目。解开这道题的核心思路是:我们无法让程序正常执行到最后一行 echo $flag,因为前面的所有 if 条件最终都会触发 exit() 提前结束程序。所以,我们的目标是利用变量覆盖漏洞,把真正的 $flag 的值,注入到 exit() 要输出的变量中。


第一步:理解代码的核心机制

代码中有三个最关键的变量初始化:

php 复制代码
$yds = "dog";
$is = "cat";
$handsome = 'yds';

接着是两个遍历数组的代码,这是漏洞的根源:

  1. POST请求处理$$x = $y;
    • 意思是:如果POST传入 a=b,那么就会变成 $a = "b"。这是字符串赋值
  2. GET请求处理$$x = $$y;
    • 意思是:如果GET传入 a=b,那么就会变成 $a = $b。这是变量引用赋值(把变量b的值赋给变量a)。这是我们解题的关键!

第二步:分析 Exit(退出)条件

程序有三个退出点,我们必须从这三个退出点中选一个来输出 flag:

退出点 1:

php 复制代码
foreach($_GET as $x => $y){
    if($_GET['flag'] === $x && $x !== 'flag'){
        exit($handsome);
    }
}
  • 条件 :GET参数中必须包含 flag 这个键,且 flag 的值必须等于另一个GET参数的键名 ,同时那个键名不能是 flag 本身。
  • 输出$handsome

退出点 2:

php 复制代码
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
    exit($yds);
}
  • 条件 :GET 和 POST 都没有 传入 flag 参数。
  • 输出$yds

退出点 3:

php 复制代码
if($_POST['flag'] === 'flag'  || $_GET['flag'] === 'flag'){
    exit($is);
}
  • 条件 :POST传入的 flag 值为字符串 "flag",或者 GET传入的 flag 值为字符串 "flag"
  • 输出$is

第三步:构造 Payload 绕过

我们的终极目标是:利用 GET 请求的 $$x = $$y 特性,把 $flag 的值赋给 $yds$is$handsome,然后触发对应的 exit()

💡 解法一:最简单的解法(利用退出点2,输出 $yds)

退出点2的条件是:GET和POST都没有传入 flag

如果我们只传入 GET 参数:?yds=flag

  1. 执行 GET 遍历 $$x = $$y:此时 $x = "yds", $y = "flag"。代码执行变成 $yds = $flag此时 $yds 的值变成了真正的 flag!
  2. 退出点1检查:因为没有传入 flag 参数,$_GET['flag'] 为空,条件不满足,跳过。
  3. 退出点2检查:!isset($_GET['flag']) 成立(没传flag),!isset($_POST['flag']) 成立(没传flag),触发 exit($yds)
  4. 此时 $yds 已经是真正的 flag 了,程序输出 flag 并结束。

Payload: ?yds=flag


💡 解法二:利用退出点3(输出 $is)

退出点3的条件是:GET传入 flag=flag

如果我们构造 GET 参数:?is=flag&flag=flag

  1. 执行 GET 遍历 $$x = $$y
    • 当遍历到 is=flag 时:$x="is", $y="flag",执行 $is = $flag此时 $is 变成了真正的 flag!
    • 当遍历到 flag=flag 时:$x="flag", $y="flag",执行 $flag = $flag。相当于自己赋值给自己,保护了 $flag 没有被覆盖掉
  2. 退出点1检查:$_GET['flag']"flag"。遍历到 is 时,$x="is""flag" === "is" 不成立;遍历到 flag 时,虽然 "flag" === "flag" 成立,但是 $x !== 'flag' 不成立(因为此时 $x 就是 'flag'),所以跳过。
  3. 退出点2检查:传入了 $_GET['flag'],条件不满足,跳过。
  4. 退出点3检查:$_GET['flag'] === 'flag' 成立!触发 exit($is)
  5. 此时 $is 已经是真正的 flag,程序输出 flag 并结束。

Payload: ?is=flag&flag=flag


💡 解法三:利用退出点1(输出 $handsome,稍微进阶)

退出点1的条件是:GET传入 flag=x,且还要有一个参数名叫 x(x不能是flag)。

比如我们传入:?handsome=flag&flag=handsome

  1. 执行 GET 遍历 $$x = $$y
    • 如果先遍历到 handsome=flag$handsome = $flag成功覆盖!
    • 然后遍历到 flag=handsome$flag = $handsome。此时 $flag 的值变成了刚才的 $handsome(也就是真正的flag),$flag 没丢。
      (注意:PHP中数组遍历顺序通常和参数书写顺序一致,所以这样写是安全的)
  2. 退出点1检查:$_GET['flag'] 的值是 "handsome"。当遍历到 handsome=flag 时,$x="handsome"。条件 $_GET['flag'] === $x"handsome" === "handsome" 成立!且 $x !== 'flag' 也成立!触发 exit($handsome)
  3. 此时 $handsome 已经是真正的 flag,程序输出 flag。

Payload: ?handsome=flag&flag=handsome


📚 总结与学习要点

  1. 变量覆盖漏洞 :当遇到 $$var 这种可变变量时,一定要警惕。如果允许用户控制键名和键值,就可以覆盖程序原有的变量。
  2. 逆向思维 :当正常的执行逻辑(echo $flag)被重重 exit() 阻断时,不要死磕如何走到最后,而是思考如何利用 exit() 当作我们的"输出面板"。
  3. $$x = $y$$x = $$y 的区别 :前者只能赋值字符串,后者可以赋值变量的引用。本题中 $flag 的值我们不知道,所以不能用 POST 的 $$x=$y 来让某个变量等于 flag,只能用 GET 的 $$x=$$y 来进行变量间的值传递。
相关推荐
Fnetlink11 小时前
Fnet 云网安 260810
网络·安全·web安全
笨#小孩15 小时前
文件上传漏洞:原理与20种实战绕过方法(上)
安全·web安全
AI创界者1 天前
【网络安全运维】Kali Linux 下 Medusa(美杜莎)工具的高效部署、故障排查与安全测试实战
linux·运维·web安全
云水一下2 天前
零基础玩转bWAPP靶场(四十五):XSS - Reflected (JSON)
web安全·json·xss·bwapp·reflected
祁白_2 天前
WebShell工具流量特征分析
笔记·web安全·流量特征·webshell工具
砚凝霜3 天前
软考网络工程师|第 6 章 网络安全基础、攻击、等保完整备考笔记
网络·笔记·web安全
数据知道4 天前
XSS 攻防全解:反射型、存储型、DOM 型实战演示
前端·安全·web安全·网络安全·xss
空杆推不起4 天前
企业数据加密实战:传输链路与存储层全场景落地指南
网络·安全·web安全
Atomic121384 天前
登录界面渗透测试全攻略
web安全·网络安全
Yan_chen6665 天前
SQL-LABS_Less18-20实战攻略
数据库·sql·web安全·网络安全