freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

反序列化漏洞详解(一)
Luc1fer 2023-03-02 13:36:32 228495
所属地 陕西省

本系列第一篇简单介绍php反序列化和java反序列化知识。

序列化:序列化是将对象的状态信息转换为可以存储或传输的形式或过程。在序列化期间,对象将当前状态写入到临时或者持久化存储区,然后可以从存储区中读取反序列化对象状态,重新创建该对象。

反序列化:将存储的字节序列恢复成对象的过程称为反序列化

一、PHP反序列化漏洞

1. 什么是php反序列化漏洞?

程序未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,通过在参数中注入一些代码,从而达到代码执行、SQL注入、目录遍历等不可控后果,危害较大。

2. php序列化和反序列化怎么实现?

在php中,用serialize函数和unserialize函数实现序列化和反序列化,我们结合例子看:

1668996464_637add709e084b55b4bd3.png!small?1668996465608

这里我们new了一个Person对象进行序列化,Person中包含四个成员,分别是三个分别具有不同修饰符的属性和一个方法,序列化结果如下:

1668996709_637ade655b88f8156289a.png!small?1668996710538

O:6表示Object,对数组进行序列化的话就是a,6表示对象名长度是6,Person是对象名,3表示对象中的成员变量的数量。

{}中s表示string类型,i表示int类型,i类型不包括长度,而且类中的方法并不会被序列化

这里可以发现根据成员变量修饰类型不同,序列化中的表示方法也不一样,name修饰符是public,age修饰符是private,grade修饰符是protected。

public修饰时,就按原本名称和值序列化:s:4:"name";s:4:"lisa";

private修饰时,按类名+属性名序列化:s:11:"Personage";i:18;这里s:11是因为属性实际上是\x00Person\x00age,\x00是空字符但是也占空间

protected修饰时,会加上*:s:8:"*grade";i:97; 这里s:8实际上是\x00*\x00grade

接下来我们反序列化试试看,如下图可以发现反序列化后不包括方法。

1668999048_637ae78818c630ad60cd1.png!small?1668999049020

3. 魔法函数是什么?有什么用?

通过上面的例子我们知道序列化过程并不序列化对象中的方法,仅仅序列化属性。那么如果想要传入恶意参数进行攻击,就需要对对象中的属性值进行修改。但其实这样利用方法较少,因为对象属性值基本上是预先设置好的。

但php中有一种机制让我们可以利用对象中的方法,就是魔法函数。魔法函数是在对应的类序列化或反序列化的同时自动完成的,不用手动调用,所以反序列过程中我们可以利用这些魔法函数实现攻击。

常见魔法函数:

__constructed():构造函数,此函数会在创建一个类的实例时自动调用

__destruct():析构函数,此函数在对象的所有引用都被删除或者类被销毁时自动调用

//简单来说就是上面的函数在类被创建的时候被调用

__sleep():在serialize()函数执行之前自动调用

__wakeup():在unserialize()函数执行之前自动调用,用来预备对象需要的资源

__toString():当对象被当作字符串使用时被调用,比如echo $obj、拼接字符串......

//tostring啥时候被触发?

1.echo $obj /print($obj)

2.反序列化对象与字符串连接时

3.对象参与格式化字符

4.对象与字符串进行比较的时候(php比较的时候会转换参数类型)

5.对象参与格式化SQL语句,进行参数绑定

6.对象经过php字符串函数,如strlen()、addslashes()

__set():给不可访问属性赋值时调用

__get():在读取不可访问的属性值的时候调用。

__isset():当对不可访问属性调用isset()或者empty()

__upset():对不可访问属性调用unset()时,会被调用

__call():在调用未定义的方法时被调用。

__invoke():当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。

4. php反序列化漏洞原理

当应用程序中存在可用的类、类中有可用的魔法函数、反序列化的参数用户可控的时候,攻击者就可以构造恶意的序列化字符串,让应用程序在序列化字符串的过程中完成恶意操作。

先看一个简单例子:

1669001567_637af15fa97e828010feb.png!small?1669001568636

