变量覆盖漏洞
来自:
[BJDCTF2020]Mark loves cat
开始打开就是一个js/css页面,源码看不到东西,没有其他入口,像这种题,基本上是源码泄露。
dirsearch一扫发现一堆/.git,应该就是git源码泄露,我们直接githack下载:
(注,此处我开始下不下来,搜了下问题应该在于扫的太快了导致给我拒了,把里面一个self.thread_count的值从10改到1,其实速度没有多慢,就能下载下来了。
打开flag.php,果不其然啥也没有,就告诉你一个flag的路径在根目录:
打开index.php,开始也没找到,翻到最后找到关键源码:
搜了一下,这里应该属于是变量覆盖漏洞,从这$$就可以看出来。
但令我震惊的是,这道题有四种解法,厚礼谢!!!
我们进行一下代码审计,第一个foreach就是POST传参可以给你整成变量,使得$键 = $值的值。 第二个foreach就是GET传参,并且键和值都引用了两次,相当于我们可以设计两层,第一层是内部的一个属性,第二层为flag,这样就把flag的值给赋到$$x上去了。 第三个foreach是新的过滤条件:不能flag的值等于某个键名,并且它还要求那个键值是flag,不然就exit了,乍一看挺矛盾,先搁着。 第四个是if判断条件,如果我们把GET/POST都传了flag的参,就会exit。 第五个也是if判断条件,如果我们GET或者POSY传的flag的参值就是flag字符串的话,就会exit。 看起来是要把这个东西都绕过才会执行到最后一行的echo出flag。
但你别忘了,exit本身就可以作为一种输出,所以可以直接利用这个exit把flag给套出来。
解法一
看到第三个foreach:
foreach($_GET as $x => $y){ if($_GET['flag'] === $x && $x !== 'flag'){ exit($handsome); }
如果我们不去绕过它,而是满足它的条件,然后触发这个exit($handsome),需要怎么做?
我们传的payload需要:
?flag=(变量a)&(变量a)=flag
看起来逻辑有点矛盾,但是传参的时候它不会考虑这些。
我们把$_GET['flag']替换一下,就发现逻辑通了:
a===a & a!=='flag',这不就是true了吗?!!
如果我们令?flag=a&a=flag,那么函数判定到这里时,第一个条件flag的值是a,但与此同时a的值不是'flag'字符串,有点指针的意思了。
所以就会执行到exit($handsome),我们就能知道最终目的是让$handsome=$flag。
哪里有赋值给$handsome的方法呢?
我们再看到第二个foreach:
foreach($_GET as $x => $y){ $$x = $$y; }
试想,如果我们让$x=handsome,$y=flag,这时就会出现$$x=$handsome,$$y=$flag,就达到了把flag爆出来的效果。
payload1:
<url>?handsome=flag&flag=a&a=flag
payload2(来自其他师傅,太高了):
<url>?handsome=flag&flag=handsome
可以看作等效精简了a变量。
输入payload后查看源码得到flag。
解法二
我们看到这个条件:
if(!isset($_GET['flag']) && !isset($_POST['flag'])){ exit($yds); }
意思就是只要我们GET/POST都不设置flag了,就直接进exit($yds),而属性里有个$handsome='yds'。
想通了就会发现,太简单了,不让我们设置那就不设置呗,目的是$yds=$flag,那就是yds=flag。
那不就结束了吗,直接传:
<url>?yds=flag
成功。(不一定只有GET/POST这种才能传参哦,只要有这个属性都可以传这个属性的值)
解法三
看到第二个判断条件:
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){ exit($is); }
嗯?GET/POST只要传了flag的参=flag就直接exit($is)???
那不就直接出来了?
<url>?flag=flag&is=flag
简简单单~~
解法四
最难做的一个方法,就是真的把所有的条件都绕过,使用最后的echo给套出flag。
这里本人能力不足,原博客:https://blog.csdn.net/weixin_44895005/article/details/123721901
强类型比较绕过
我在尝试的时候发现有个问题:
正常情况
http://127.0.0.1/CTF/?a=flag&flag=a
这里触发了 if($_GET['flag'] === $x && $x !== 'flag'){ //不能同时 $_GET['flag'] 的值等于某个键名,那个键名又不是flag
这个条件,进入了 exit($handsome);
里面了,这很好理解的。函数遍历 a=flag
的时候, $_GET['flag'] === $x && $x !== 'flag'
--> a === a && a !== 'flag'
这就进来了 true && true
就进来了, 然后 exit($handsome);
不正常的情况
http://127.0.0.1/CTF/?1=flag&flag=1
这是啥情况呢,按道理来说跟上面一样啊,这里触发了 if($_GET['flag'] === $x && $x !== 'flag'){ //不能同时 $_GET['flag'] 的值等于某个键名,那个键名又不是flag
这个条件,进入了 exit($handsome);
里面了,这很好理解的。函数遍历 1=flag
的时候, $_GET['flag'] === $x && $x !== 'flag'
--> 1 === 1 && 1 !== 'flag'
这就进来了 true && true
就进来了, 然后 exit($handsome);
但是事实不是这样的,我估计是涉及到 字符串和数字的问题,都打印出来看看。
打印代码:
foreach($_GET as $x => $y){ echo "</br>foreach :".'</br>'; echo '$_GET[\'flag\'] :'; var_dump($_GET['flag']).'</br>'; echo '$x :'; var_dump($x).'</br>'; echo '$_GET[\'flag\'] === $x :'; var_dump($_GET['flag'] === $x).'</br>'; echo '$x !== \'flag\' :'; var_dump($x !== 'flag').'</br>'; if($_GET['flag'] === $x && $x !== 'flag'){ //不能同时 1 === 1 && 1 !== 'flag' flag的值等于某个键名,键名又不是flag exit($handsome); } }
果然啊,get
传参数的时候,如果传入1
,默认:做为键:类型是 int
; 做为值,类型是 string
问题就这这里了:$_GET['flag'] === $x
: 如果传入 a=flag&flag=a
那么 判断 a=flag
的时候 $_GET['flag']
是 a
, &x
是 a
,完全相等 进入if条件, 而如果传入 1=flag&flag=1
那么判断 1=flag
的时候 $_GET['flag']
是 (string)1
,&x
是 (int)1
不完全相等,无法进入if条件,进而绕过
这样就绕过好了,满足了大佬说的几句话,不会改变 flag 而且不会提前结束,具体其他几个循环自己打印看看就行。
这个题我是没找到 绕过判断不提前结束 ,而又不改变flag值的情况下到达最后的echo。
还有一个可能的解法(没试,不知道),传入 $$$xx=$$xx
,或者 更多的 $$$$$$$x=$xx
, 来绕过 变量覆盖,这里记一下,变量覆盖可以多次递归(没实验过)