本帖最后由 ala 于 2026-3-23 20:07 编辑
通过网站备份得到网站源码,这里提供一个和站长不一样的思路(站长的那个思路是简单的,我这个稍显复杂)
<div>include "flag.php";
$_403 = "Access Denied";
$_200 = "Welcome Admin";
if ($_SERVER["REQUEST_METHOD"] != "POST")
<span style="white-space:pre"> </span>die("BugsBunnyCTF is here :p...");
if ( !isset($_POST["flag"]) )
<span style="white-space:pre"> </span>die($_403);
foreach ($_GET as $key => $value)
<span style="white-space:pre"> </span>$$key = $$value;
foreach ($_POST as $key => $value)
<span style="white-space:pre"> </span>$$key = $value;
if ( $_POST["flag"] !== $flag )
<span style="white-space:pre"> </span>die($_403);
echo "This is your flag : ". $flag . "\n";
die($_200);</div>
分析代码:
1. 代码首先会检查我们的访问方式,如果不是POST方式,在第一层检测后就会被拒绝;
2. 然后检查我们有没有以POST方式提交flag字段,如果没有,同样拒绝访问;
3. 接下来会将我们以GET方式传入的参数键值对进行遍历赋值,这里注意,它是将键值对作为变量名来令前者等于后者的;
4. 接着将我们以POST方式传入的参数flag进行赋值,即flag=值;
5. 下一步判断我们POST提交的flag字段与$flag变量的值是否一致,若不一致则进入失败分支,若一致,则打印flag。
方法1:
代码分析完毕我们发现一个问题,即第4第5步,我们发现,不管我们POST发送的数据是什么,都会进入成功分支的,因为$flag会首先变为我们POST传过去的值,然后再和我们传过去的值进行比较,这不就多此一举了么?
但是我们注意到,POST提交的数据会将$flag覆盖,也就是说最后成功以后,打印的flag实际上是我们以POST方式传入的值。
还好他不只打印$flag,他还打印了$_200,因此我们可以在第3步GET遍历的时候将$_200的值修改为flag的值,这样在最后就可以将flag的值打印出来了。
构造payload:
curl -X POST "【靶机地址】?_200=flag" -d "flag=anything"
使用cmd访问即可得到flag
方法2:
我们前面提到,第5步的判断一定会跳过分支,也就是条件始终为假也就是比较的两者始终相等,真的是100%吗?并不是,实际上,不仅以上文件里面的变量可以被覆盖,一些超全局变量同样可以被覆盖。
如果我们要进入那条分支,那么我们只能使用$_403打印相关信息了。也就是说,我们要令$_403的值等于$flag,那么我们就要在GET请求里面构造:
然后我们要想办法使得第5步的判断失效,那么我们直接修改$_POST变量的值(使用GET请求方法)
当然不要忘了POST的flag字段,不然第二步检测是过不去的:
GET: _403=flag&_POST=anything POST: flag=anything
总体payload:
curl -X POST "【靶机地址】?_403=flag&_POST=anything" -d "flag=anything"
梳理一下流程:
1. 首先是POST请求(-X POST),第一步检测通过;
2. POST的flag字段有效(-d "flag=anything"),第二步通过;
3. GET遍历将$_403覆盖为$flag(_403=flag),存储真正的flag值,将$_POST覆盖为"anything"字符串;
4. POST遍历失效,因为$_POST已经不是“键值对”了,已经只是一个字符串了;
5. 比较,$_POST['flag']为null,而$flag依旧存储着真正的flag值,两者不相等,进入失败分支,打印变量$_403,而此时$_403恰好存储着flag的值(见第3步前半部分)。
|