我们先看这段代码是否满足php反序列化漏洞的利用:

有可用的类:Exam

Exam类中有可用的魔法函数:__wakeup在反序列化时被自动调用

有用户可控的参数:$ex1接受用户传入的user_req的值

这里很简单可以利用eval进行代码执行,执行的值是id的属性值,所以可以构造序列化数据,修改其中的id值为想要执行的代码,比如简单来个phpinfo(); 【注意代码执行必须需要以;结尾】

一般构造的时候,可以先序列化一个初始的Exam类得到:O:4:"Exam":1:{s:2:"id";s:3:"111";},这样不容易出错

然后修改需要修改的值:O:4:"Exam":1:{s:2:"id";s:10:"phpinfo();";}

1669002365_637af47d47e33ef3b40d7.png!small?1669002366163

5. php反序列化漏洞实例

php反序列化在真实场景下不怎么常见,guo外的站可能多一点,guo内的话还是java最多,但php反序列化常在CTF竞赛中出现。

实例1:pikachu靶场中的反序列化,下载点这个

1669014072_637b22388f9a720374651.png!small?1669014073425

作者在这个小节的概述里就给了payload,会显示pikachu:

1669014333_637b233d6488f455372f2.png!small?1669014334181

1669014487_637b23d7a908d9d5731c2.png!small?1669014488452

那我们直接去看源码吧,源码在pikachu源目录下的vul/unserilization/unser.php中,发现可以插xss:

1669014639_637b246f9f3ae37610c01.png!small?1669014640636

1669014864_637b255049b44791e8b79.png!small?1669014865243

很明显可以看到,这个应用程序拥有反序列化漏洞的3个特点:

可用的类:S ; 类中有可用的魔法函数__construct; 有用户可控的参数$s

因为这段程序接收到用户输入的可以反序列化的数据,就将反序列化中的test属性值插入html代码中显示,让人联想到XSS。正好S类中存在test属性,所以可以构造类(如test.php中所示):

1669015956_637b29948276addcd44d3.png!small?1669015957316

1669015985_637b29b1d0885460eeaf7.png!small?1669015986597

实例2:攻防世界web_php_unserialize链接

获取在线场景后,直接给出了源码:

1669016461_637b2b8d87c791aa7be10.png!small?1669016462270

先看满不满足3个条件:有类;类中有可用的魔法函数;输入的参数用户可控

这里提示了flag在fl4g.php文件中,Demo类中的__destruct函数中可以将$file包含到页面中,且$var用户可控

【highlight_file函数:本函数通过使用 PHP 语法高亮程序中定义的颜色,输出或返回包含在 filename 中的代码的语法高亮版本】

所以构造序列化数据如下,序列化过程不序列化函数,所以只需要属性值

1669018260_637b3294cac0acd836e1d.png!small?1669018261542

1669018303_637b32bf800c4ec1e7dd5.png!small?1669018304236

1669018361_637b32f905bfb17ab9aba.png!small?1669018361957

被拦截了,仔细看看代码,发现有两处代码做了限制:

(1)这里对base64解码后的值进行了正则匹配,[oc]:\d+:,/.../i表示忽略大小写

1669018406_637b332601ab0d9272584.png!small?1669018406811

原本的序列化值为:O:4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}

1669019188_637b3634bb9b3106e3bf9.png!small?1669019189498

可以看到匹配的是前面的值,修改为O:+4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}

(2)还有一处是这里,反序列化的时候会检测是不是index.php文件

1669019581_637b37bdda2b7be278c26.png!small?1669019582556

在 PHP5 < 5.6.25, PHP7 < 7.0.10 的版本存在wakeup的漏洞。当反序列化中object的个数和之前的个数不等时,wakeup就会被绕过,所以我们将:1:改为:2:或者其他大于1的值就可以绕过__wakeup。

所以最终:

1669020689_637b3c117b2ad012bcfc7.png!small?1669020690197

1669020713_637b3c2972a1ff7972be8.png!small?1669020714171

1669020745_637b3c4958e6f83bc61e9.png!small?1669020746043

