关于Session的前置知识:
session 对数据的序列化方式一共有三种:
默认是 php 处理器:session.serialize_handler = php
效果如图:
通过|分割数据,|前面为键值,后面为数据序列化后的内容。
当php <= 5.6.13的时候,php底层对该数据的处理方式是:
获取第一个 | 然后解析其前面为键,后面的内容反序列化后为值。
但是当后面的内容有问题,不能成功反序列化的时候,就将 寻找下一个 | ,重复这里执行。
问题就出在这里。测试如图:
假如,我们后面的语句因为某些原因被截断了呢?
joomla 问题也出在这,因为joomla 修改了默认储存引擎,session.save_handler,将其改为了mysql储存,这本来是没有问题的,
但是mysql 设置数据的字符集=utf-8 的时候,mysql中的utf-8是被阉割版的,只占三个字节,utf8mb4才是完整的utf8,占四个字节。
所以当我们的可控字符中存在一个 4字节的数据的时候,就会导致数据被截断,导致不能成功反序列化我们的数据。然后处理引擎寻找下一个 | 进行解析。
如图:
其实不用66个字符,只要大于该后面的所有字符总和就行了。
总结:1.php <= 5.6.13的时候,php序列化引擎会依次向后序列化每一个键值对,根据就是 | (竖线)
2. joomla也刚好使用了mysql,utf8。这就会导致数据被截断,不能正常反序列化,然后直接寻找下一个 | 就直接找到了我们伪造的 |
Joomla 3.4.6 RCE -- 这个漏洞更神。
前置知识:https://www.php.net/manual/zh/function.session-set-save-handler.php
session_set_save_handler( 函数:设置用户自定义的会话储存函数。
需要实现:SessionHandlerInterface 接口中的6个方法。https://www.php.net/manual/zh/class.sessionhandlerinterface.php
简而言之:
$name = $_SESSION['name']; 会调用你重写的 read()函数
$_SESSION['name'] = 'test123'; 会调用你重写的 write()函数
看看joomla 中重写的 read 和 write 方法:
因为序列化 受保护的,私有的属性的时候,会产生%00 这种 NULL 字符,mysql是没办法储存 NULL 字符的。
所以程序就通过
$data = str_replace(chr(0) . '*' . chr(0), ' ', $data);
这种替换的方式,在写入的时候替换掉NULL字符,在读取的时候在替换回来,这看似一点问题都没有。
但如果我们的数据中就直接包含了 这个字符呢?那么在取出的时候,也会二话不说,给我们替换成chr(0) . '*' . chr(0),这样就会造成字符个数不对等的差异。
正常的序列化是这样的:
就是前面的字符个数,和后面的数据,肯定是一样的。但是由于我们写入的时候: 这个字符串计算的是6个字符,但是读取出来的时候替换成了3个字符。
而且前面的个数是不会变的,这就会导致反序列化的时候继续向后取,不管你是啥,反正取够这么多字符为止,这就会导致我们可控的数据逃逸了出来,造成了反序列化。
成功因为逃逸造成反序列化:
注意:这个是逃逸,不是因为反序列化数据出错导致,所以这个是不受PHP版本限制的。php 7依然可以。
参考文章:
https://www.leavesongs.com/PENETRATION/joomla-unserialize-code-execute-vulnerability.html 第一个漏洞
https://www.anquanke.com/post/id/83120 第一个漏洞为什么版本 <= 5.6.13
https://www.freebuf.com/vuls/216512.html 第二个漏洞
https://github.com/80vul/phpcodz/blob/master/research/pch-013.md ctf容易出题的地方,处理器差异造成。
http://www.360doc.com/content/11/1222/00/1372409_174118218.shtml joomla源码分析
https://www.anquanke.com/post/id/86178 joomla源码分析