01、题目简介
02、解题步骤
启动并访问靶机

使用dirseach扫描,发现存在压缩包www.zip

访问下载进行解压操作

接下来就是对代码进行解读了,config.php内容如下,发现全局变量flag
<?php
$config['hostname'] = '127.0.0.1';
$config['username'] = 'root';
$config['password'] = '';
$config['database'] = '';
$flag = '';
?>接下来看一下class.php,这里我认为最主要的就是filter方法,过滤了单引号、反斜线及SQL关键字。
<?php
require('config.php'); // 引入配置文件,通常包含数据库连接信息 $config
// user 类继承 mysql 类,实现用户管理功能
class user extends mysql {
private $table = 'users'; // 指定数据库中的用户表名
// 检查用户名是否已存在
public function is_exists($username) {
$username = parent::filter($username); // 调用父类的过滤函数(存在安全缺陷)
$where = "username = '$username'"; // 拼接 SQL WHERE 子句,易导致 SQL 注入(虽然 filter 试图过滤)
return parent::select($this->table, $where); // 执行查询并返回结果对象
}
// 用户注册
public function register($username, $password) {
$username = parent::filter($username); // 过滤用户名
$password = parent::filter($password); // 过滤密码
$key_list = Array('username', 'password'); // 插入的字段名
$value_list = Array($username, md5($password)); // 字段值(密码使用 MD5 加密,不安全)
return parent::insert($this->table, $key_list, $value_list); // 执行插入
}
// 用户登录验证
public function login($username, $password) {
$username = parent::filter($username); // 过滤用户名
$password = parent::filter($password); // 过滤密码
$where = "username = '$username'"; // 拼接 WHERE 条件
$object = parent::select($this->table, $where); // 查询用户记录
if ($object && $object->password === md5($password)) { // 比对密码 MD5 值
return true;
} else {
return false;
}
}
// 获取用户资料(序列化字符串)
public function show_profile($username) {
$username = parent::filter($username); // 过滤用户名
$where = "username = '$username'"; // WHERE 条件
$object = parent::select($this->table, $where); // 查询用户
return $object->profile; // 返回 profile 字段(序列化存储)
}
// 更新用户资料
public function update_profile($username, $new_profile) {
$username = parent::filter($username); // 过滤用户名
$new_profile = parent::filter($new_profile); // 过滤新资料内容
$where = "username = '$username'"; // WHERE 条件
return parent::update($this->table, 'profile', $new_profile, $where); // 执行更新
}
// 魔术方法,将对象转为字符串时返回类名
public function __tostring() {
return __class__;
}
}
// mysql 类,封装数据库操作(使用已废弃的 mysql_* 函数)
class mysql {
private $link = null; // 数据库连接资源
// 连接数据库
public function connect($config) {
$this->link = mysql_connect(
$config['hostname'],
$config['username'],
$config['password']
); // 建立 MySQL 连接(mysql_connect 已过时)
mysql_select_db($config['database']); // 选择数据库
mysql_query("SET sql_mode='strict_all_tables'"); // 设置严格 SQL 模式
return $this->link;
}
// 查询单条记录(仅返回第一条)
public function select($table, $where, $ret = '*') {
$sql = "SELECT $ret FROM $table WHERE $where"; // 拼接 SQL 查询语句(严重 SQL 注入风险)
$result = mysql_query($sql, $this->link); // 执行查询
return mysql_fetch_object($result); // 取回结果对象
}
// 插入记录
public function insert($table, $key_list, $value_list) {
$key = implode(',', $key_list); // 拼接字段名
$value = '\'' . implode('\',\'', $value_list) . '\''; // 拼接值,加单引号
$sql = "INSERT INTO $table ($key) VALUES ($value)"; // 拼接 INSERT 语句
return mysql_query($sql); // 执行插入
}
// 更新记录
public function update($table, $key, $value, $where) {
$sql = "UPDATE $table SET $key = '$value' WHERE $where"; // 拼接 UPDATE 语句
return mysql_query($sql); // 执行更新
}
// 输入过滤函数(存在绕过风险)
public function filter($string) {
// 将单引号 ' 和反斜线 \ 替换为下划线 _
$escape = array('\'', '\\\\');
$escape = '/' . implode('|', $escape) . '/';
$string = preg_replace($escape, '_', $string);
// 将危险 SQL 关键字替换为 'hacker'(不区分大小写)
$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
}
// 魔术方法,对象转字符串返回类名
public function __tostring() {
return __class__;
}
}
// 启动会话
session_start();
// 实例化 user 对象并连接数据库
$user = new user();
$user->connect($config); // $config 来自 config.php,包含数据库连接参数
?>接下来看一下update.php,我认为这里最主要的就是serialize($profile)序列化操作。
<?php
// 引入 class.php 文件,该文件包含 user 类和 mysql 类的定义,以及创建 $user 对象和启动 session
require_once('class.php');
// 检查当前会话中是否有 username,即用户是否已登录
if($_SESSION['username'] == null) {
die('Login First'); // 未登录则终止并输出提示
}
// 检查是否通过 POST 提交了 phone、email、nickname 以及上传了 photo 文件
if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {
// 从会话中获取已登录的用户名
$username = $_SESSION['username'];
// 验证手机号:必须是11位数字(正则:^\d{11}$)
if(!preg_match('/^\d{11}$/', $_POST['phone']))
die('Invalid phone');
// 验证邮箱格式:允许下划线、字母数字,每部分1-10字符,域名部分类似
// 格式:xxx@xxx.xxx,不严格但有一定限制
if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
die('Invalid email');
// 验证昵称:只允许字母、数字、下划线,且长度不超过10
if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');
// 获取上传的文件信息
$file = $_FILES['photo'];
// 检查文件大小:小于5字节或大于1MB则报错(注意:<5 可能允许0或1字节,但move_uploaded_file可能失败)
if($file['size'] < 5 or $file['size'] > 1000000)
die('Photo size error');
// 将上传的临时文件移动到 upload/ 目录下,文件名使用原始文件名的 MD5 值(无扩展名)
move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
// 构建要保存的用户资料数组
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']); // 存储图片的访问路径
// 序列化资料数组,并调用 user 类的 update_profile 方法更新数据库中当前用户的 profile 字段
$user->update_profile($username, serialize($profile));
// 输出成功信息,并提供一个链接到 profile.php 查看资料
echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
}
else {
// 如果未完整提交 POST 数据,则不执行更新,可能输出表单页面(原代码未闭合 else 的大括号?此处省略了表单 HTML)
// 实际代码中这里可能包含一个 HTML 表单,让用户填写资料
?> 既然看到profile就看一下profile.php。我认为profile主要是将字符串还原为数组直接输出。
<?php
// 引入 class.php,其中包含 user 类、mysql 类的定义,以及创建 $user 对象并启动 session
require_once('class.php');
// 检查用户是否已登录(会话中是否存在 username)
if($_SESSION['username'] == null) {
die('Login First'); // 未登录则终止并提示
}
// 从会话中获取当前登录的用户名
$username = $_SESSION['username'];
// 调用 user 对象的 show_profile 方法,从数据库获取该用户序列化后的个人资料
$profile = $user->show_profile($username);
// 如果资料为空(用户尚未填写过资料),则重定向到 update.php 进行填写
if($profile == null) {
header('Location: update.php');
}
else {
// 反序列化从数据库取出的资料字符串,将其还原为数组
$profile = unserialize($profile);
// 从数组中提取各个字段
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
// 读取照片文件内容并转换为 base64 编码(通常用于嵌入 HTML 中的 data:image 显示)
$photo = base64_encode(file_get_contents($profile['photo']));接下来再看一下index.php,一个简单的登录
<?php
// 引入 class.php,包含用户类、数据库操作类以及 $user 对象和 session 启动
require_once('class.php');
// 检查当前是否已登录(会话中存在 username)
if($_SESSION['username']) {
header('Location: profile.php'); // 已登录则直接跳转到个人资料页
exit; // 终止脚本执行
}
// 检查是否通过 POST 提交了用户名和密码
if($_POST['username'] && $_POST['password']) {
$username = $_POST['username']; // 获取提交的用户名
$password = $_POST['password']; // 获取提交的密码
// 验证用户名长度:必须在 3 到 16 个字符之间
if(strlen($username) < 3 or strlen($username) > 16)
die('Invalid user name');
// 验证密码长度:必须在 3 到 16 个字符之间
if(strlen($password) < 3 or strlen($password) > 16)
die('Invalid password');
// 调用 user 类的 login 方法进行身份验证
if($user->login($username, $password)) {
$_SESSION['username'] = $username; // 登录成功,将用户名存入会话
header('Location: profile.php'); // 跳转到个人资料页
exit;
} else {
die('Invalid user name or password'); // 登录失败,输出错误信息
}
}
else {
// 如果既没有登录也没有提交 POST 数据,则执行此处看一下register.php
<?php
// 引入 class.php,其中包含 user 类、mysql 类以及 $user 对象的创建和 session 启动
require_once('class.php');
// 检查是否通过 POST 提交了用户名和密码
if($_POST['username'] && $_POST['password']) {
$username = $_POST['username']; // 获取提交的用户名
$password = $_POST['password']; // 获取提交的密码
// 验证用户名长度:必须在 3 到 16 个字符之间
if(strlen($username) < 3 or strlen($username) > 16)
die('Invalid user name');
// 验证密码长度:必须在 3 到 16 个字符之间
if(strlen($password) < 3 or strlen($password) > 16)
die('Invalid password');
// 调用 user 类的 is_exists 方法检查用户名是否已存在
if(!$user->is_exists($username)) {
// 用户名不存在,调用 register 方法注册新用户(密码会被 MD5 处理)
$user->register($username, $password);
// 注册成功后输出成功信息,并提供链接让用户跳转到登录页
echo 'Register OK!<a href="index.php">Please Login</a>';
} else {
// 用户名已存在,输出错误信息
die('User name Already Exists');
}
}
else {接下来就可以进行具体操作了,首先我们可以利用update.php序列化,也将s:10:"config.php";}添加到序列化结果后面,反序列化的时候就能通过profile.php中file_get_content()方法来读取config.php的文件内容。
update.php中$nickname可以成为利用点,$nickname后面需要序列化的内容为
;}s:5:"photo";s:10:"config.php";}长度为34,所以挤出34位长度,这时候利用class.php中的filter代码,每次过滤where关键字就会将字符串的长度添加1位,所以我们需要在$nickname中输入34个where,然后将;}s:5:"photo";s:10:"config.php";}添加到关键字后面
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}然后接下来注册一个账户,注册完成后登录,会让你更新信息

然后修改下图标红的两个参数,修改$nickename为nickename[]进行数组绕过和$nickename的值

然后访问/profile.php

然后右键查看源代码

这里就是base64加密的config.php的内容,进行解密即可
