在Web应用安全中,反序列化漏洞是一种非常常见的漏洞形式,这篇文章主要参考了PortSwigger,介绍了不安全的反序列化的概念,并通过一个简单易懂的实验说明了不安全的反序列化是如何危害到Web安全的。
概念与背景
序列化和反序列化
序列化是将复杂的数据结构(例如对象其字段)转换为“更扁平”的格式的过程,这种格式可以用于字节流发送和接收,使用序列化技术,我们可以将复杂的数据写入内存、文件或数据库,并且通过网络在应用程序的不同组件之间或在API调用中发送复杂的数据。在序列化对象时,其状态也将保留下来。换句话说,序列化将保留对象的属性及其分配的值。
很多编程语言都支持序列化操作,具体的序列化方式取决于不同的语言,某些语言将对象序列化为二进制格式,而其他语言则使用不同的字符串格式,并具有不同程度的可读性。需要注意的是,所有原始对象的属性都存储在序列化的数据流中,包括任何私有字段。在实际应用中,如果需要防止某些字段被序列化,必须在类声明中将其显式标记为“ transient”。在使用不同的编程语言时,序列化可能被称为marshalling(Ruby)或pickling(Python)。在本文中,这些术语与“序列化”同义。
不安全的反序列化
不安全的反序列化是指攻击者操纵用户可控制的数据,在其中插入有害数据,这部分数据被序列化后发送给网站,网站接着对其进行反序列化,有害数据就被传递到了应用程序代码中。
攻击者甚至有可能用不同类型的对象替换序列化的对象。如果网站不对序列化数据进行检查,那么任何类型的对象都会被反序列化和实例化。因此,不安全的反序列化也被称为“对象注入”漏洞。
许多基于反序列化的攻击在反序列化之前就已经完成,这意味着即使网站本身并未与恶意对象进行直接交互,反序列化过程本身也可以发起攻击,因此,逻辑上依赖于编程语言的网站也容易受到这种技术的攻击。
不安全的反序列化漏洞产生的原因
不安全的反序列化漏洞的产生通常源于人们普遍缺乏对反序列化的危险程度的认识。人们通常认为反序列化对象是可信任的,尤其是当使用具有二进制序列化格式的语言时,开发人员可能会认为用户无法有效读取或操纵数据。但是,尽管可能需要付出更多的努力,但攻击者有可能利用二进制序列化的对象,就像利用基于字符串格式的对象进行攻击一样。
有时网站所有者会对反序列化的数据实施某些形式的附加检查来保证安全性。然而,这种方法通常是无效的,因为验证不能覆盖到所有可能的情况。这些检查从根本上来说也是有缺陷的,因为它们依赖于对数据进行反序列化后对其进行检查,在许多情况下,这时反序列化的攻击已经完成。
现代网站中存在的大量依赖关系使得基于反序列化的攻击成为可能。一个典型的站点可能会实现许多不同的库,每个库也都有自己的依赖性,这会创建大量难以保证安全性的类和方法,由于攻击者可以创建任何这些类的实例,因此很难预测恶意数据调用了哪些方法。因此,几乎不可能预料到恶意数据的流动并堵塞每个潜在的漏洞。
常见的序列化格式
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()
JAVA格式序列化
Java的序列化使用二进制格式。二进制虽然可读性差,但是序列化数据中还是存在一些明显的迹象,可以用于识别序列化的数据。例如,序列化的Java对象始终以相同的字节开头,这些字节的编码方式为ac ed 十六进制和rO0Base64。
Java中任何实现java.io.Serializable的类都可以序列化和反序列化。readObject()方法用于从输入流中读取和反序列化数据。
不安全的反序列化漏洞利用
平台
操作机:Kali Linux
工具:Burpsuite
测试对象:PortSwigger Web Security Academy提供的Lab
修改序列化对象
利用一些反序列化漏洞可以更改序列化对象的属性。随着对象状态的持久化,可以研究序列化的数据以识别和编辑有趣的属性值。然后,可以通过反序列化过程将恶意对象传递到网站中,这是基本反序列化利用的第一步。
广义上讲,处理序列化对象时可以采用两种方法。一种方法是直接以其字节流形式编辑对象,另一种方法是使用相应的语言编写简短的脚本来自己创建和序列化新对象。当使用序列化采用二进制格式时,通常采用后一种方法进行操作更容易。
在本次测试中,我们的测试对象是一个使用序列化User对象的网站,该网站将有关用户会话的数据存储在cookie中,我们需要通过修改序列化对象属性来提升我们的权限。
假设网站使用此cookie来检查当前用户是否有权访问某些管理功能:
1 | $user = unserialize($_COOKIE); |
我们首先使用已有的账号密码登录该网站,借助burpsuite对HTTP请求进行抓取我们可以得到如下信息:
其中cookie包含了用户会话有关的信息,可以看到这部分数据已经被序列化过了,下一步我们将cookie部分“Send to Decoder”,并使用Base64编码进行反序列化操作得到:
其中admin属性是判断用户是否为admin的依据,我们可以简单地将该属性的布尔值更改为1(true),重新编码对象,然后使用此修改后的值覆盖其当前cookie,发送修改后的request。
假设网站不会检查序列化对象的真实性,我们的request将这些数据传递到条件语句中,我们的权限将会得到提升。在response中,我们可以发现和admin有关的文件位置。
接下来,修改request的发送地址为/admin,再次发送同样的请求,阅读返回的response,我们可以发现admin可以进行的账户管理操作
再次向/admin/delete?username=carlos发送相同的请求即可达到删除用户carlos的目的。
这种简单的情况在实际应用并不常见。但是,以这种方式编辑属性值说明了不安全反序列化的危害。
参考
https://portswigger.net/web-security/deserialization/exploiting