将base64编码后的值赋值给var,得到flag。

java反序列化

不管是php还是java,反序列化的出发点都是一样的,都是为了对象等数据的存储、传输。

为什么需要序列化?

计算机网络中,网络传输可以以字节流或者字符流的格式。如果不进行序列化就传输或者保存,会导致乱码。转化为序列化的方式进行传输后,中文或者符号就可以转变为字节流或者字符流的形式在计算机网络中进行传输,这样就不会形成乱码问题。

在java中,常用到反序列化的场景包括:

1.持久化,如想把对象的字节序列保存到一个文件中或者数据库中时,java对象是在JVM中生成,是内存数据,如果想要保存起来,需要经过序列化转成二进制字节。

2.想将对象进行网络传输的时候,如JNDI、RMI。发送方需要把java对象转成字节序列,接收方需要把字节转成java对象才能在内存中使用。

如在RMI(remote method invocation)通信过程中,允许运行在一个java虚拟机上的对象调用运行在另一个虚拟机上的对象的方法,这两个虚拟机可以是运行在相同计算机上的不同进程中,也可以是运行在网络上的不同计算机中,那么传输过程中均需要序列化数据。

1. java中的序列化和反序列化

JDK类库提供的序列化API:

序列化:java.io.ObjectOutputStream被用来将对象的原始数据序列化

java.io.ObjectOutputStream类的声明

public class ObjectOutputStream extends OutputStream implements ObjectOutput, ObjectStreamConstants

构造函数:

1669108378_637c929a6b56e6935322e.png!small?1669108378138

类中重要方法:

void writeObject(Object obj):将给定参数的obj对象进行序列化,将一连串的字节序列写到指定的目标输出流中。【需要注意的是,被transient关键字修饰的属性不会被序列化】

反序列化:java.io.ObjectInputStream用来将序列化的数据反序列化

java.io.ObjectInputStream类的声明:

public class ObjectInputStream extends InputStream implements ObjectInput,ObjectStreamConstants

确保所有的对象类型和jvm中的一样,使用标准机制按要求加载。

构造函数

1669106141_637c89dda89fc0ca587a9.png!small?1669106141458

类中重要方法

Object readObject():此方法从源输入流读取字节序列,并将其反序列化为一个对象并返回。

2. java中序列化方法:

(1)对类实现serializable接口(隐式序列化)

若要进行序列化的类实现了serializable接口,就可以通过ObjectOutputStream和ObjectInputStream默认的序列化和反序列化方式,对非static和transient关键字修饰的成员变量进行序列化和反序列化。

如下图,先实现一个Student类,如第3行,实现Serializable接口。

1669174971_637d96bb85c756059fa6c.png!small?1669174972438

然后使用java.io.*中的ObjectOutputStream、ObjectInputStream类库进行序列化和反序列化:

1669174997_637d96d55ab4dd5b5d3de.png!small?1669174997814

1669175020_637d96ec7be23c5a13d2a.png!small?1669175020797

1. 从执行结果可以得知:

  • 序列化后的数据无法阅读,必须反序列化后才能转换成能理解的格式。
  • transient修饰的成员不会被序列化和反序列化,如上面例子里面的c_type。证据是最终反序列化后c_type得到null。
  • static修饰的成员不会被序列化和反序列化,如上面例子里面的grade。证据是在第48行修改了grade的值,反序列化后grade值也被修改了。序列化应该保持对象的状态,所以反序列化后值应该与序列化值一样,所以static修饰的grade并没有被序列化。
  • java中同样不序列化类中的方法。

2. 这里使用的io流是ByteArrayOutputStream/ByteArrayInputStream,适用于流的来源和目的地是内存或者数组这种不是文件的情况,也可以使用别的io流如FileInputStream/FileOutputStream.

(2)对类实现Externalizable接口(显式序列化)【项目中真用到的也少】

