PHP内核层解析反序列化漏洞-PHP7

资源魔 40 0

媒介

正在学习PHP的进程中发现有些PHP特点的货色欠好了解,如PHP中的00截断,MD5缺点,反序列化绕过__wakeup等等。自己没有想拘泥于外表景象的了解,想探索PHP内核究竟是怎么做到的。

上面是将用CTF中罕用的一个反序列化破绽CVE-2016-7124(绕过邪术函数__wakeup)为例,将这次调试PHP内核的进程分享进去。包罗从内核源码调试环境的搭建,序列化与反序列化内核源码剖析到最初的破绽剖析整个局部。(保举:PHP教程)

1、一个例子诱发的考虑

咱们能够起首看自己写的小例子。

48f696a5da4bdb66eb51f24fb505879.png

依据上图咱们先引见下PHP中的邪术函数:

咱们先看下民间文档对几个罕用邪术函数的引见:

b7e7957c6cf875a78c1ec87395a5468.png

这里稍作总结,当一个类被初始化为实例时会挪用__construct,当被销毁时会挪用__destruct

当一个类挪用serialize进行序列化时会主动挪用__sleep函数,当字符串要行使unserialize反序列化成一个类时会挪用__wakeup函数。上述邪术函数假如存正在都将会主动进行挪用。不必本人手动进行显示挪用。

如今咱们来看最开端的代码局部,正在__destruct函数中有写入文件的敏感操作。咱们这里行使反序列化结构风险的字符串有可能会造成代码执行破绽。

当咱们结构好相应的字符串预备进行行使时,咱们却发现它的__wakeup函数中有过滤操作,这就给咱们的结构造成为了障碍。由于咱们晓得反序列化无论若何都是要先挪用__wakeup函数的。

这里咱们不由想到了行使这个PHP反序列化破绽CVE-2016-7124(绕过邪术函数__wakeup),轻松绕过反序列化会主动挪用的邪术函数___wakeup,把敏感操作写入进了文件。

当然,下面的代码只是我集体举患上一个简略例子,实在状况中没有乏有上述相似状况的呈现。然而这类绕过办法却使我十分感兴味。PHP的外部究竟是若何操作以及解决才会影响到下层代码逻辑呈现如斯神秘的状况(BUG)。接上去自己将对PHP内核进举动态调试剖析。探索此成绩。

此破绽(CVE-2016-7124)受影响版本PHP5系列为5.6.25以前,7.x系列为7.0.10以前。以是咱们前面会编译两个版本:一为没有受此破绽影响的版本7.3.0,另外一个版本为破绽存正在的版本5.6.10。经过两个版本的比照来更具体的理解其差别。

2、PHP源码调试环境搭建

咱们都晓得PHP是由C言语开发,因自己所应用环境为WIN 10,以是次要引见Windows下的环境搭建。咱们需求以下资料:

PHP源码
PHP SDK对象包,用于构建PHP
调试所需求IDE

源码可正在GITHUB上下载,链接:https://github.com/php/php-src,能够抉择所需求的版本进行下载。

PHPSDK 的对象包下载地点: https://github.com/Microsoft/php-sdk-binary-tools 这个地点所下载的对象包只支持VC14,VC15。当然你也能够从https://windows.php.net/downloads/找到支持PHP低版本的VC11,VC12等,正在应用PHP SDK以前必需保障你有装置对应版本Windows SDK组件的VS。

后文中会应用PHP7.3.0以及5.6.10,上面会引见这两个版本的源码编译,其余版本手段相似。

2.1 编译Windows PHP 7.3.0

本机环境WIN10 X64,PHP SDK是正在上述github链接上下载。进入SDK目次,发现4个批解决文件,这里双击phpsdk-vc15-x64

d385e20f70d635ad86bd1d16710839d.png

接着正在此shell中输出 phpsdk_buildtreephp7,会发现同目次下呈现了php7文件夹,而且shell目次也发作了变动。

e0dcf729e7fd36d815c4f8b6e1480ab.png

c48cc1b85edb0391f00b6e3bca041f2.png

接着咱们把解压后的源码放正在\php7\vc15\x64下,shell进入此文件夹内,行使phpsdk_deps–update–branchmaster饬令更新下载相干依赖组件。

期待实现后,进入源码目次下双击buildconf.bat批解决文件,它会开释configure.bat以及configure.js两个文件,正在shell中运转configure–disable-all–enable-cli–enable-debug–enable-phar 设置装备摆设相应的编译选项,如另有此外需要,可执行 configure –help 查看

12f825bda155b80a5b5543547a03326.png

