467 lines
16 KiB
Python
Executable File
467 lines
16 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
# This tool is designed to be an easy way to test exfiltrating data
|
|
# from the network you are currently plugged into. Used for red or
|
|
# blue teams that want to test network boundary egress detection
|
|
# capabilities.
|
|
|
|
|
|
import argparse
|
|
import copy
|
|
import os
|
|
import random
|
|
import socket
|
|
import ssl
|
|
import string
|
|
import sys
|
|
import time
|
|
import urllib2
|
|
from ftplib import FTP
|
|
from ftplib import error_perm
|
|
from SocketServer import ThreadingMixIn
|
|
from threading import Thread
|
|
from BaseHTTPServer import BaseHTTPRequestHandler
|
|
from BaseHTTPServer import HTTPServer
|
|
from pyftpdlib.authorizers import DummyAuthorizer
|
|
from pyftpdlib.handlers import FTPHandler
|
|
from pyftpdlib.servers import FTPServer
|
|
|
|
|
|
class GetHandler(BaseHTTPRequestHandler):
|
|
# Some of the http server code came from Dave Kennedy's AES shell
|
|
# over http - the server specific code
|
|
|
|
# should be performing GET requests Help from
|
|
# http://pymotw.com/2/BaseHTTPServer/
|
|
def do_GET(self):
|
|
|
|
# 404 since we aren't serving up any pages, only receiving data
|
|
self.send_response(404)
|
|
self.end_headers()
|
|
return
|
|
|
|
# handle post request
|
|
def do_POST(self):
|
|
|
|
# Gather the Posted URI from the agent/browser
|
|
# parsed_path = urlparse.urlparse(self.path)
|
|
uri_posted = self.path
|
|
uri_posted = uri_posted.replace("/", "")
|
|
#incoming_ip = self.client_address[0]
|
|
# current directory
|
|
exfil_directory = os.path.join(os.getcwd(), "data")
|
|
loot_path = exfil_directory + "/"
|
|
|
|
# Info for this from -
|
|
# http://stackoverflow.com/questions/13146064/simple-
|
|
# python-webserver-to-save-file
|
|
if uri_posted == "ccdata.php":
|
|
|
|
self.send_response(200)
|
|
self.end_headers()
|
|
loot_path = exfil_directory + "/"
|
|
|
|
# Check to make sure the agent directory exists, and a loot
|
|
# directory for the agent. If not, make them
|
|
if not os.path.isdir(loot_path):
|
|
os.makedirs(loot_path)
|
|
|
|
# Get the date info
|
|
current_date = time.strftime("%m/%d/%Y")
|
|
current_time = time.strftime("%H:%M:%S")
|
|
screenshot_name = current_date.replace("/", "") +\
|
|
"_" + current_time.replace(":", "") + "ccdata.txt"
|
|
|
|
# Read the length of the screenshot file being uploaded
|
|
screen_length = self.headers['content-length']
|
|
screen_data = self.rfile.read(int(screen_length))
|
|
|
|
# Write out the file
|
|
with open(loot_path + screenshot_name, 'w') as cc_data_file:
|
|
cc_data_file.write(screen_data)
|
|
|
|
elif uri_posted == "ssndata.php":
|
|
self.send_response(200)
|
|
self.end_headers()
|
|
|
|
# Check to make sure the agent directory exists, and a loot
|
|
# directory for the agent. If not, make them
|
|
if not os.path.isdir(loot_path):
|
|
os.makedirs(loot_path)
|
|
|
|
# Get the date info
|
|
current_date = time.strftime("%m/%d/%Y")
|
|
current_time = time.strftime("%H:%M:%S")
|
|
screenshot_name = current_date.replace("/", "") +\
|
|
"_" + current_time.replace(":", "") + "ssndata.txt"
|
|
|
|
# Read the length of the screenshot file being uploaded
|
|
screen_length = self.headers['content-length']
|
|
screen_data = self.rfile.read(int(screen_length))
|
|
|
|
# Write out the file
|
|
with open(loot_path + screenshot_name, 'w') as cc_data_file:
|
|
cc_data_file.write(screen_data)
|
|
|
|
# All other Post requests
|
|
else:
|
|
|
|
self.send_response(404)
|
|
self.end_headers()
|
|
|
|
print "Odd... someone else is trying to access this web server..."
|
|
print "Might want to check that out..."
|
|
return
|
|
|
|
|
|
class ThreadingHTTPServer(ThreadingMixIn, HTTPServer):
|
|
pass
|
|
|
|
|
|
def cli_parser():
|
|
# Command line argument parser
|
|
parser = argparse.ArgumentParser(
|
|
add_help=False,
|
|
description="The Egress-Assess is a tool used to assess egress filters\
|
|
protecting a network")
|
|
parser.add_argument(
|
|
'-h', '-?', '--h', '-help', '--help', action="store_true",
|
|
help=argparse.SUPPRESS)
|
|
|
|
protocols = parser.add_argument_group('Client Protocol Options')
|
|
protocols.add_argument(
|
|
"--ftp", default=False, action='store_true',
|
|
help="Extract data over FTP.")
|
|
protocols.add_argument("--http", default=False, action='store_true',
|
|
help="Extract data over http.")
|
|
protocols.add_argument("--https", default=False, action='store_true',
|
|
help="Extract data over https.")
|
|
protocols.add_argument("--ip", metavar="192.168.1.2", default=None,
|
|
help="IP to extract data to.")
|
|
|
|
servers = parser.add_argument_group('Server Protocol Options')
|
|
servers.add_argument(
|
|
"--ftp-server", default=False, action='store_true',
|
|
help="FTP Server that receives client data.")
|
|
servers.add_argument("--http-server", default=False, action='store_true',
|
|
help="HTTP and HTTPS server, receives POST data.")
|
|
|
|
ftp_options = parser.add_argument_group('FTP Options')
|
|
ftp_options.add_argument(
|
|
"--username", metavar="testuser", default=None,
|
|
help="Username for FTP server authentication.")
|
|
ftp_options.add_argument(
|
|
"--password", metavar="pass123", default=None,
|
|
help="Password for FTP server authentication.")
|
|
|
|
data_content = parser.add_argument_group('Data Content Options')
|
|
data_content.add_argument(
|
|
"--data-size", default=1, type=int,
|
|
help="Number of megs to send")
|
|
data_content.add_argument(
|
|
"--ssn", default=False, action='store_true',
|
|
help="Extract data containing fake social security numbers.")
|
|
data_content.add_argument(
|
|
'--cc', default=False, action='store_true',
|
|
help="Extract data containing fake credit card numbers")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.h:
|
|
parser.print_help()
|
|
sys.exit()
|
|
|
|
# If using FTP, check to make sure a username and pass is provided
|
|
if (args.ftp or args.ftp_server) and (
|
|
args.password is None or args.username is None):
|
|
print "[*] Error: FTP Server requires a username and password!"
|
|
print "[*] Error: Please re-run and provide the required info!"
|
|
sys.exit()
|
|
|
|
if (args.ftp or args.http or args.https) and args.ip is None:
|
|
print "[*] Error: You said to act like a client, but provided no ip"
|
|
print "[*] Error: to connect to. Please re-run with required info!"
|
|
sys.exit()
|
|
|
|
if (args.ftp or args.http or args.https) and (args.cc is False and args.ssn is False):
|
|
print "[*] Error: You need to tell Egress-Assess the type of data to send!"
|
|
print "[*] Error: to connect to. Please re-run with required info!"
|
|
sys.exit()
|
|
|
|
if not (
|
|
args.ftp or args.http or args.https or args.http_server or
|
|
args.ftp_server):
|
|
print "[*] Error: You didn't tell Egress-Assess to act like \
|
|
a server or client!".replace(' ', '')
|
|
print "[*] Error: Please re-run and provide an action to perform!\n\n"
|
|
parser.print_help()
|
|
sys.exit()
|
|
|
|
return args
|
|
|
|
|
|
def completed_number(prefix, length, the_generator):
|
|
"""
|
|
'prefix' is the start of the CC number as a string, any number of digits.
|
|
'length' is the length of the CC number to generate. Typically 13 or 16
|
|
"""
|
|
|
|
ccnumber = prefix
|
|
|
|
# generate digits
|
|
while len(ccnumber) < (length - 1):
|
|
digit = str(the_generator.choice(range(0, 10)))
|
|
ccnumber.append(digit)
|
|
|
|
# Calculate sum
|
|
sum = 0
|
|
pos = 0
|
|
|
|
reversedCCnumber = []
|
|
reversedCCnumber.extend(ccnumber)
|
|
reversedCCnumber.reverse()
|
|
|
|
while pos < length - 1:
|
|
odd = int(reversedCCnumber[pos]) * 2
|
|
if odd > 9:
|
|
odd -= 9
|
|
|
|
sum += odd
|
|
|
|
if pos != (length - 2):
|
|
sum += int(reversedCCnumber[pos + 1])
|
|
pos += 2
|
|
|
|
# Calculate check digit
|
|
checkdigit = ((sum / 10 + 1) * 10 - sum) % 10
|
|
ccnumber.append(str(checkdigit))
|
|
return ''.join(ccnumber)
|
|
|
|
|
|
def credit_card_number(prefixList, length, howMany):
|
|
|
|
generator = random.Random()
|
|
generator.seed()
|
|
|
|
result = []
|
|
|
|
while len(result) < howMany:
|
|
ccnumber = copy.copy(generator.choice(prefixList))
|
|
result.append(completed_number(ccnumber, length, generator))
|
|
|
|
return result
|
|
|
|
|
|
def ftp_client_connect(command_line_object):
|
|
# Create FTP objects that connects to the ftp server
|
|
# with the provided username and password
|
|
try:
|
|
ftp = FTP(command_line_object.ip)
|
|
except socket.gaierror:
|
|
print "[*] Error: Cannot connect to FTP server. Checking provided ip!"
|
|
sys.exit()
|
|
|
|
try:
|
|
ftp.login(command_line_object.username, command_line_object.password)
|
|
except error_perm:
|
|
print "[*] Error: Username or password is incorrect! Please re-run."
|
|
sys.exit()
|
|
|
|
# Create file to upload
|
|
if command_line_object.ssn:
|
|
# Get the date info
|
|
current_date = time.strftime("%m/%d/%Y")
|
|
current_time = time.strftime("%H:%M:%S")
|
|
ftp_file_name = current_date.replace("/", "") +\
|
|
"_" + current_time.replace(":", "") + "ssndata.txt"
|
|
|
|
# Generate 150000 SSNs for http(s) transfer
|
|
# This is about 1.9 megs
|
|
ssns = ''
|
|
for single_ssn in range(0, 81500 * command_line_object.data_size):
|
|
ssns += generate_ssn() + ', '
|
|
with open(os.getcwd() + "/" + ftp_file_name, 'w') as ssn_temp_file:
|
|
ssn_temp_file.write(ssns)
|
|
|
|
elif command_line_object.cc:
|
|
# Get the date info
|
|
current_date = time.strftime("%m/%d/%Y")
|
|
current_time = time.strftime("%H:%M:%S")
|
|
ftp_file_name = current_date.replace("/", "") +\
|
|
"_" + current_time.replace(":", "") + "ccdata.txt"
|
|
|
|
all_ccs = ''
|
|
credit_cards = generate_credit_cards(command_line_object)
|
|
for card in credit_cards:
|
|
all_ccs += card + ', '
|
|
with open(os.getcwd() + "/" + ftp_file_name, 'w') as cc_temp_file:
|
|
cc_temp_file.write(all_ccs)
|
|
|
|
ftp.storlines("STOR " + ftp_file_name, open(ftp_file_name))
|
|
ftp.quit()
|
|
os.remove(ftp_file_name)
|
|
print "[*] File sent!!!"
|
|
return
|
|
|
|
|
|
def ftp_server(command_line_object):
|
|
# current directory
|
|
exfil_directory = os.path.join(os.getcwd(), "data")
|
|
loot_path = exfil_directory + "/"
|
|
|
|
# Check to make sure the agent directory exists, and a loot
|
|
# directory for the agent. If not, make them
|
|
if not os.path.isdir(loot_path):
|
|
os.makedirs(loot_path)
|
|
|
|
try:
|
|
authorizer = DummyAuthorizer()
|
|
authorizer.add_user(
|
|
command_line_object.username, command_line_object.password,
|
|
loot_path, perm="lrw")
|
|
|
|
handler = FTPHandler
|
|
handler.authorizer = authorizer
|
|
|
|
# Define a customized banner (string returned when client connects)
|
|
handler.banner = "Connecting to Egress-Assess's FTP server!"
|
|
|
|
server = FTPServer(('', 21), handler)
|
|
server.serve_forever()
|
|
except ValueError:
|
|
print "[*] Error: The directory you provided may not exist!"
|
|
print "[*] Error: Please re-run with a valid FTP directory."
|
|
sys.exit()
|
|
|
|
|
|
def generate_credit_cards(command_object):
|
|
# Fake credit card generation code came from:
|
|
# https://github.com/grahamking/darkcoding-credit-card
|
|
|
|
# credit card constants
|
|
visaPrefixList = [
|
|
['4', '5', '3', '9'],
|
|
['4', '5', '5', '6'],
|
|
['4', '9', '1', '6'],
|
|
['4', '5', '3', '2'],
|
|
['4', '9', '2', '9'],
|
|
['4', '0', '2', '4', '0', '0', '7', '1'],
|
|
['4', '4', '8', '6'],
|
|
['4', '7', '1', '6'],
|
|
['4']]
|
|
mastercardPrefixList = [
|
|
['5', '1'], ['5', '2'], ['5', '3'], ['5', '4'], ['5', '5']]
|
|
amexPrefixList = [['3', '4'], ['3', '7']]
|
|
|
|
mastercards = credit_card_number(
|
|
mastercardPrefixList, 16, 19800 * command_object.data_size)
|
|
visas = credit_card_number(
|
|
visaPrefixList, 16, 19800 * command_object.data_size)
|
|
amexes = credit_card_number(
|
|
amexPrefixList, 15, 19800 * command_object.data_size)
|
|
|
|
all_cards = mastercards + visas + amexes
|
|
return all_cards
|
|
|
|
|
|
def generate_ssn():
|
|
ssn = randomNumbers(9)
|
|
ssn = ssn[0:3] + "-" + ssn[3:5] + "-" + ssn[5:9]
|
|
return ssn
|
|
|
|
|
|
def http_server():
|
|
Thread(target=serve_on_port, args=[443]).start()
|
|
return
|
|
|
|
|
|
def randomNumbers(b):
|
|
"""
|
|
Returns a random string/key of "b" characters in length, defaults to 5
|
|
"""
|
|
random_number = int(''.join(random.choice(string.digits) for x in range(b))
|
|
) + 10000
|
|
|
|
if random_number < 100000:
|
|
random_number = random_number + 100000
|
|
|
|
return str(random_number)
|
|
|
|
|
|
def serve_on_port(port):
|
|
if port == 443:
|
|
cert_path = os.getcwd() + '/server.pem'
|
|
server = ThreadingHTTPServer(("0.0.0.0", port), GetHandler)
|
|
server.socket = ssl.wrap_socket(
|
|
server.socket, certfile=cert_path, server_side=True)
|
|
server.serve_forever()
|
|
elif port == 80:
|
|
server80 = ThreadingHTTPServer(("0.0.0.0", port), GetHandler)
|
|
server80.serve_forever()
|
|
return
|
|
|
|
|
|
def title_screen():
|
|
os.system('clear')
|
|
print "################################################################################"
|
|
print "# Egress-Assess #"
|
|
print "################################################################################\n"
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
title_screen()
|
|
|
|
cli_parsed = cli_parser()
|
|
|
|
if cli_parsed.http or cli_parsed.https:
|
|
if cli_parsed.ssn:
|
|
# Generate 150000 SSNs for http(s) transfer
|
|
# This is about 1.9 megs
|
|
post_data = ''
|
|
for single_ssn in range(0, 81500 * cli_parsed.data_size):
|
|
post_data += generate_ssn() + ', '
|
|
if cli_parsed.https:
|
|
post_url = 'https://' + cli_parsed.ip + '/ssndata.php'
|
|
elif cli_parsed.http:
|
|
post_url = 'http://' + cli_parsed.ip + '/ssndata.php'
|
|
|
|
elif cli_parsed.cc:
|
|
# Generate about 1.8 megs of different credit cards
|
|
post_data = ''
|
|
credit_cards = generate_credit_cards(cli_parsed)
|
|
for card in credit_cards:
|
|
post_data += card + ', '
|
|
# Setup URL that data is sent to, then post it there
|
|
if cli_parsed.https:
|
|
post_url = 'https://' + cli_parsed.ip + '/ccdata.php'
|
|
elif cli_parsed.http:
|
|
post_url = 'http://' + cli_parsed.ip + '/ccdata.php'
|
|
|
|
try:
|
|
f = urllib2.urlopen(post_url, post_data)
|
|
f.close()
|
|
print "[*] File sent!!!"
|
|
except urllib2.URLError:
|
|
print "[*] Error: Web server may not be active on " + cli_parsed.ip
|
|
print "[*] Error: Please check server to make sure it is active!"
|
|
sys.exit()
|
|
|
|
elif cli_parsed.ftp:
|
|
ftp_client_connect(cli_parsed)
|
|
|
|
elif cli_parsed.http_server:
|
|
try:
|
|
print "[*] Starting web server..."
|
|
# bind to all interfaces
|
|
Thread(target=serve_on_port, args=[443]).start()
|
|
Thread(target=serve_on_port, args=[80]).start()
|
|
print "[*] Web server is currently running"
|
|
print "[*] Type \"killall -9 python\" to stop the web server."
|
|
# handle keyboard interrupts
|
|
except KeyboardInterrupt:
|
|
print "[!] Rage quiting, and stopping the web server!"
|
|
|
|
elif cli_parsed.ftp_server:
|
|
ftp_server(cli_parsed)
|