Externalizable接口继承自Serializable,都是依赖ObjectInputStream和ObjectOutputStream。如果想使用Externalizable进行序列化,需要让类实现Externalizable接口,并重写writeExternal()和readExternal()方法,这两个方法是接口中定义的抽象方法,对应的是Serializable接口中的readObject()和writeObject()方法。在类进行序列化的时候这两个方法将自动执行,代码实现者可以自己选择哪些部分进行序列化。

依然以Student举例,先实现Student类,把之前写的copy一下就好啦,记得修改第三行的接口。

1669189501_637dcf7d6ce21b6f4e666.png!small?1669189501596

成员变量不变,Serializable中写的几个方法也原样保留,这里不截图啦:

1669189748_637dd0742a108ffd80e32.png!small?1669189748318

加一个无参构造函数,不然会报错:

1669190922_637dd50a00bb63c873b8d.png!small?1669190922114

1669189641_637dd009d736512a2f49d.png!small?1669189642006

重写writeExternal和readExternal方法:

1669189691_637dd03ba4fe2c86180de.png!small?1669189691953

然后使用java.io.*中的ObjectOutputStream、ObjectInputStream类库进行序列化和反序列化:

1669189915_637dd11bb763025de656a.png!small?1669189916270

1669190113_637dd1e1c0df5253c781c.png!small?1669190113941

在writeExternal和readExternal的实现中可以自定义需要序列化的属性,比如我们只序列化前三个:

1669190647_637dd3f76721a8d7fa416.png!small?1669190647570

1669190773_637dd4759fd6838749d0f.png!small?1669190773758

只序列化age和grade:

1669195965_637de8bd1c8736b9f57fb.png!small?1669195965188

1669195984_637de8d018e64f10d974b.png!small?1669195984194

从上面的运行结果可以得出如下结论:

  • Externalization实现序列化的时候,类中需要一个无参构造函数,不然会报错
  • Externalization实现序列化的时候,static和transient修饰的成员变量也会被序列化
  • 用重写writeExternal/readExternal方法的方法,实现自定义序列化

(3)对类实现 Serializable接口+writeObject/readObject方法(显隐式序列化)

如果又想使用Serializable接口,又想自定义序列化属性,可以先实现Serializable接口,然后添加writeObject()和readObject()方法。

依然以Student为例,与Serialization接口那段代码一致,但在Student类中添加writeObject和readObject方法,如下图,自定义序列化age、static修饰的grade和transient修饰的c_type:

1669196764_637debdcb911bad4af2bc.png!small?1669196764856

1669196867_637dec43d66a1e45af013.png!small?1669196868168

需要注意的点:

1. 当调用defaultReadObject/defaultWriteObject时,表示默认序列化和反序列化所有非static和transient成员

2. 添加的writeObject/readObject方法,必须是private修饰,必须返回值是void传参也必须是ObjectOutputStream和ObjectInputStream,否则不生效。

3.为什么说是显隐式方法

对于所有非static和transient修饰成员:可采用隐式序列化,调用默认的defaultRead/WriteObject() 
对于static和transient修饰的成员:进行显式序列化

(4)JSON序列化(如阿里开源的Fastjson)

json(javascript object notation)是一种数据格式,不仅非常轻量级,而且简洁清晰。易于程序员阅读编写,也易于程序解析和生成,可以有效提高网络传输效率,那么将java对象转换成Json格式进行保存和传输也是很好的序列化方法。

相信做安全的大家都知道fastjson,在我的专栏《WEB四大洞总结分析》中也有写(还没写完,后续补)。Fastjson是阿里巴巴开源的一个Java库,可用于将Java对象和JSON进行相互转换,相比较于其他JSON库的特点是迅速,应用范围很广。

这个具体怎么使用我就不写了,别人写的比我写的好多啦。

建议的参考资料:

https://www.w3cschool.cn/fastjson/fastjson-quickstart.html

3. java反序列化漏洞

在上文学习java序列化的过程中我们发现,java虽然提供了默认的序列化机制,但是用户也可以自定义序列化策略,比如重写writeExternal/readExternal方法、添加writeObject/readObject方法等。