依据提醒,间接应用nmake进行编译。

52a301681eb6a2454ac2ea157e4ff70.png

编译实现,可执行文件目次正在php7\vc15\x64\php-src\x64\Debug_TS文件夹下。咱们可输出php -v查看相干信息。

285e432548bca9084e96c86861e7eeb.png

2.2 编译Windows PHP 5.6.10

办法跟7.3.0 相反,只要留意的是PHP5.6应用WindowsSDK 组件版本为VC11,需求下载VS2012,而且不克不及应用github上下载的PHP SDK进行编译,需求正在 https://windows.php.net/downloads/ 上抉择VC11 的PHP SDK以及相干依赖组件进行编译,其他以及上述齐全相反,这里再也不反复。

defc92eb9315a7ec7a6eb658c84d430.png

2.3 调试设置装备摆设

由于咱们上述曾经编译好了PHP诠释器,咱们这里间接应用VSCODE来进行调试。

下载实现后装置C/C++调试扩大。

9325aeeeefa771ad220f5d317be48ad.png

接着关上源码目次,点击调试—>关上设置装备摆设,会关上launch.json文件。

64c7d99511ed427a6f61db2a81b0199.png

依据上图,设置装备摆设好这三个参数后,可正在以后目次下1.php中写PHP代码,正在PHP源码中下断点间接进行调试。

调试环境搭建实现。

3、PHP反序列化源码解析

普通说起PHP反序列化,往往就是serialize以及unserialize两个成对呈现的函数,当然必不成少的另有__sleep()以及__wakeup()这两个魔术办法。家喻户晓,序列化简略点来讲就是工具存文件,反序列化刚好相同,从文件中把工具读掏出来并实例化。

上面,咱们依据下面搭好的调试环境,经过静态调试的手段来直观的反响PHP(7.3.0版本)中序列化与反序列化到底干了哪些事件。

3.1 serialize源码剖析

咱们先写个没有含有__sleep邪术函数的简略Demo:

0ddeae6f869eb22e17da5df6dbbbe73.png

接着咱们正在源码中全局搜寻serialize函数,定位此函数是正在var.c文件中。咱们间接正在函数头下断点,并启动调试。

a78fa59fdd0753c40ff5892b11c37c4.png

咱们可见正在做了一些预备工作后,开端进入序列化解决函数,咱们跟进php_var_serialize函数。

468c514b15888e578fb804b9e53eec6.png

咱们这里持续跟进php_var_serialize_intern函数,上面就是次要解决函数了,由于函数代码比拟多,咱们这里只截出要害局部,此函数还正在var.c文件中。

10893067a68e9b1a53bac22dc081674.png

整个函数的构造是switch case,经过宏Z_TYPE_P解析struc变体的类型(此宏开展为struc->u1.v.type),来判别要序列化的类型,从而进入相应的CASE局部进行操作。下图为类型界说。

d63aff2c4e686ec87d71ddb6bc9d409.png

依据上图红框中的数字8,咱们可知此时需求要序列化为一个工具IS_OBJECT,进入相应的CASE分支:

a60211fc93252c036d53c0b6d97d9c1.png

咱们正在上图中看到了邪术函数__sleep的挪用机遇,由于咱们写的Demo中并无此函数,以是流程其实不会进入此分支。没有同的分支代表没有同的解决流程,咱们稍后再看带有邪术函数__sleep的流程。

d82b1386382b48674d562b7195d5434.png

因下面case IS_OBJECT分支中不流程掷中,case中又不break语句,持续执前进入IS_ARRAY分支,正在这里从struc构造中提掏出类名,较量争论其长度并赋值到buf构造中,并提掏出类中要序列化的构造存入哈希数组中。

28e277c8578e81bf459c8391148b167.png

接上去就是行使php_var_serialize_intern函数递归解析整个哈希数组的进程,从中辨别提掏出变量名以及值进行格局解析并将解析实现的字符串拼接到buf构造中。最初当整个进程完结后,整个字符串讲齐全存进柔性数组构造buf中。

5772c7b2a09f3686b9acb3100cc1619.png

从上图红框中可看出跟终极后果是相吻合的。咱们接上去略微修正下Demo,增加邪术函数__sleep,依据民间文档中形容,__sleep函数必需前往一个数组。咱们并正在该函数中挪用了一个类的成员函数。察看其详细行为。

24b02c6423c567b8eda83572eb7a429.png

后面流程齐全相反,此处再也不反复,咱们从分支点开端看。

8be28c3b0c14530f057166286e92427.png

咱们间接跟进php_var_serialize_call_sleep函数。

