笔者《Qftm》原文发布: https://www.freebuf.com/vuls/216512.html
* 严正声明: 本文仅限于技术讨论与分享, 严禁用于非法途径
0*00 背景
10 月 9 号国内几家安全媒体公布了 Joomla RCE 的漏洞预警, 并且网上已公布漏洞利用 EXP https://www.exploit-db.com/exploits/47465 , 影响版本包括 Joomla 3.0.0 - 3.4.6.
0*01 环境搭建
Joomla 是一套全球知名的内容管理系统. Joomla 是使用 PHP 语言加上 MySQL 数据库所开发的软件系统, 目前最新版本是 3.9.11 . 可以在 Linux,Windows,MacOSX 等各种不同的平台上执行.
Joomla 环境搭建下载:
PS: 搭建环境要求 PHP 5.3.10 以上
0*02 漏洞分析
Session 会话机制
PHP 本身对 Session 的存储默认放在文件中, 当有会话产生使用到 Session 时候, 将会在网站服务端设置好的路径写入相应的 Session 文件, 文件的内容为默认序列化处理器序列化后的数据. 然而在 Joomla 中则改变了 PHP 的默认处理规则, 相反将序列化之后的数据存放在数据库的 "hzlnp_session" 表中存储:
将序列化之后的数据存放在数据库中所对应的处理函数为由 session_set_save_handler()设置的 \ libraries\Joomla\session\storage\database.PHP 中的 write():
相应的取值函数 read()也位于 \ libraries\Joomla\session\storage\database.PHP 中
接着从代码中可以看出, 在存入数据库之前, 会将传入数据中的 chr(0) . '*' .chr(0) 替换为 \ 0\0\0, 原因是 MySQL 数据库无法处理 NULL 字节, 而 protected 修饰符修饰的字段在序列化之后是以 \ x00\x2a\x00 开头的. 然后从数据库中取出来的时候, 再将字符进行替换还原, 防止无法正常反序列化.
Session 会话逃逸
session 在 Joomla 中的处理存在一些问题, 它会把没有通过验证的用户名和密码存储在 hzlnp_session 表中
当用户在登陆过程中, 会有一个 303 的跳转, 主要是用于 write()数据库写入用户会话然后 read()相应取出会话进行对比, 显示结果
通过分析 Session 会话机制和 Session 逃逸我们还不明确 Session 形成的漏洞到底在哪!
首先需要了解一下 PHP 的序列化的机制, PHP 在序列化数据的过程中, 如果序列化的字段是一个字符串, 那么将会保留该字符串的长度, 然后将长度写入到序列化之后的数据, 反序列化的时候按照长度进行读取.
知道 PHP 序列化过程之后, 针对 Joomla 的内置序列化方法 write 和 read 函数, 如果写入数据库的时候, 是 \ 0\0\0, 取出来的时候将会变成 chr(0) . '*' . chr(0), 这样的话, 入库的时候生成的序列化数据长度为 6(\0\0\0), 取出来的时候将会成为 3(N*N, N 表示 NULL), 依据 PHP 反序列化原理, 该数据在反序列化的时候, 如果按照原先的长度进行读取, 就会导致溢出.
那么由 "\0\0\0" 溢出会造成什么问题呢? 按照 PHP 反序列化的特点, PHP 按照长度读取指定字段的值, 读取完成以分号结束, 接着开始下一个, 如果我们能够控制两个字段的值, 第一个用来溢出第一个字段和第二个字段的前一部分, 第二个字段的另一部分用来构造序列化利用的 payload, 最终序列化结果将会把第一个字段开始部分到第二个字段的前一部分当成第一个字段的全部内容, 第二个字段内容成功逃逸出来并且被反序列化.
为了触发我们的任意对象并实现 RCE, 我们需要将登录框处的两个字段 username 和 password 进行处理, 第一个字段将导致 "溢出", 第二个字段将包含漏洞利用的最后一部分.
漏洞利用大概思路
编写本地测试代码
此处伪代码对 Joomla 内置的 write(),read()函数进行模拟, username 字段通过 9 组 "\0\0\0" 进行赋值, 其值序列化存入数据库 db.txt 长度为 54, 反序列化取出长度变为 27, 加上后面第二个字段 password 的 27 个字符构成实际的 username 值:
O:4:"User":2:{s:8:"username";s:57:"xx...xxx";s:8:"password";s:nn:"payload";}
由于 ";s:8:"password";s:nn:" 长度为 23,\0\0\0 经 read()函数处理之后会减半, 所以要想覆盖 password 字段值, username 字段值就要取 9 组 "\0\0\0", 同时 password 的字段值长度至少需要是两位(nn 代表占位符), 因为实际上 payload 的长度会大于 10.
测试结果 实现任意对象的注入
构造 POP 执行链, 执行任意代码
首先参考 PHITHON 师傅的一篇文章 "Joomla 远程代码执行漏洞分析" 收获很多, 依据 PHITHON 师傅的思路, 同样, 在我们可以控制反序列化对象以后, 我们只需构造一个能够一步步调用的执行链, 即可进行一些危险的操作了. exp 构造的执行链, 分别利用了 JDatabaseDriverMysqli 和 SimplePie 类
我们可以在 JDatabaseDriverMysqli 类 (\libraries\Joomla\database\driver\mysqli.PHP) 的析构函数里找到一处敏感操作:
由于 exp 构造的对象反序列化后, 将会成为一个 JDatabaseDriverMysqli 类对象, 不管中间如何执行, 最后都将会调用__destruct 魔法函数,__destruct 将会调用 disconnect,disconnect 里有一处敏感函数: call_user_func_array. 但很遗憾的是, 这里的 call_user_func_array 的第二个参数是我们无法控制的, 但是, 我们可以进行回调利用:
call_user_func_array([$obj,"任意方法"],array( &$this))
进一步跟踪到 SimplePie 类 (\libraries\simplepie\simplepie.PHP), 通过将 SimplePie 对象和它本身的 init() 函数可以组成一个回调函数[new SimplePie(), 'init'], 传入 call_user_func_array
分析 init()函数
通过分析代码发现此处的 call_user_func 参数可控, 只要满足条件 $this->cache=true && $parsed_feed_url['scheme'] !== Null, 将其中第二个 call_user_func 的第一个参数 cache_name_function 赋值为 assert, 第二个参数赋值为我们需要执行的代码, 这样就可以构成一个可利用的 "回调后门", 达到任意代码执行效果.
PS: 对于网上爆的利用回调后门在网站根目录下的 configuration.PHP 中写入一句话木马 getshell 这种方式, 在真实环境中大多都不能利用成功(权限问题), 效果并不是太好.
0*03 漏洞预防
1, 版本更新
2, 对 session 信息进行编码存储
0*04 参考链接
来源: https://www.cnblogs.com/qftm/p/11743263.html