通过这些方法,用户可以在序列化对象的时候使用writeObject添加一些自定义的数据,然后在反序列化的时候用readObject读取,如果readObject方法的代码中存在一些敏感操作,就可能引发漏洞。所以可以想到的利用思路是:寻找重写了readObject()方法的类、利用自定义的readObject()方法执行代码。

简单例子:还是拿上面的Student类举个例子。

1669202565_637e0285a7d596ebb7c4f.png!small?1669202565719

如上图,我们修改readObject方法为:


private void readObject(ObjectInputStream in) throws ClassNotFoundException,

IOException {

in.defaultReadObject();

Runtime.getRuntime().exec("calc");

}

运行截图:弹计算器

1669202667_637e02eb6852631d32c97.png!small?1669202667412

当然这只是一个最简单的例子,正常情况下,谁会在自己的代码中放入如此简单却高危的代码呢。但是如果通过诸如readObject这类反序列化方法,进行一系列的调用后能构造一个利用链,最终执行攻击者的恶意代码,这就会带来十分严重的安全问题。

利用链例子:

这里就不得不提那个”最被低估了的漏洞“Apache CommonsCollection REC" 和 最最最最最好用的反序列化漏洞工具——ysoserial。

Apache Commons Collections是Apache Commons的组件,广泛用于JBoss、Jenkins、Weblogic等服务器上,2015年底两位黑客发现了其存在反序列化命令执行漏洞。漏洞出现在<=3.1版本的CommonsCollections组件中,核心原因在于org.apache.commons.collections.Transformer接口,有一个InvokerTransformer类实现了Transformer接口,且这个InvokerTransformer类的transform方法可以调用java的反射机制来调用任意函数。

下载commonsCollections3.1

在Transformer接口中有一个transform方法:1669257470_637ed8fe8d4679d6d4eed.png!small?1669257470128

在docs文件夹中给出了这个函数的解释:transform将输入的object转换为输出的object

1669258974_637edede6f4c19c6c5ceb.png!small?1669258973927

对Transformer进行go to implementations可以看到InvokerTransformer类实现了这个接口:

1669257763_637eda23785ba410c235a.png!small?1669257763172

查看InvokerTransformer类,实现了Transformer和Serializable:

1669258278_637edc26220837fd09d82.png!small?1669258277749

其中有类的构造方法和transform方法:(freebuf啥时候能像c某dn一样能嵌代码呢)

1669259867_637ee25bcb76d6a9ae777.png!small?1669259867365

1669259910_637ee286e360922185d24.png!small?1669259910696

transform方法在docs文档中解释如下:通过invoke输入中的一个方法实现输入到输出的转换

1669260146_637ee37253b6b24b271ce.png!small?1669260145788

可以看到transform中使用到了invoke,利用反射机制调用输入对象的任意方法,并且输入参数可控,那么就尝试反射调用Runtime.getRuntime.exec()。

先看构造方法中的三个参数:被调用的方法名、参数类型、参数值

1669261032_637ee6e8a09231ba6443b.png!small?1669261032271

攻击思路为:new一个InvokerTramsformer类,传入要执行的Runtime.getRuntime().exec("calc")函数,再通过transform方法传入类对象。(但Runtime类没有实现Serializable接口,反序列化中不能直接用Runtime执行,但是可以通过反射方式调用,这个后面会进行优化)

1669343930_63802aba4ffb9c5824504.png!small?1669343930682

【利用链详细的介绍会放在反序列化漏洞详解(二),最近终于写完毕业论文了,有了一小段自己的时间,准备好好复习一下反序列化漏洞,包括那几条常用的利用链,加油加油!】

# java反序列化 # php反序列化漏洞 # Java反序列化漏洞分析
本文为 Luc1fer 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
Luc1fer LV.4
这家伙太懒了,还未填写个人描述!
  • 8 文章数
  • 4 关注者
反序列化漏洞详解(二)——URLDNS链
2023-03-16
WEB四大洞(FSWL)总结分析系列——Shiro
2022-10-22
WEB四大洞(FSWL)总结分析系列——log42j
2022-06-08
文章目录