汤青松 中公教育高级安全工程师
摘要:PHP的反序列化漏洞也叫PHP对象注入,如果达到此漏洞利用的条件,一般都会产生很严重的后果;主要原因没有对前端传入的参数进行检测,导致攻击者通过控制反序列化过程,从而导致代码执行、文件操作、执行数据库操作等不可控后果;本文就PHP反序列化进行分析讲解,议题内容包含漏洞原理、利用方式、实践演示等。
今天分享的PHP反序列化也是异样安全,每一年都会爆发反序列化的问题,PHP反序列化的问题大家会很少听到或者是没有听过,有几点,首先(英)影响的范围比较广泛,PHP反序列化主要是在CMS或者是框架当中去做,影响范围比较少,它的门槛比较高,关注的人也会比较少一些,在去漏洞第一时间会挖一些(英),应用扫描器扫描或者是常见的逻辑性的漏洞,对于反序列化漏洞一般关注点相对来说比较少,但反序列化漏洞有一个特点,如果利用成功了,它的危害往往是比较大的,可以面面执行,代码注入等等。
我今天分享相对来说比较偏一点的话题,今天有点,首先认识漏洞为什么会产生。第二个怎么利用它。第三个拿一个实例讲解一下。
反序列化漏洞有一些同学不是太清楚,和反序列化有什么关系,我相信很多人去淘宝买家具,不会直接把家具给你运过来,会把这些东西打包拆成一块一块,序列化的过程也是一样的,会把一个“类”或者是“数组”变成这么一个串,反序列化会把这个串再反向操作一下,再把它的变量复制过去。
认识漏洞,这个漏洞相对来说比较偏,首先要知道PHP的对象,模数方法,序列化,反序列化,序列化与魔术方法,以及数据结构。
先看一个代码,这是一个非常简单的小样代码,在代码当中能够看到一个class,这是对象,各种语言基本上都差不多,运行之后会执行对象里面的某一个方法,魔术方法,它是在我们不去主动调用某一个方法自动去触发,在PHP反序列化漏洞当中就利用了自动触发的问题去造成问题,面向对象编程语言会执行它的方法,销毁一个对象的时候,这些自动触发的方法可以理解为魔术方法。
这个代码当中就是加了下面几个方法,这三个方法,第一个方法这样的对象会自动触发它。第二个就是结束会触发。第三个把对象当成错的去触发,我们先看一下这个会自动触发方法。
第二个是手动调用方法,后面把对象作为字符串自动输出,最后程序要结束,这是运行结果,这是一个反序列化漏洞主要的关键点。
假设还是有一个类,会有两个变量,这里面会有两个方法,有了这样一个对象,把对象两个变量复制,调用了一下这个方法,这边会输出字符串,再把它进行序列化操作,输出出来,输出的结果这个是程序当中(英)的值,这是反序列化的数据,把反序列化的数据复制出来,再认识一下反序列的操作,这个代码当中同样的代码不变,下面不去做这个方法,把它的序列化数据放在这里面,反序列化操作一下,得到的还是这样的类,并且在序列化类的过程当中会把变量给复制进去,这边并没有给进行复制操作,依然是零,这边还是一个空的,当我们执行这个方法我们输出的结果这个是我们这边的名字,这是运行结果。
反序列化操作会做两个事情,这边有一个自定义的方法,后边都是魔术方法,在自动触发的时候,来认识一下这几个魔术方法,序列化也会触发这样的方法,当我们输出序列化字符,在这边进行反序列化操作的时候又会调用这样的魔术方法,在反序列化漏洞操作的时候,在迂回的不断尝试这几个方法的过程,接下来看一下例子,这是刚才代码的运行结果,可以看到这边的魔术方法都是自动执行的,序列化的结构,这里面定了一个数组,数组当中进行了序列化操作,并且把序列化操作的数据给输出出来了,PHP序列化结构是什么,3是代表数组,有3个数据,ABC,这边有一个S1,就代表字符串,1是它的长度,内容是A,大概是这样的结构,这边还有一些特征,我们一会儿用到对象的时候它可能是O,代表的是对象。
接下来看一下怎么去利用的,我们在这个代码当中有两类,第一个类,第二个类,下面有一行代码,这行代码不在这两个类当中,代码是有漏洞的,怎么利用,在执行之后前端传递了一个(英),通过(英)传递了一个数据,这里并没有进行安全过滤,就把它进行到了反序列化操作,这里就会触发一个操作,在这个类里会自动触发这个魔术方法,又会调用自定义方法,这个方法当中又会调用这个对象,这个对象当前类并没有这样的对象,它是一个变量,怎么做?我们可以把变量复制为对象,可以把这样的变量复制为这样的对象,在这个对象里面又可以把PHP设置为我们想执行的命令,调用这样的方法就执行了相应的命令,看一下POC,第一步我们在这边再写一个POC的代码,让程序识别,在这个对象当中把它进行复制,这样的变量在识别的时候会调用识别方法,自动给变量复制,复制的时候又把下一个对象放进去,下一个对象在方法里面也放了一个(英),这样的值就是(英),这个变量里面的数据就是一个对象,运行出来之后就会输出这样的字符,当把这样的字符放到前面代码传递过去就会执行相应的操作,把结果放进去,因为会执行(英),再通过反序列化操作就会产生这样的结果。
我们看一个实例,这个实例在PHP里面比较流行的博客系统,之前爆发了一个代码的漏洞,这个漏洞通过反序列化操作的过程,分析一下漏洞的利用过程,我们说到PHP反序列化操作,就是使用反序列化函数,把字符串放进去,当这个函数去调用了,就可能产生反序列化的漏洞,在这个博客当中直接去搜索函数,在安装的位置这个地方有这样的一串代码,这个代码我们可以看到只做了三个事情,首先从cookie取出数据,通过base64解码后进行反序列化操作,从前端传递出来数据没有进行安全过滤就放在反序列化操作,就会产生安全问题,有这样的安全问题最主要是看它能不能利用,如果有一个漏洞,但是这个漏洞并不能进行利用,那就说明没有什么效果,反序列化操作的时候会调用特征方法,反序列化操作的时候会捋一个对象,会调用反序列化操作方法,会调用一个魔术方法,在进行反序列化漏洞的时候有两种思路,第一个就是直接利用,只要进行反序列化操作必定会执行这三个方法,这是反序列化的魔术方法以及对象结束的方法,我们就可以搜索全系统当中有哪些位置有这几个方法,看这三个方法有没有对它当前类里面的变量进行处理,使用(英)处理当前的变量或者是其他的方法,如果有的话就会有问题,反序列化操作是可以控制某一个类里面的变量数据,在处理变量的时候就会产生相应的问题。
另外一个就是间接利用,去了一个迷宫,去迷宫有两种结果,第一种我们直接找到出口。另外一种我们并不能一下子找到出口我们会在里面绕,一直找到出口为止,我们在直接利用的时候不能用,就会对代码进行跟进,首先会排除这三个方法再去找其他可以间接利用的方法,比如tostring,(英)等方法。
回到这个代码当中,这个代码当中按照直接利用思路会搜索魔术方法,在全局系统里面去搜这几个,在系统当中就找到了这两处位置有方法,但是并没有对变量进行处理,这个位置并不适用,直接利用法把它放弃了,接下来就需要迂回的去看这个系统当中还有没有其他的安全问题,间接利用我们除了直接利用的那三个魔术方法,跟进代码看它有没有把一个对象进行字符串操作,比如他把一个对象进行字符串操作的时候就会去触发(英)方法,也有可能在调用变量的时候调用到不可访问的变量,就会触发另外一个(英)的魔术方法,我们接着对代码进行审计,在刚才那串代码的时候看到它下面又把反序列化数据了,里面有两个变量放在这里面,我们就根据这个方法里面的处理,这里面传递了一个(英),首先看这样一个变量,它确实是把这样的变量作为字符串处理,假设我们把这样的变量是作为对象,并没有进行安全过滤,把它作为对象,就会触发(英)的魔术方法,什么地方有(英)方法,只要在全局系统当中搜索一下,搜索的时候就搜索(英),搜索有三处进行(英)操作,虽然我们找到了这些方法,但这些方法有没有对变量进行相应的处理,前面两处是没有进行任何处理的,最后一处他是有对对象进行处理,我们继续跟进,处理的时候并没有直接对某一个变量进行安全的过滤或者是进行相应的转换,它是又进行了一次迂回,就像我们去迷宫,迷宫我们找到一个房间,这个房间发现并不是一个出口,还有一条门就继续跟进这条门,前面直接利用的三个方法就排除了,再加上(英)只找到当前类可以利用的,(英)也排除了,还剩下哪几个魔术方法可以利用,可能会有set、isset这几个魔术方法可以被利用,我们的(英)有没有调用不可访问的代码,继续跟进,这三行代码需要注意,这三行代码有什么样的特点,首先调用了当前变量类里面的变量,反序列化操作是可以知道它的结构,把这样的变量定义为数组,里面某一个(英)值再给它一个对象,并且这样的对象是不存在这样的值。这样的一个类还要满足一个条件,这个类必须要有一个(英)魔术方法,继续接着去迂回,搜索,在这些系统当中有哪些get的魔术方法,全局搜索之后又搜索出三个位置有get的方法,有两处是没有意义的,因为并不能调用当前类里面的变量进行处理,因为(英)它有,我们可以看到它这边又调用了一个get,把变量返回,我们继续跟进get的方法,跟进之后,我们看看这个代码,在这个方法里面他有一个案子判断一下当前类里面的变量是否存在,如果存在的时候就取里面的值,这个变量我们是可以控制的,我们在反序列化操作的时候肯定会把变量设置成这样的值,我们是可以控制会走这样的一行代码,它上面还有一行代码,这行代码会判断当前变量是数组还是字符串,以及进行相应的过滤,这里做了一个安全过滤,这里面就有一处问题了,首先会判断(英)有没有(英),(英)是否存在,我们是可以控制这样类里面的方法值,可以让它找这里面的方法,使用哪些方法对某一个值进行过滤,既然(英)可以控制,在(英)我们可以填入(英),继续判断这个值是数组还是对象,使用不同的方法把(英)对值进行过滤,把这样的(英)放进去,(英)可以控制,我们可以有一个任意代码执行的问题,接着根据我们刚才的思路,我们构造一个POC,在构造POC的时候,主要有四点,首先我们可以看到取数据的位置取得(英)数据,取得的是64的数据,POC就输出一个64的数据,这是最外面的包装,里面调用了两个变量,这里就给他两个变量,在前面有一个方法把某一个变量当做字符串,为了让它触发(英)方法,我们就给他放一个对象,当他把数据作为字符串处理就做成(英)方法,这个方法里面有什么?这个是POC,前面调用了一个不存在的方法,这个不存在的方法是有属性的,调用了这样的方法我们又给他放入了相应的对象,这个对象就是(英)的对象,主要给它设置两个值,第一个设置了(英)值,我们想执行某一个函数,是动态的。第二个会获取变量,前面那个代码当中调用了变量,把相应的值传进去,结果就会产生这样的效果,就是(英)里面/1,就会被执行出来,也就是这样的POC就构造成功了。
刚才复盘的时候,这边取的是(英)64,这边输出了64,为了让它触发(英)方法,因为这个地方是把它作为字符串处理,我们这个地方放入一个对象就可以调用这里面的方法,在调用(英)方法这里面使用了构造的魔术方法,这些本来应该是字符串的数据,结果又把它放入了另外一个类,在调用这样数据的时候,结果发现这样的类是找不到的,这样的类本来是可以找到的数据,结果忙入这样的类,这样的参数反而是私有方法,找不到,又会调用get方法,调用get方法又会触发构造方法,环环相扣,就执行了(英),传递出了一个/1。(速记)