Skip to content

Commit

Permalink
Fixes to prevent arbitrary file system traversal
Browse files Browse the repository at this point in the history
  • Loading branch information
Antony Saba committed Oct 11, 2017
1 parent 4069f4c commit a4bf869
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 74 deletions.
3 changes: 3 additions & 0 deletions fakenet/configs/default.ini
Expand Up @@ -163,6 +163,8 @@ BlackListPortsUDP: 67, 68, 137, 138, 443, 1900, 5355
# * Webroot - Set webroot path for HTTPListener.
# * DumpHTTPPosts - Store HTTP Post requests for the HTTPListener.
# * DumpHTTPPostsFilePrefix - File prefix for the stored HTTP Post requests used by the HTTPListener.
# * BITSFilePrefix - File prefix for the stored BITS uploads used by the BITSListener.
# * TFTPFilePrefix - File prefix for the stored tftp uploads used by the TFTPListener.
# * DNSResponse - IP address to respond with for A record DNS queries. (DNSListener)
# * NXDomains - A number of DNS requests to ignore to let the malware cycle through
# all of the backup C2 servers. (DNSListener)
Expand Down Expand Up @@ -315,6 +317,7 @@ Protocol: UDP
Listener: TFTPListener
TFTPRoot: defaultFiles/
Hidden: False
TFTPFilePrefix: tftp

[POPServer]
Enabled: True
Expand Down
66 changes: 25 additions & 41 deletions fakenet/listeners/BITSListener.py
Expand Up @@ -13,13 +13,16 @@
import socket

import posixpath
import mimetypes

import time

import urllib

from BaseHTTPServer import HTTPServer
from SimpleHTTPServer import SimpleHTTPRequestHandler

import fakenet.listeners


# BITS Protocol header keys
K_BITS_SESSION_ID = 'BITS-Session-Id'
Expand Down Expand Up @@ -350,8 +353,8 @@ def _handle_create_session(self):
# case mutual supported protocol is found
if protocols_intersection:
headers[K_BITS_PROTOCOL] = list(protocols_intersection)[0]
requested_path = self.path[1:] if self.path.startswith("/") else self.path
absolute_file_path = os.path.join(self.server.config.get('webroot','defaultFiles'), requested_path)
safe_path = self.server.bits_file_prefix + '_' + urllib.quote(self.path, '')
absolute_file_path = fakenet.listeners.safe_join(os.getcwd(), safe_path)

session_id = self.__get_current_session_id()
self.server.logger.info("Creating BITS-Session-Id: %s", session_id)
Expand Down Expand Up @@ -416,14 +419,7 @@ def do_BITS_POST(self):
repr(e.internal_exception))
self.__send_response(headers, status_code = status_code)

class HTTPListener():

if not mimetypes.inited:
mimetypes.init() # try to read system mime.types
extensions_map = mimetypes.types_map.copy()
extensions_map.update({
'': 'text/html', # Default
})
class BITSListener():

def __init__(self, config={}, name='BITSListener',
logging_level=logging.DEBUG, running_listeners=None):
Expand All @@ -444,46 +440,28 @@ def __init__(self, config={}, name='BITSListener',
for key, value in config.iteritems():
self.logger.debug(' %10s: %s', key, value)

# Initialize webroot directory
self.webroot_path = self.config.get('webroot','defaultFiles')

# Try absolute path first
if not os.path.exists(self.webroot_path):

# Try to locate the webroot directory relative to application path
self.webroot_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), self.webroot_path)

if not os.path.exists(self.webroot_path):
self.logger.error('Could not locate webroot directory: %s', self.webroot_path)
sys.exit(1)
self.bits_file_prefix = self.config.get('bitsfileprefix', 'bits')

def start(self):
self.logger.debug('Starting...')

self.server = ThreadedHTTPServer((self.local_ip, int(self.config.get('port'))), SimpleBITSRequestHandler)
self.server.logger = self.logger
self.server.bits_file_prefix = self.bits_file_prefix
self.server.config = self.config
self.server.webroot_path = self.webroot_path
self.server.extensions_map = self.extensions_map

if self.config.get('usessl') == 'Yes':
self.logger.debug('Using SSL socket.')

keyfile_path = 'privkey.pem'
if not os.path.exists(keyfile_path):
keyfile_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), keyfile_path)

if not os.path.exists(keyfile_path):
self.logger.error('Could not locate privkey.pem')
sys.exit(1)

certfile_path = 'server.pem'
if not os.path.exists(certfile_path):
certfile_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), certfile_path)
keyfile_path = fakenet.listeners.abs_config_path('privkey.pem')
if keyfile_path is None:
self.logger.error('Could not locate privkey.pem')
sys.exit(1)