0094a655b766ca3ca16dbc5604a3e72.png

咱们这里持续跟进call_user_function,依据宏界说,它其实是挪用了_call_user_function_ex函数,正在这里做了一些拷贝举措,故没有做截图,流程接上去进入zend_call_function函数的挪用。

cc8bedd99bb300bec524a7d7011e8a1.png

函数zend_call_function中,实际状况下,正在__sleep中需求做一些咱们本人的事件,这里PHP将要做的操作压入PHP本人的zend_vm引擎货仓旅馆中,稍后会进行一条条解析(就是解析相应的OPCODE)。

2c74401155751a75c04d27c57ca3cb4.png

这里流程会掷中此分支,咱们跟进zend_execute_ex函数。

0e98ccf329d540dccf7746a35b6aa66.png

咱们这里能够看到正在ZEND_VM中,全体体解决流程为while(1)轮回,一直解析ZEND_VM栈中的操作。上图红框中ZEND_VM引擎会行使ZEND_FASTCALL形式派发到到相应的解决函数。

4f8d50c705b53de2d813785cdbad82d.png

ec6c465cfccca3094cdc0be41ca693b.png

由于咱们正在__sleep中挪用了成员函数show,这里起首定位出了show,接着会将接上去的操作持续压入ZEND_VM货仓旅馆中进行下一轮新的解析(这里是解决show中的操作),直到解析完好个操作为止。咱们这里再也不持续跟进。

155f0fb4269438897c65d27f774c0fb.png

还记患上下面的传出参数retval么,也就是__sleep的前往值,上图为前往数组的第一个元素x,当然你也能够从变量中间接查看。

绕了这么年夜一圈,异曲同工,正在解决完_sleep函数中的一系列操作之后,接上去用php_var_serialize_class函数来序列化类名,递归序列化其_sleep函数前往值中的构造。终极都把后果存正在了buf构造中。至此序列化的整个流程终了。

3.1.1 serialize流程小结

咱们总结下序列化的流程 :

当不邪术函数时,序列化类名–>行使递归序列化剩下的构造

当存正在邪术函数时,挪用邪术函数__sleep–>行使ZEND_VM引擎解析PHP操作—>前往需求序列化构造的数组–>序列化类名–>行使递归序列化__sleep的前往值构造。

3.2 unserialize源码剖析

看完serialize的流程,接上去,咱们仍是从最简略的一个Demo来看unserialize流程。此例子没有含邪术函数。

025c53fd4037d0ff90a2eca839d1c58.png

办法跟下面相反,unserialize源码也正在var.c文件中。

6628b4a118158447d700dcb8e2ca97c.png

a8745adda2536eea6d2c4cb57651b57.png

上图中触及到了PHP7中的新特点,带过滤的反序列化,依据allowed_classes的设置状况来过滤相应的PHP工具,避免合法数据注入。被过滤的工具会被转化成__PHP_Incomplete_Class对象不克不及被间接应用,然而这里对反序列化流程不影响,这里没有做具体讨论。咱们跟进php_var_unserialize函数。

77cb18f8c040bc50490b76fd8b5595d.png

咱们这里持续跟入php_var_unserialize_internal函数。

6cdce4273101dc353efcb40858cc663.png

此函数外部次要操作流程为对字符串进行解析,而后跳转到相应的解决流程。上图中解析出第一个字母0,代表这次反序列化为一个工具。

a3a56e193bbe64e395746cfa1f2e13d.png

这里起首会解析出工具名字,并进行查表操作确定此工具的确存正在,咱们持续向下看。

500e225f41c7018ffbd2f6807768b9a.png

上述操作做完之后,咱们这里依据工具称号new出了本人新的工具并进行了初始化,然而咱们的反序列化操作仍是不实现,咱们跟进object_co妹妹on2函数。

正在这里咱们看到了对邪术函数的判别与检测,然而挪用局部其实不正在此。咱们持续跟进process_nested_data函数。

193aeb9635fda3e670da2f6015fa42d.png

32fe49bf4b7b8f74c49fcfc9562e25c.png

看来这个函数行使WHILE轮回来嵌套解析残余的局部了,·此中蕴含两个php_var_unserialize_internal函数,第一个会解析称号,第二个是解析称号所对应的值。process_nested_data函数运转终了后,字符串解析终了,反序列化操作次要内容曾经实现,流程行将进入序幕了。

1721ceaa31d4bf26a24a3203594f3e8.png

逐层前往至最后的函数PHP_FUNCTION中,咱们看到就是一些开头工作了,开释请求的空间,反序列化终了。这里并无挪用到咱们的邪术函数__wakeup。为了找出__wakeup的挪用机遇,咱们这里修正下Demo。

