From 6477642b6e6260de2a49741ae848a93c70619cd5 Mon Sep 17 00:00:00 2001 From: Chris Truncer Date: Wed, 24 Dec 2014 08:37:31 -0500 Subject: [PATCH] Merged modular branch into master --- Egress-Assess.ps1 | 97 ++-- Egress-Assess.py | 501 ++---------------- README.md | 17 +- common/__init__.py | 0 common/helpers.py | 116 ++++ common/orchestra.py | 55 ++ datatypes/__init__.py | 0 datatypes/creditcards.py | 98 ++++ datatypes/socials.py | 28 + protocols/__init__.py | 0 protocols/clients/__init__.py | 0 protocols/clients/ftp_client.py | 50 ++ protocols/clients/http_client.py | 32 ++ protocols/clients/https_client.py | 32 ++ protocols/servers/__init__.py | 0 protocols/servers/ftp_server.py | 49 ++ protocols/servers/http_server.py | 32 ++ protocols/servers/https_server.py | 38 ++ protocols/servers/serverlibs/__init__.py | 0 protocols/servers/serverlibs/base_handler.py | 67 +++ protocols/servers/serverlibs/threaded_http.py | 6 + setup/setup.sh | 5 +- 22 files changed, 717 insertions(+), 506 deletions(-) mode change 100644 => 100755 Egress-Assess.ps1 create mode 100644 common/__init__.py create mode 100644 common/helpers.py create mode 100644 common/orchestra.py create mode 100644 datatypes/__init__.py create mode 100644 datatypes/creditcards.py create mode 100644 datatypes/socials.py create mode 100644 protocols/__init__.py create mode 100644 protocols/clients/__init__.py create mode 100644 protocols/clients/ftp_client.py create mode 100644 protocols/clients/http_client.py create mode 100644 protocols/clients/https_client.py create mode 100644 protocols/servers/__init__.py create mode 100644 protocols/servers/ftp_server.py create mode 100644 protocols/servers/http_server.py create mode 100644 protocols/servers/https_server.py create mode 100644 protocols/servers/serverlibs/__init__.py create mode 100644 protocols/servers/serverlibs/base_handler.py create mode 100644 protocols/servers/serverlibs/threaded_http.py diff --git a/Egress-Assess.ps1 b/Egress-Assess.ps1 old mode 100644 new mode 100755 index 5802d3c..003c8e7 --- a/Egress-Assess.ps1 +++ b/Egress-Assess.ps1 @@ -1,6 +1,4 @@ - - -function Egress-Assess { +function Invoke-EgressAssess { <# .Synopsis @@ -11,58 +9,50 @@ function Egress-Assess { Due to processing overhead in Powershell, numbers are created in batches of 5,000. Reference: http://powershell.org/wp/2013/09/16/powershell-performance-the-operator-and-when-to-avoid-it/ -.Parameter HTTP - The switch to enable transfer over http - -.Parameter HTTPS - The switch to enable transfer over https - -.Parameter FTP - The switch to enable transer over ftp +.Parameter Client + The string containing the protocol to egress data over .Parameter IP The string containing the IP or hostname of the egress assess server. +.Parameter Proxy + This switch is used when you need to exfiltrate data using the system proxy. + .Parameter Username The username for the ftp server .Parameter Password The password for the ftp server -.Parameter CC - Enable this switch if you want to send credit card data - -.Parameter SSN - Enable this switch if you want to send social securit numbers +.Parameter Datatype + The string containing the data you want to generate and exfil .Parameter Size How many blocks of 5000 numbers to generate .Example Import-Module Egress-Assess.ps1 - Egress-Assess -http -ip 127.0.0.1 -CC -Size 1 -Verbose + Invoke-EgressAssess -client http -ip 127.0.0.1 -datatype cc -Size 1 -Verbose -Script created by @rvrsh3ll +Script created by @rvrsh3ll @christruncer @harmj0y @sixdub https://www.rvrsh3ll.net -http://www.rvrsh3ll.net/blog/ - - -Thanks to @christruncer for the project and @harmjoy for the powershell help! https://www.christophertruncer.com/ http://blog.harmj0y.net/ +http://sixdub.net/ + #> [CmdletBinding()] Param ( - [switch]$HTTP, - [switch]$HTTPS, - [switch]$FTP, + [Parameter(Mandatory=$True)] + [string]$CLIENT, [Parameter(Mandatory=$True)] [string]$IP, + [switch]$Proxy, + [Parameter(Mandatory=$True)] + [string]$Datatype, [string]$Username, [string]$Password, - [switch]$CC, - [switch]$SSN, [int]$Size=1 ) @@ -133,26 +123,26 @@ begin { # check for cc or ssn and pass to body - if ($CC) { + if ($DATATYPE -eq "cc") { Generate-CreditCards $Body = @() $Body = $allCC - if ($http){ - $url = "http://" + $IP + "/ccdata.php" + if ($client -eq "http"){ + $url = "http://" + $IP + "/post_data.php" } - elseif ($https){ - $url = "https://" + $IP + "/ccdata.php" + elseif ($client -eq "https") { + $url = "https://" + $IP + "/post_data.php" } } - elseif ($SSN){ + elseif ($DATATYPE -eq "ssn"){ Generate-SSN $Body = @() $Body = $allSSN - if ($http){ - $url = "http://" + $IP + "/ssndata.php" + if ($client -eq "http"){ + $url = "http://" + $IP + "/post_data.php" } - elseif ($https){ - $url = "https://" + $IP + "/ssndata.php" + elseif ($client -eq "https"){ + $url = "https://" + $IP + "/post_data.php" } } else { @@ -163,6 +153,11 @@ begin { [Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} $uri = New-Object -TypeName System.Uri -ArgumentList $url $wc = New-Object -TypeName System.Net.WebClient + if ($proxy) { + $proxy = [System.Net.WebRequest]::GetSystemWebProxy() + $proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials + $wc.proxy = $proxy + } Write-Verbose "Uploading data.." $wc.UploadString($uri, $Body) Write-Verbose "Transaction Complete." @@ -170,32 +165,40 @@ begin { function Use-Ftp { - if ($CC) { + $Date = Get-Date -Format Mdyyyy_hhmmss + $Path = "ftpdata" + $Date + ".txt" + + if ($DATATYPE -eq "cc") { Generate-CreditCards - out-file -filepath ftpdata.txt -inputobject $allCC -encoding ASCII + $FTPData = $allCC } - elseif ($SSN){ + elseif ($DATATYPE -eq "ssn"){ Generate-SSN - out-file -filepath ftpdata.txt -inputobject $allSSN -encoding ASCII + $FTPData=$allSSN } else { Write-Verbose "You did not provide a data type to generate." } - $Path = "ftpdata.txt" - $Destination = "ftp://" + $IP + "/ftpdata.txt" + $Destination = "ftp://" + $IP + "/" + $Path $Credential = New-Object -TypeName System.Net.NetworkCredential -ArgumentList $Username,$Password # Create the FTP request and upload the file $FtpRequest = [System.Net.FtpWebRequest][System.Net.WebRequest]::Create($Destination) + if ($proxy) { + $proxy = [System.Net.WebRequest]::GetSystemWebProxy() + $proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials + $FtpRequest.proxy = $proxy + } + $FtpRequest.KeepAlive = $False $FtpRequest.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile $FtpRequest.Credentials = $Credential # Get the request stream, and write the file bytes to the stream + $Encoder = [system.Text.Encoding]::UTF8 $RequestStream = $FtpRequest.GetRequestStream() - Get-Content -Path $Path -Encoding Byte | % { $RequestStream.WriteByte($_); } + $Encoder.GetBytes($FTPData) | % { $RequestStream.WriteByte($_); } $RequestStream.Close() - Remove-Item $Path Write-Verbose "File Transfer Complete." } @@ -203,11 +206,11 @@ begin { } process { - if ($http -or $https) { + if ($client -eq "http" -or $client -eq "https") { Use-HTTP } - elseif ($ftp) { + elseif ($client -eq "ftp") { Use-Ftp } else { diff --git a/Egress-Assess.py b/Egress-Assess.py index 21bd5fa..dfdd04c 100755 --- a/Egress-Assess.py +++ b/Egress-Assess.py @@ -6,461 +6,70 @@ # 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" +from common import helpers +from common import orchestra if __name__ == "__main__": - title_screen() + helpers.title_screen() - cli_parsed = cli_parser() + cli_parsed = helpers.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' + the_conductor = orchestra.Conductor() - 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' + # Check if only listing supported server/client protocols or datatypes + if cli_parsed.list_servers: + print "[*] Supported server protocols: \n" + the_conductor.load_server_protocols(cli_parsed) + for name, server_module in the_conductor.server_protocols.iteritems(): + print "[+] " + server_module.protocol + print + sys.exit() - 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.list_clients: + print "[*] Supported client protocols: \n" + the_conductor.load_client_protocols(cli_parsed) + for name, client_module in the_conductor.client_protocols.iteritems(): + print "[+] " + client_module.protocol + print + sys.exit() - elif cli_parsed.ftp: - ftp_client_connect(cli_parsed) + elif cli_parsed.list_datatypes: + print "[*] Supported data types: \n" + the_conductor.load_datatypes(cli_parsed) + for name, datatype_module in the_conductor.datatypes.iteritems(): + print "[+] " + datatype_module.cli + " - (" +\ + datatype_module.description + ")" + print + sys.exit() - 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!" + if cli_parsed.server is not None: + the_conductor.load_server_protocols(cli_parsed) - elif cli_parsed.ftp_server: - ftp_server(cli_parsed) + for full_path, server in the_conductor.server_protocols.iteritems(): + + if server.protocol == cli_parsed.server.lower(): + server.serve() + + elif cli_parsed.client is not None: + # load up all supported client protocols and datatypes + the_conductor.load_client_protocols(cli_parsed) + the_conductor.load_datatypes(cli_parsed) + + # Loop through and find the requested datatype + for name, datatype_module in the_conductor.datatypes.iteritems(): + if datatype_module.cli == cli_parsed.datatype.lower(): + generated_data = datatype_module.generate_data() + + # Once data has been generated, transmit it using the + # protocol requested by the user + for proto_name, proto_module in the_conductor.client_protocols.iteritems(): + if proto_module.protocol == cli_parsed.client.lower(): + proto_module.transmit(generated_data) + sys.exit() + + print "[*] Error: You either didn't provide a valid datatype or client protocol to use." + print "[*] Error: Re-run and use --list-datatypes or --list-clients to see possible options." + sys.exit() diff --git a/README.md b/README.md index f63393a..f81e8ab 100644 --- a/README.md +++ b/README.md @@ -24,25 +24,18 @@ Blog posts are available here: Typical use case for Egress-Assess is to copy this tool in two locations. One location will act as the server, the other will act as the client. Egress-Assess can send data over FTP, HTTP, and HTTPS. -To extract data over FTP, you would first start Egress-Assess’s FTP server by selecting “–ftp-server” and providing a username and password to use: +To extract data over FTP, you would first start Egress-Assess’s FTP server by selecting “--server ftp” and providing a username and password to use: -./Egress-Assess.py –ftp-server –username testuser –password pass123 +./Egress-Assess.py --server ftp --username testuser --password pass123 Now, to have the client connect and send data to the ftp server, you could run... -./Egress-Assess.py --ftp --username testuser --password pass123 --ip 192.168.63.149 --ssn - +./Egress-Assess.py --client ftp --username testuser --password pass123 --ip 192.168.63.149 --datatype ssn Also, you can setup Egress-Assess to act as a web server by running.... -./Egress-Assess.py --http-server +./Egress-Assess.py --server https Then, to send data to the FTP server, and to specifically send 15 megs of credit card data, run the following command... -./Egress-Assess.py –http –data-size 15 –ip 192.168.63.149 –cc - - -Upcoming Changes -================ - -1. Make Egress-Assess modular so users can easily extend the tool to support additional frameworks and data types. +./Egress-Assess.py --client https --data-size 15 --ip 192.168.63.149 --datatype cc diff --git a/common/__init__.py b/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/common/helpers.py b/common/helpers.py new file mode 100644 index 0000000..f8ed167 --- /dev/null +++ b/common/helpers.py @@ -0,0 +1,116 @@ +''' + +This is for functions potentially used by all modules + +''' + +import argparse +import os +import random +import string +import sys + + +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( + "--client", default=None, metavar="[http]", + help="Extract data over the specified protocol.") + protocols.add_argument( + "--list-clients", default=False, action='store_true', + help="List all supported client protocols.") + 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( + "--server", default=None, metavar='[http]', + help="Create a server for the specified protocol.") + servers.add_argument("--list-servers", default=False, action='store_true', + help="Lists all supported server protocols.") + + 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( + "--datatype", default=None, metavar='[ssn]', + help="Extract data containing fake social security numbers.") + data_content.add_argument( + "--data-size", default=1, type=int, + help="Number of megs to send") + data_content.add_argument( + "--list-datatypes", default=False, action='store_true', + help="List all data types that can be generated by the framework.") + + args = parser.parse_args() + + if args.h: + parser.print_help() + sys.exit() + + if (args.server == "ftp" or args.client == "ftp") and ( + args.username is None or args.password is None): + print "[*] Error: FTP connections require a username and password!" + print "[*] Error: Please re-run and provide the required info!" + sys.exit() + + if args.client 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.client is not None) and (args.datatype is None): + 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 (args.client is None and args.server is None and + args.list_servers is None and args.list_clients is None and + args.list_datatypes is None): + 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!" + parser.print_help() + sys.exit() + + return args + + +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 title_screen(): + os.system('clear') + print "################################################################################" + print "# Egress-Assess #" + print "################################################################################\n" + return + + +def ea_path(): + return os.getcwd() diff --git a/common/orchestra.py b/common/orchestra.py new file mode 100644 index 0000000..10b9f49 --- /dev/null +++ b/common/orchestra.py @@ -0,0 +1,55 @@ +''' + +This is the conductor which controls everything + +''' + +import glob +import imp +from protocols.servers import * +from protocols.clients import * +from datatypes import * + + +class Conductor: + + def __init__(self): + # Create dictionaries of supported modules + # empty until stuff loaded into them + self.client_protocols = {} + self.server_protocols = {} + self.datatypes = {} + + def load_client_protocols(self, command_line_object): + for name in glob.glob('protocols/clients/*.py'): + if name.endswith("__init__.py"): + pass + elif name.endswith(".pyc"): + pass + else: + loaded_client_proto = imp.load_source(name.replace("/", ".").rstrip('.py'), name) + self.client_protocols[name] = loaded_client_proto.Client(command_line_object) + return + + + def load_server_protocols(self, command_line_object): + for name in glob.glob('protocols/servers/*.py'): + if name.endswith("__init__.py"): + pass + elif name.endswith(".pyc"): + pass + else: + loaded_server_proto = imp.load_source(name.replace("/", ".").rstrip('.py'), name) + self.server_protocols[name] = loaded_server_proto.Server(command_line_object) + return + + def load_datatypes(self, command_line_object): + for name in glob.glob('datatypes/*.py'): + if name.endswith("__init__.py"): + pass + elif name.endswith(".pyc"): + pass + else: + loaded_datatypes = imp.load_source(name.replace("/", ".").rstrip('.py'), name) + self.datatypes[name] = loaded_datatypes.Datatype(command_line_object) + return diff --git a/datatypes/__init__.py b/datatypes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/datatypes/creditcards.py b/datatypes/creditcards.py new file mode 100644 index 0000000..644c806 --- /dev/null +++ b/datatypes/creditcards.py @@ -0,0 +1,98 @@ +''' + +This module generates credit card data + +''' + +import copy +import random + + +class Datatype: + + def __init__(self, cli_object): + self.cli = "cc" + self.description = "Credit Card Numbers" + self.filetype = "text" + self.datasize = int(cli_object.data_size) + + def completed_number(self, 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(self, prefixList, length, howMany): + + generator = random.Random() + generator.seed() + + result = [] + + while len(result) < howMany: + ccnumber = copy.copy(generator.choice(prefixList)) + result.append(self.completed_number(ccnumber, length, generator)) + + return result + + def generate_data(self): + # 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 = self.credit_card_number( + mastercardPrefixList, 16, 19800 * self.datasize) + visas = self.credit_card_number( + visaPrefixList, 16, 19800 * self.datasize) + amexes = self.credit_card_number( + amexPrefixList, 15, 19800 * self.datasize) + + all_cards = mastercards + visas + amexes + final_cards = '' + + for card in all_cards: + final_cards += card + ', ' + + return final_cards diff --git a/datatypes/socials.py b/datatypes/socials.py new file mode 100644 index 0000000..09524b8 --- /dev/null +++ b/datatypes/socials.py @@ -0,0 +1,28 @@ +''' + +This module generates social security numbers + +''' + +from common import helpers + + +class Datatype: + + def __init__(self, cli_object): + self.cli = "ssn" + self.description = "Social Security Numbers" + self.filetype = "text" + self.datasize = int(cli_object.data_size) + + def create_ssn(self): + ssn = helpers.randomNumbers(9) + ssn = ssn[0:3] + "-" + ssn[3:5] + "-" + ssn[5:9] + return ssn + + def generate_data(self): + ssns = '' + # This is approx 1 meg of socials + for single_ssn in range(0, 81500 * self.datasize): + ssns += self.create_ssn() + ', ' + return ssns diff --git a/protocols/__init__.py b/protocols/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/protocols/clients/__init__.py b/protocols/clients/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/protocols/clients/ftp_client.py b/protocols/clients/ftp_client.py new file mode 100644 index 0000000..6a59b08 --- /dev/null +++ b/protocols/clients/ftp_client.py @@ -0,0 +1,50 @@ +''' + +This is the ftp client code + +''' + +import os +import socket +import sys +import time +from common import helpers +from ftplib import FTP +from ftplib import error_perm + + +class Client: + + def __init__(self, cli_object): + self.protocol = "ftp" + self.remote_server = cli_object.ip + self.username = cli_object.username + self.password = cli_object.password + + def transmit(self, data_to_transmit): + + try: + ftp = FTP(self.remote_server) + except socket.gaierror: + print "[*] Error: Cannot connect to FTP server. Checking provided ip!" + sys.exit() + + try: + ftp.login(self.username, self.password) + except error_perm: + print "[*] Error: Username or password is incorrect! Please re-run." + sys.exit() + + # Create file name and write out file for ftp transfer + current_date = time.strftime("%m/%d/%Y") + current_time = time.strftime("%H:%M:%S") + ftp_file_name = current_date.replace("/", "") +\ + "_" + current_time.replace(":", "") + "ftp_data.txt" + + with open(helpers.ea_path() + "/" + ftp_file_name, 'w') as cc_temp_file: + cc_temp_file.write(data_to_transmit) + + ftp.storlines("STOR " + ftp_file_name, open(helpers.ea_path() + "/" + ftp_file_name)) + ftp.quit() + os.remove(helpers.ea_path() + "/" + ftp_file_name) + print "[*] File sent!!!" diff --git a/protocols/clients/http_client.py b/protocols/clients/http_client.py new file mode 100644 index 0000000..4eeddc3 --- /dev/null +++ b/protocols/clients/http_client.py @@ -0,0 +1,32 @@ +''' + +This is the web client code + +''' + +import sys +import urllib2 + + +class Client: + + def __init__(self, cli_object): + # Really http and https + self.data_to_transmit = '' + self.remote_server = cli_object.ip + self.protocol = "http" + + def transmit(self, data_to_transmit): + # Create the url to post to + url = "http://" + self.remote_server + "/post_data.php" + + # Post the data to the web server at the specified URL + try: + f = urllib2.urlopen(url, data_to_transmit) + f.close() + print "[*] File sent!!!" + except urllib2.URLError: + print "[*] Error: Web server may not be active on " + self.remote_server + print "[*] Error: Please check server to make sure it is active!" + sys.exit() + return diff --git a/protocols/clients/https_client.py b/protocols/clients/https_client.py new file mode 100644 index 0000000..672dbc6 --- /dev/null +++ b/protocols/clients/https_client.py @@ -0,0 +1,32 @@ +''' + +This is the web client code + +''' + +import sys +import urllib2 + + +class Client: + + def __init__(self, cli_object): + # Really http and https + self.data_to_transmit = '' + self.remote_server = cli_object.ip + self.protocol = "https" + + def transmit(self, data_to_transmit): + # Create the url to post to + url = "https://" + self.remote_server + "/post_data.php" + + # Post the data to the web server at the specified URL + try: + f = urllib2.urlopen(url, data_to_transmit) + f.close() + print "[*] File sent!!!" + except urllib2.URLError: + print "[*] Error: Web server may not be active on " + self.remote_server + print "[*] Error: Please check server to make sure it is active!" + sys.exit() + return diff --git a/protocols/servers/__init__.py b/protocols/servers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/protocols/servers/ftp_server.py b/protocols/servers/ftp_server.py new file mode 100644 index 0000000..1f3d9bf --- /dev/null +++ b/protocols/servers/ftp_server.py @@ -0,0 +1,49 @@ +''' + +This is the code for the ftp server + +''' + +import os +from pyftpdlib.authorizers import DummyAuthorizer +from pyftpdlib.handlers import FTPHandler +from pyftpdlib.servers import FTPServer + + +class Server: + + def __init__(self, cli_object): + self.protocol = "ftp" + self.username = cli_object.username + self.password = cli_object.password + self.data_directory = "" + + def serve(self): + # 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( + self.username, self.password, + loot_path, perm="elradfmwM") + + 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() + return diff --git a/protocols/servers/http_server.py b/protocols/servers/http_server.py new file mode 100644 index 0000000..e074eee --- /dev/null +++ b/protocols/servers/http_server.py @@ -0,0 +1,32 @@ +''' + +This is the code for the web server + +''' + +from protocols.servers.serverlibs import base_handler +from protocols.servers.serverlibs import threaded_http +from threading import Thread + + +class Server: + + def __init__(self, cli_object): + self.protocol = "http" + + def serve(self): + try: + print "[*] Starting web (http) server..." + # bind to all interfaces + Thread(target=self.serve_on_port).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!" + + def serve_on_port(self): + server80 = threaded_http.ThreadingHTTPServer( + ("0.0.0.0", 80), base_handler.GetHandler) + server80.serve_forever() + return diff --git a/protocols/servers/https_server.py b/protocols/servers/https_server.py new file mode 100644 index 0000000..05cce6e --- /dev/null +++ b/protocols/servers/https_server.py @@ -0,0 +1,38 @@ +''' + +This is the code for the web server + +''' + +import ssl +from common import helpers +from protocols.servers.serverlibs import base_handler +from protocols.servers.serverlibs import threaded_http +from threading import Thread + + +class Server: + + def __init__(self, cli_object): + self.protocol = "https" + + def serve(self): + try: + print "[*] Starting web (https) server..." + # bind to all interfaces + Thread(target=self.serve_on_port).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!" + + def serve_on_port(self): + cert_path = helpers.ea_path() +\ + '/protocols/servers/serverlibs/server.pem' + server = threaded_http.ThreadingHTTPServer( + ("0.0.0.0", 443), base_handler.GetHandler) + server.socket = ssl.wrap_socket( + server.socket, certfile=cert_path, server_side=True) + server.serve_forever() + return diff --git a/protocols/servers/serverlibs/__init__.py b/protocols/servers/serverlibs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/protocols/servers/serverlibs/base_handler.py b/protocols/servers/serverlibs/base_handler.py new file mode 100644 index 0000000..2536637 --- /dev/null +++ b/protocols/servers/serverlibs/base_handler.py @@ -0,0 +1,67 @@ +import os +import time +from BaseHTTPServer import BaseHTTPRequestHandler +from common import helpers + + +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(helpers.ea_path(), "data") + loot_path = exfil_directory + "/" + + # Info for this from - + # http://stackoverflow.com/questions/13146064/simple- + # python-webserver-to-save-file + if uri_posted == "post_data.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(":", "") + "web_data.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 diff --git a/protocols/servers/serverlibs/threaded_http.py b/protocols/servers/serverlibs/threaded_http.py new file mode 100644 index 0000000..44f3d7b --- /dev/null +++ b/protocols/servers/serverlibs/threaded_http.py @@ -0,0 +1,6 @@ +from BaseHTTPServer import HTTPServer +from SocketServer import ThreadingMixIn + + +class ThreadingHTTPServer(ThreadingMixIn, HTTPServer): + pass diff --git a/setup/setup.sh b/setup/setup.sh index 6794cc5..28f540b 100755 --- a/setup/setup.sh +++ b/setup/setup.sh @@ -1,15 +1,18 @@ #!/bin/bash clear +echo "[*] Installing Egress-Assess Dependencies..." echo "[*] Installing pyftpdlib..." git clone https://github.com/giampaolo/pyftpdlib.git cd pyftpdlib python setup.py install cd .. rm -rf pyftpdlib -cd .. +cd ../protocols/servers/serverlibs clear echo "[*] Generating SSL Certificate" openssl req -new -x509 -keyout server.pem -out server.pem -days 365 -nodes echo +echo echo "[*] Install complete!" +echo "[*] Enjoy Egress-Assess!"