if not os.path.exists(certfile_path):
self.logger.error('Could not locate certfile.pem')
sys.exit(1)
certfile_path = fakenet.listeners.abs_config_path('server.pem')
if certfile_path is None:
self.logger.error('Could not locate certfile.pem')
sys.exit(1)

self.server.socket = ssl.wrap_socket(self.server.socket, keyfile=keyfile_path, certfile=certfile_path, server_side=True, ciphers='RSA')

Expand All @@ -501,11 +479,17 @@ def test(config):
pass

def main():
"""
Run from the flare-fakenet-ng root dir with the following command:
python2 -m fakenet.listeners.BITSListener
"""
logging.basicConfig(format='%(asctime)s [%(name)15s] %(message)s', datefmt='%m/%d/%y %I:%M:%S %p', level=logging.DEBUG)

config = {'port': '80', 'usessl': 'No', 'webroot': '../defaultFiles' }
config = {'port': '80', 'usessl': 'No' }

listener = HTTPListener(config)
listener = BITSListener(config)
listener.start()

###########################################################################
Expand Down
51 changes: 25 additions & 26 deletions fakenet/listeners/HTTPListener.py
Expand Up @@ -15,6 +15,7 @@

import time

import fakenet.listeners

MIME_FILE_RESPONSE = {
'text/html': 'FakeNet.html',
Expand Down Expand Up @@ -68,24 +69,19 @@ def __init__(
self.name = 'HTTP'
self.port = self.config.get('port', 80)

self.logger.info('Starting...')
self.logger.info('Initializing...')

self.logger.debug('Initialized with config:')
for key, value in config.iteritems():
self.logger.debug(' %10s: %s', key, value)

# Initialize webroot directory
self.webroot_path = self.config.get('webroot','defaultFiles')

# Try absolute path first
if not os.path.exists(self.webroot_path):
path = self.config.get('webroot','defaultFiles')
self.webroot_path = fakenet.listeners.abs_config_path(path)
if self.webroot_path is None:
self.logger.error('Could not locate webroot directory: %s', path)
sys.exit(1)

# Try to locate the webroot directory relative to application path
self.webroot_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), self.webroot_path)

if not os.path.exists(self.webroot_path):
self.logger.error('Could not locate webroot directory: %s', self.webroot_path)
sys.exit(1)

def start(self):
self.logger.debug('Starting...')
Expand All @@ -101,26 +97,22 @@ def start(self):
self.logger.debug('Using SSL socket.')

keyfile_path = 'listeners/ssl_utils/privkey.pem'
if not os.path.exists(keyfile_path):
keyfile_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), keyfile_path)

if not os.path.exists(keyfile_path):
self.logger.error('Could not locate privkey.pem')
sys.exit(1)
keyfile_path = fakenet.listeners.abs_config_path(keyfile_path)
if keyfile_path is None:
self.logger.error('Could not locate %s', keyfile_path)
sys.exit(1)

certfile_path = 'listeners/ssl_utils/server.pem'
if not os.path.exists(certfile_path):
certfile_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), certfile_path)

if not os.path.exists(certfile_path):
self.logger.error('Could not locate certfile.pem')
sys.exit(1)
certfile_path = fakenet.listeners.abs_config_path(certfile_path)
if certfile_path is None:
self.logger.error('Could not locate %s', certfile_path)
sys.exit(1)

self.server.socket = ssl.wrap_socket(self.server.socket, keyfile=keyfile_path, certfile=certfile_path, server_side=True, ciphers='RSA')

self.server_thread = threading.Thread(target=self.server.serve_forever)
self.server_thread.daemon = True
self.server_thread.start()
self.server_thread.start()

def stop(self):
self.logger.info('Stopping...')
Expand Down Expand Up @@ -236,7 +228,8 @@ def get_response(self, path):
_, ext = posixpath.splitext(path)
response_type = self.server.extensions_map.get(ext, 'text/html')

response_filename = os.path.join(self.server.webroot_path, path[1:])
# Do after checking for trailing '/' since normpath removes it
response_filename = fakenet.listeners.safe_join(self.server.webroot_path, path)

# Check the requested path exists
if not os.path.exists(response_filename):
Expand Down Expand Up @@ -293,9 +286,15 @@ def test(config):
print '-'*80

def main():
"""
Run from the flare-fakenet-ng root dir with the following command:
python2 -m fakenet.listeners.HTTPListener
"""
logging.basicConfig(format='%(asctime)s [%(name)15s] %(message)s', datefmt='%m/%d/%y %I:%M:%S %p', level=logging.DEBUG)

config = {'port': '80', 'usessl': 'No', 'webroot': '../defaultFiles' }
config = {'port': '8443', 'usessl': 'Yes', 'webroot': 'fakenet/defaultFiles' }

listener = HTTPListener(config)
listener.start()
Expand Down
33 changes: 27 additions & 6 deletions fakenet/listeners/TFTPListener.py
Expand Up @@ -9,6 +9,9 @@
import socket
import struct