5b2c31167b7371255bcb4910e4103f3.png

这里开端新的一轮调试。发如今序列化实现后,正在PHP_VAR_UNSERIALIZE_DESTROY开释空间处呈现了咱们所心愿看到的挪用。

13574172311b287d3331f773e75a6f2.png

还记患上反序列化流程中当发现有__wakeup时对其进行的VAR_WAKEUP_FLAG标记么,正在这里当遍历bar_dtor_hash数组遇到这个标记时,正式开启对__wakeup挪用,前期的挪用手段以及后面所引见的__sleep挪用手段齐全相反,这里再也不做反复阐明。至此,反序列化一切流程终了。

3.2.1 serialize流程小结

咱们能够从下面能够看到,反序列化流程绝对于序列化流程来讲并无由于能否呈现邪术函数来对流程造成份歧。Unserialize流程以下:

猎取反序列化字符串–>依据类型进行反序列化—>查表找到对应的反序列化类–>依据字符串判别元素个数–>new出新实例–>迭代解析化剩下的字符串–>判别能否具备邪术函数__wakeup并标志—>开释空间并判别能否具备具备标志—>开启挪用。

4、PHP反序列化破绽

有了下面源码根底的铺垫,咱们如今再来探索破绽CVE-2016-7124(绕过__wakeup)邪术函数。

因而破绽对版本有肯定要求,咱们应用下面编译好的另外一个PHP版本(5.6.10)来复现以及调试此破绽。

起首咱们进行一下破绽复现:

8a60f251cdbfec83fe3f059bd8ae6d1.png

咱们这里能够看到,TEST类中只蕴含一个元素$a,咱们这里正在反序列化时当修正元素字符串中代表元素个数的数值时,会触发此破绽,该类避过了邪术函数__wakeup的挪用。

当然正在触发破绽的进程中也发现了一个风趣的景象,触发手法其实不只有这一种。

0b90b8d622cba2a40be614f2193b059.png

上图中4个payload所对应的反序列化操作城市触发此破绽。尽管说下方这四个城市触发破绽,然而此中另有一些巨大的差异。这里咱们略微修正下代码:

a0b096081da1ca9ac1cc5abd67e0c93.png

咱们依据上图能够看到,正在反序列化的字符串中,只需正在解析类中的元素呈现谬误时,城市触发此破绽。然而更改类元素外部操作(如上图的修正字符串长度,类变量类型等)会招致类成员变量赋值失败。只有修正类成员的个数(比原有成员个数年夜)时,能力保障类成员赋值时胜利的。

咱们上面来经过调试来看成绩所正在:

依据第三局部咱们对反序列化源码的剖析,猜想多是正在最初解析变量哪里出了成绩。咱们这里间接上调试器静态调试下:

bb6146570313cb8c6a79e016983f4c3.png

咱们能够看到,与7.3.0版本的源码比照,此版本不过滤参数,且通过这么多版本的迭代,低版本的解决进程如今看来也绝对简单。然而全体谐逻辑并无扭转,咱们这里间接跟进php_var_unserialize函数,尔后相反逻辑再也不进行反复阐明,咱们间接跟赴任异处(object_co妹妹on2函数)也就是解决类中成员变量的代码

e609362ae9d1e428e701d210dda4a16.png

正在函数object_co妹妹on2中,存正在两个次要操作,process_nested_data迭代解析类中的数据以及邪术函数__wakeup的挪用,且当process_nested_data函数解析失败后,间接前往0值,前面的__wakeup函数将不挪用的机会。

这里就诠释了为什么触发破绽没有止一种payload。

c03900fa99ded97585afa451ccea295.png

当只修正类成员的个数时,while轮回能够实现的进行一次,这使患上咱们类中成员变量能被完好的赋值。当修正成员变量外部时,pap_var_unserialize函数挪用失败,紧接着会挪用zval_dtor 以及FREE_ZVAL函数开释以后key(变量)空间,招致类中的变量赋值失败。

反观正在PHP7.3.0版本中此处并无呈现挪用进程,只是做了简略的标志,整个邪术函数的挪用进程的机遇移至开释数据处。这样就防止了这个绕过的成绩。此破绽应该属于逻辑上的缺点招致的。

以上就是PHP内核层解析反序列化破绽的具体内容,更多请存眷资源魔其它相干文章!

标签: php7开发教程 php7开发资料 php7开发自学 PHP反序列化漏洞

抱歉,评论功能暂时关闭!