[Assignment-4] added tcpproxy

This commit is contained in:
Sascha Tommasone 2024-05-25 10:25:40 +02:00
parent ac334954e6
commit 4c97dbb963
Signed by: saschato
GPG key ID: 751068A86FCAA217
20 changed files with 1443 additions and 0 deletions

View file

@ -0,0 +1,38 @@
#!/usr/bin/env python3
import os
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = os.path.splitext(os.path.basename(__file__))[0]
self.description = 'Find HTTP Digest Authentication and replace it with a Basic Auth'
self.verbose = verbose
self.realm = 'tcpproxy'
if options is not None:
if 'realm' in options.keys():
self.realm = bytes(options['realm'], 'ascii')
def detect_linebreak(self, data):
line = data.split(b'\n', 1)[0]
if line.endswith(b'\r'):
return b'\r\n'
else:
return b'\n'
def execute(self, data):
delimiter = self.detect_linebreak(data)
lines = data.split(delimiter)
for index, line in enumerate(lines):
if line.lower().startswith(b'www-authenticate: digest'):
lines[index] = b'WWW-Authenticate: Basic realm="%s"' % self.realm
return delimiter.join(lines)
def help(self):
h = '\trealm: use this instead of the default "tcpproxy"\n'
return h
if __name__ == '__main__':
print('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,34 @@
#!/usr/bin/env python3
import os.path as path
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = path.splitext(path.basename(__file__))[0]
self.description = 'Print a hexdump of the received data'
self.incoming = incoming # incoming means module is on -im chain
self.len = 16
if options is not None:
if 'length' in options.keys():
self.len = int(options['length'])
def help(self):
return '\tlength: bytes per line (int)'
def execute(self, data):
# this is a pretty hex dumping function directly taken from
# http://code.activestate.com/recipes/142812-hex-dumper/
result = []
digits = 2
for i in range(0, len(data), self.len):
s = data[i:i + self.len]
hexa = ' '.join(['%0*X' % (digits, x) for x in s])
text = ''.join([chr(x) if 0x20 <= x < 0x7F else '.' for x in s])
result.append("%04X %-*s %s" % (i, self.len * (digits + 1), hexa, text))
print("\n".join(result))
return data
if __name__ == '__main__':
print ('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,35 @@
#!/usr/bin/env python3
import os.path as path
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = path.splitext(path.basename(__file__))[0]
self.description = 'Prepend HTTP response header'
self.server = None
if options is not None:
if 'server' in options.keys():
self.server = bytes(options['server'], 'ascii')
# source will be set by the proxy thread later on
self.source = None
def execute(self, data):
if self.server is None:
self.server = bytes(self.source[0], 'ascii')
http = b"HTTP/1.1 200 OK\r\n"
http += b"Server: %s\r\n" % self.server
http += b"Connection: keep-alive\r\n"
http += b"Content-Length: %d\r\n" % len(data)
return http + b"\r\n" + data
def help(self):
h = '\tserver: remote source, used in response Server header\n'
return h
if __name__ == '__main__':
print('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,41 @@
#!/usr/bin/env python3
import os.path as path
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = path.splitext(path.basename(__file__))[0]
self.description = 'Prepend HTTP header'
self.incoming = incoming # incoming means module is on -im chain
self.targethost = None
self.targetport = None
if options is not None:
if 'host' in options.keys():
self.targethost = bytes(options['host'], 'ascii')
if 'port' in options.keys():
self.targetport = bytes(options['port'], 'ascii')
# destination will be set by the proxy thread later on
self.destination = None
def execute(self, data):
if self.targethost is None:
self.targethost = bytes(self.destination[0], 'ascii')
if self.targetport is None:
self.targetport = bytes(str(self.destination[1]), 'ascii')
http = b"POST /to/%s/%s HTTP/1.1\r\n" % (self.targethost, self.targetport)
http += b"Host: %s\r\n" % self.targethost
http += b"Connection: keep-alive\r\n"
http += b"Content-Length: %d\r\n" % len(data)
return http + b"\r\n" + str(data)
def help(self):
h = '\thost: remote target, used in request URL and Host header\n'
h += '\tport: remote target port, used in request URL\n'
return h
if __name__ == '__main__':
print('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,27 @@
#!/usr/bin/env python3
import os.path as path
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = path.splitext(path.basename(__file__))[0]
self.description = 'Remove HTTP header from data'
self.incoming = incoming # incoming means module is on -im chain
def detect_linebreak(self, data):
line = data.split(b'\n', 1)[0]
if line.endswith(b'\r'):
return b'\r\n' * 2
else:
return b'\n' * 2
def execute(self, data):
delimiter = self.detect_linebreak(data)
if delimiter in data:
data = data.split(delimiter, 1)[1]
return data
if __name__ == '__main__':
print('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,79 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# THIS MODULE DOES NOT WORK AND WILL BE REPLACED, DO NOT USE
import os.path as path
import platform
if 'java' in platform.system().lower():
import java.io as io
from com.thoughtworks.xstream import XStream
from java.lang import Exception
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
self.is_jython = 'java' in platform.system().lower()
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = path.splitext(path.basename(__file__))[0]
self.description = 'Serialization or deserialization of Java objects' if self.is_jython else \
'Serialization or deserialization of Java objects (needs jython)'
self.incoming = incoming # incoming means module is on -im chain
self.execute = self.error
if options is not None:
if 'mode' in options.keys():
if 'deserial' in options['mode']:
self.execute = self.deserial
elif 'serial' in options['mode']:
self.execute = self.serial
def help(self):
return '\tmode: [serial|deserial] select deserialization (to XML) or serialization (to Java object)'
def deserial(self, data):
if not self.is_jython:
print ('[!] This module can only be used in jython!')
return data
try:
# turn data into a Java object
bis = io.ByteArrayInputStream(data)
ois = io.ObjectInputStream(bis)
obj = ois.readObject()
# converting Java object to XML structure
xs = XStream()
xml = xs.toXML(obj)
return xml
except Exception as e:
print ('[!] Caught Exception. Could not convert.\n')
return data
def serial(self, data):
if not self.is_jython:
print ('[!] This module can only be used in jython!')
return data
try:
# Creating XStream object and creating Java object from XML structure
xs = XStream()
serial = xs.fromXML(data)
# writing created Java object to and serializing it with ObjectOutputStream
bos = io.ByteArrayOutputStream()
oos = io.ObjectOutputStream(bos)
oos.writeObject(serial)
# I had a problem with signed vs. unsigned bytes, hence the & 0xff
return "".join([chr(x & 0xff) for x in bos.toByteArray().tolist()])
except Exception as e:
print ('[!] Caught Exception. Could not convert.\n')
return data
def error(self, data):
print ('[!] Unknown mode. Please specify mode=[serial|deserial].')
return data
if __name__ == '__main__':
print ('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,41 @@
#!/usr/bin/env python3
import os.path as path
import time
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = path.splitext(path.basename(__file__))[0]
self.description = 'Log data in the module chain. Use in addition to general logging (-l/--log).'
self.incoming = incoming # incoming means module is on -im chain
self.find = None # if find is not None, this text will be highlighted
# file: the file name, format is (in|out)-20160601-112233.13413
self.file = ('in-' if incoming else 'out-') + \
time.strftime('%Y%m%d-%H%M%S.') + str(time.time()).split('.')[1]
if options is not None:
if 'file' in options.keys():
self.file = options['file']
self.handle = None
def __del__(self):
if self.handle is not None:
self.handle.close()
def execute(self, data):
if self.handle is None:
self.handle = open(self.file, 'wb', 0) # unbuffered
print('Logging to file', self.file)
logentry = bytes(time.strftime('%Y%m%d-%H%M%S') + ' ' + str(time.time()) + '\n', 'ascii')
logentry += data
logentry += b'-' * 20 + b'\n'
self.handle.write(logentry)
return data
def help(self):
h = '\tfile: name of logfile'
return h
if __name__ == '__main__':
print('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,73 @@
#!/usr/bin/env python3
import os.path as path
import paho.mqtt.client as mqtt
from distutils.util import strtobool
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = path.splitext(path.basename(__file__))[0]
self.description = 'Publish the data to an MQTT server'
self.incoming = incoming # incoming means module is on -im chain
self.client_id = ''
self.username = None
self.password = None
self.server = None
self.port = 1883
self.topic = ''
self.hex = False
if options is not None:
if 'clientid' in options.keys():
self.client_id = options['clientid']
if 'server' in options.keys():
self.server = options['server']
if 'username' in options.keys():
self.username = options['username']
if 'password' in options.keys():
self.password = options['password']
if 'port' in options.keys():
try:
self.port = int(options['port'])
if self.port not in range(1, 65536):
raise ValueError
except ValueError:
print(f'port: invalid port {options["port"]}, using default {self.port}')
if 'topic' in options.keys():
self.topic = options['topic'].strip()
if 'hex' in options.keys():
try:
self.hex = bool(strtobool(options['hex']))
except ValueError:
print(f'hex: {options["hex"]} is not a bool value, falling back to default value {self.hex}.')
if self.server is not None:
self.mqtt = mqtt.Client(self.client_id)
if self.username is not None or self.password is not None:
self.mqtt.username_pw_set(self.username, self.password)
self.mqtt.connect(self.server, self.port)
else:
self.mqtt = None
def execute(self, data):
if self.mqtt is not None:
if self.hex is True:
self.mqtt.publish(self.topic, data.hex())
else:
self.mqtt.publish(self.topic, data)
return data
def help(self):
h = '\tserver: server to connect to, required\n'
h += ('\tclientid: what to use as client_id, default is empty\n'
'\tusername: username\n'
'\tpassword: password\n'
'\tport: port to connect to, default 1883\n'
'\ttopic: topic to publish to, default is empty\n'
'\thex: encode data as hex before sending it. AAAA becomes 41414141.')
return h
if __name__ == '__main__':
print('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,34 @@
#!/usr/bin/env python3
import os.path as path
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = path.splitext(path.basename(__file__))[0]
self.description = 'Replace gzip in the list of accepted encodings ' \
'in a HTTP request with booo.'
self.incoming = incoming # incoming means module is on -im chain
# I chose to replace gzip instead of removing it to keep the parsing
# logic as simple as possible.
def execute(self, data):
try:
# split at \r\n\r\n to split the request into header and body
header, body = data.split(b'\r\n\r\n', 1)
except ValueError:
# no \r\n\r\n, so probably not HTTP, we can go now
return data
# now split the header string into its lines
headers = header.split(b'\r\n')
for h in headers:
if h.lower().startswith(b'accept-encoding:') and b'gzip' in h:
headers[headers.index(h)] = h.replace(b'gzip', b'booo')
break
return b'\r\n'.join(headers) + b'\r\n\r\n' + body
if __name__ == '__main__':
print('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,62 @@
#!/usr/bin/env python3
import os
import re
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = os.path.splitext(os.path.basename(__file__))[0]
self.description = 'Replace text on the fly by using regular expressions in a file or as module parameters'
self.verbose = verbose
self.search = None
self.replace = None
self.filename = None
self.separator = ':'
if options is not None:
if 'search' in options.keys():
self.search = bytes(options['search'], 'ascii')
if 'replace' in options.keys():
self.replace = bytes(options['replace'], 'ascii')
if 'file' in options.keys():
self.filename = options['file']
try:
open(self.filename)
except IOError as ioe:
print("Error opening %s: %s" % (self.filename, ioe.strerror))
self.filename = None
if 'separator' in options.keys():
self.separator = options['separator']
def execute(self, data):
pairs = [] # list of (search, replace) tuples
if self.search is not None and self.replace is not None:
pairs.append((self.search, self.replace))
if self.filename is not None:
for line in open(self.filename).readlines():
try:
search, replace = line.split(self.separator, 1)
pairs.append((bytes(search.strip(), 'ascii'), bytes(replace.strip(), 'ascii')))
except ValueError:
# line does not contain separator and will be ignored
pass
for search, replace in pairs:
# TODO: verbosity
data = re.sub(search, replace, data)
return data
def help(self):
h = '\tsearch: string or regular expression to search for\n'
h += ('\treplace: string the search string should be replaced with\n')
h += ('\tfile: file containing search:replace pairs, one per line\n')
h += ('\tseparator: define a custom search:replace separator in the file, e.g. search#replace\n')
h += ('\n\tUse at least file or search and replace (or both).\n')
return h
if __name__ == '__main__':
print('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,34 @@
#!/usr/bin/env python3
import os.path as path
from distutils.util import strtobool
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = path.splitext(path.basename(__file__))[0]
self.description = 'Print the size of the data passed to the module'
self.verbose = verbose
self.source = None
self.destination = None
self.incoming = incoming
if options is not None:
if 'verbose' in options.keys():
self.verbose = bool(strtobool(options['verbose']))
def execute(self, data):
size = len(data)
msg = "Received %d bytes" % size
if self.verbose:
msg += " from %s:%d" % self.source
msg += " for %s:%d" % self.destination
print(msg)
return data
def help(self):
h = '\tverbose: override the global verbosity setting'
return h
if __name__ == '__main__':
print('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,79 @@
#!/usr/bin/env python2
import os.path as path
import time
from distutils.util import strtobool
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = path.splitext(path.basename(__file__))[0]
self.description = 'Change HTTP responses of a certain size to 404.'
self.incoming = incoming # incoming means module is on -im chain
self.size = 2392 # if a response has this value as content-length, it will become a 404
self.verbose = False
self.custom = False
self.rewriteall = False # will we block the first occurence?
self.firstfound = False # have we found the first occurence yet?
self.resetinterval = None # if we haven't found a fitting response in this many seconds, reset the state and set first to False again
self.timer = time.time()
if options is not None:
if 'size' in options.keys():
try:
self.size = int(options['size'])
except ValueError:
pass # use the default if you can't parse the parameter
if 'verbose' in options.keys():
self.verbose = bool(strtobool(options['verbose']))
if 'custom' in options.keys():
try:
with open(options['custom'], 'rb') as handle:
self.custom = handle.read()
except Exception:
print('Can\'t open custom error file, not using it.')
self.custom = False
if 'rewriteall' in options.keys():
self.rewriteall = bool(strtobool(options['rewriteall']))
if 'reset' in options.keys():
try:
self.resetinterval = float(options['reset'])
except ValueError:
pass # use the default if you can't parse the parameter
def execute(self, data):
contentlength = b'content-length: ' + bytes(str(self.size), 'ascii')
if data.startswith(b'HTTP/1.1 200 OK') and contentlength in data.lower():
if self.resetinterval is not None:
t = time.time()
if t - self.timer >= self.resetinterval:
if self.verbose:
print('Timer elapsed')
self.firstfound = False
self.timer = t
if self.rewriteall is False and self.firstfound is False:
# we have seen this response size for the first time and are not blocking the first one
self.firstfound = True
if self.verbose:
print('Letting this response through')
return data
if self.custom is not False:
data = self.custom
if self.verbose:
print('Replaced response with custom response')
else:
data = data.replace(b'200 OK', b'404 Not Found', 1)
if self.verbose:
print('Edited return code')
return data
def help(self):
h = '\tsize: if a response has this value as content-length, it will become a 404\n'
h += ('\tverbose: print a message if a string is replaced\n'
'\tcustom: path to a file containing a custom response, will replace the received response\n'
'\trewriteall: if set, it will rewrite all responses. Default is to let the first on through'
'\treset: number of seconds after which we will reset the state and will let the next response through.')
return h
if __name__ == '__main__':
print('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,28 @@
import os.path as path
import json
import socket
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = path.splitext(path.basename(__file__))[0]
self.description = 'Simply print the received data as text'
self.incoming = incoming # incoming means module is on -im chain
self.find = None # if find is not None, this text will be highlighted
def execute(self, data):
print(f"Incoming data: {data}")
### Work with data here ###
# data_json = json.loads(data)
# data_json["content"] = "Blablabla"
# data = json.dumps(data_json)
# print(f"Outgoing data: {data}")
# return data + "\n"
print(f"Outgoing data: {data}")
return data
if __name__ == '__main__':
print('This module is not supposed to be executed alone!')

View file

@ -0,0 +1,46 @@
#!/usr/bin/env python3
import os.path as path
from codecs import decode, lookup
class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# extract the file name from __file__. __file__ is proxymodules/name.py
self.name = path.splitext(path.basename(__file__))[0]
self.description = 'Simply print the received data as text'
self.incoming = incoming # incoming means module is on -im chain
self.find = None # if find is not None, this text will be highlighted
self.codec = 'latin_1'
if options is not None:
if 'find' in options.keys():
self.find = bytes(options['find'], 'ascii') # text to highlight
if 'color' in options.keys():
self.color = bytes('\033[' + options['color'] + 'm', 'ascii') # highlight color
else:
self.color = b'\033[31;1m'
if 'codec' in options.keys():
codec = options['codec']
try:
lookup(codec)
self.codec = codec
except LookupError:
print(f"{self.name}: {options['codec']} is not a valid codec, using {self.codec}")
def execute(self, data):
if self.find is None:
print(decode(data, self.codec))
else:
pdata = data.replace(self.find, self.color + self.find + b'\033[0m')
print(decode(pdata, self.codec))
return data
def help(self):
h = '\tfind: string that should be highlighted\n'
h += ('\tcolor: ANSI color code. Will be wrapped with \\033[ and m, so'
' passing 32;1 will result in \\033[32;1m (bright green)')
return h
if __name__ == '__main__':
print('This module is not supposed to be executed alone!')