import urllib
import fakenet.listeners

EXT_FILE_RESPONSE = {
'.html': 'FakeNet.html',
'.png' : 'FakeNet.png',
Expand Down Expand Up @@ -92,7 +95,14 @@ def start(self):

self.server.logger = self.logger
self.server.config = self.config
self.server.tftproot_path = self.config.get('tftproot', 'defaultFiles')

path = self.config.get('tftproot', 'defaultFiles')
self.server.tftproot_path = fakenet.listeners.abs_config_path(path)
if self.server.tftproot_path is None:
self.logger.error('Could not locate tftproot directory: %s', path)
sys.exit(1)

self.server.bits_file_prefix = self.config.get('tftpfileprefix', 'tftp')

self.server_thread = threading.Thread(target=self.server.serve_forever)
self.server_thread.daemon = True
Expand Down Expand Up @@ -141,7 +151,10 @@ def handle(self):

if hasattr(self.server, 'filename_path') and self.server.filename_path:

f = open(self.server.filename_path, 'ab')
safe_file = self.server.tftp_file_prefix + "_" + urllib.quote(self.server.filename_path, '')
output_file = fakenet.listeners.safe_join(os.getcwd(),
safe_file)
f = open(output_file, 'ab')
f.write(data[4:])
f.close()

Expand Down Expand Up @@ -169,15 +182,17 @@ def handle(self):

def handle_rrq(self, socket, filename):

filename_path = os.path.join(self.server.tftproot_path, filename)
filename_path = fakenet.listeners.safe_join(self.server.tftproot_path,
filename)

# If virtual filename does not exist return a default file based on extention
if not os.path.isfile(filename_path):

file_basename, file_extension = os.path.splitext(filename)

# Calculate absolute path to a fake file
filename_path = os.path.join(self.server.tftproot_path, EXT_FILE_RESPONSE.get(file_extension.lower(), u'FakeNetMini.exe'))
filename_path = fakenet.listeners.safe_join(self.server.tftproot_path,
EXT_FILE_RESPONSE.get(file_extension.lower(), u'FakeNetMini.exe'))


self.server.logger.debug('Sending file %s', filename_path)
Expand All @@ -203,7 +218,7 @@ def handle_rrq(self, socket, filename):

def handle_wrq(self, socket, filename):

self.server.filename_path = os.path.join(self.server.tftproot_path, filename)
self.server.filename_path = filename

# Send acknowledgement so the client will begin writing
ack_packet = OPCODE_ACK + "\x00\x00"
Expand All @@ -224,9 +239,15 @@ def test(config):
pass

def main():
"""
Run from the flare-fakenet-ng root dir with the following command:
python2 -m fakenet.listeners.TFTPListener
"""
logging.basicConfig(format='%(asctime)s [%(name)15s] %(message)s', datefmt='%m/%d/%y %I:%M:%S %p', level=logging.DEBUG)

config = {'port': '69', 'protocol': 'udp', 'tftproot': '../defaultFiles'}
config = {'port': '69', 'protocol': 'udp', 'tftproot': 'defaultFiles'}

listener = TFTPListener(config)
listener.start()
Expand Down
40 changes: 39 additions & 1 deletion fakenet/listeners/__init__.py
Expand Up @@ -9,5 +9,43 @@
import BITSListener
import ProxyListener

__all__ = ['RawListener', 'HTTPListener', 'DNSListener', 'SMTPListener', 'FTPListener', 'IRCListener', 'TFTPListener', 'POPListener', 'BITSListener', 'ProxyListener']
import os

__all__ = ['safe_join', 'abs_config_path', 'RawListener', 'HTTPListener', 'DNSListener', 'SMTPListener', 'FTPListener', 'IRCListener', 'TFTPListener', 'POPListener', 'BITSListener', 'ProxyListener']

def safe_join(root, path):
"""
Joins a path to a root path, even if path starts with '/', using os.sep
"""

# prepending a '/' ensures '..' does not traverse past the root
# of the path
if not path.startswith('/'):
path = '/' + path
normpath = os.path.normpath(path)

return root + normpath

def abs_config_path(path):
"""
Attempts to return the absolute path of a path from a configuration
setting.
First tries just to just take the abspath() of the parameter to see
if it exists relative to the current working directory. If that does
not exist, attempts to find it relative to the 'fakenet' package
directory. Returns None if neither exists.
"""

# Try absolute path first
abspath = os.path.abspath(path)
if os.path.exists(abspath):
return abspath

# Try to locate the location relative to application path
relpath = os.path.join(os.path.dirname(os.path.dirname(__file__)), path)

if os.path.exists(relpath):
return os.path.abspath(relpath)

return None

0 comments on commit a4bf869

Please sign in to comment.