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默认的存储方式是存储到数据库中。
造成这个漏洞可行性的有两个关键点:
- 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
- 还有一个关键点是假设数据库编码是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:"JoomlaRegistryRegistry":2:{s:7:"