[0CTF2016]piapiapia"

admin 2026-10-20 20:00:58 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

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'] = '127.0.0.1';$config['username'] = 'root';$config['password'] = '';$config['database'] = '';$flag = '';?>

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

<?phprequire('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字符,域名部分类似        // 格式:[email protected],不严格但有一定限制        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!Your Profile';    }    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!Please Login';        } 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的内容,进行解密即可


[0CTF2016]piapiapia" 网络安全文章

[0CTF2016]piapiapia"

该文档分析了0CTF2016的Web题目piapiapia,通过代码审计发现用户资料更新功能存在反序列化漏洞。关键发现是filter函数对单引号和SQL关键字的过滤存在缺陷,结合序列化操作可构造恶意payload读取数据库中的flag。解题步骤包括下载源码、分析class.php中的过滤逻辑、利用update.php的序列化点进行漏洞利用。
“美军作战评估(OA)研修班”主题一:评估相关术语的翻译与辨析" 网络安全文章

“美军作战评估(OA)研修班”主题一:评估相关术语的翻译与辨析"

本文为知远战略与防务研究所发布的`美军作战评估(OA)研修班`招生通知,核心内容为系统介绍作战评估在现代战争中的关键作用及发展趋势。通知指出,在信息化、智能化战争形态下,传统依赖指挥官经验和直觉的评估方式已难以适应快节奏战场,并提及2026年美以-伊朗战争中AI辅助评估的实战印证。研修班旨在通过为期两天的专题课程(涵盖评估术语辨析、美军实践经验、MOP/MOE指标、AI赋能等议题),深化专业人士对作战评估体系的理解。
CVE-2026-46333漏洞分析报告" 网络安全文章

CVE-2026-46333漏洞分析报告"

该文档详细分析了Linux内核权限提升漏洞CVE-2026-46333(ssh-keysign-pwn),该漏洞属于TOCTOU竞态条件类型,影响Linux内核4.14至6.19.11版本。攻击者可通过pidfd_getfd系统调用在setuid程序退出时窃取文件描述符,从而读取/etc/shadow或SSH主机私钥等敏感文件。文档提供了漏洞技术原理、完整利用链、影响范围评估,并给出了升级内核、设置ptrace_scope=2等修复与缓解措施。
火山codingplan如何查使用量" 网络安全文章

火山codingplan如何查使用量"

文档介绍了火山引擎CodingPlanLite版的使用体验,重点分析了GLM-5.1模型的资源消耗情况(半天消耗5%额度),演示了通过并行agent开发Web扫描器和HTTP负载均衡器的实践,提供了使用量查询入口和模型切换方法,并对比了Lite/Pro版容量差异及免费替代方案。
评论:0   参与:  0