Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GSoC'23 Project: Implement an Interactive GUI for presenting Network-Based Indicators summary #163

Merged
merged 42 commits into from
Mar 29, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
2f5119a
collect nbi in HTTP Listener as a nested dictionary
3V3RYONE May 30, 2023
81f9fe5
added comments
3V3RYONE Jun 1, 2023
77205df
add process information in sessions dictionary
3V3RYONE Jun 2, 2023
8dcad9d
use partition method to split HTTP headers
3V3RYONE Jun 5, 2023
e4b22a7
implemented callback from http listener to diverter for unproxied req…
3V3RYONE Jun 6, 2023
1638d51
log proxied nbis
3V3RYONE Jun 13, 2023
77945b9
use proper naming conventions and code optimization
3V3RYONE Jun 15, 2023
770a6ee
collect dport, protocol and ssl information
3V3RYONE Jun 26, 2023
24f1c54
added comments for methods, code optimization
3V3RYONE Jun 26, 2023
c3d63b1
normalize nbi dictionary format
3V3RYONE Jun 29, 2023
73c72de
make a wrapper class for diverter
3V3RYONE Jul 6, 2023
8cd483c
normalize wrapper class format
3V3RYONE Jul 11, 2023
e4be3fb
index remote process ids for multihost mode in nbi dict
3V3RYONE Jul 11, 2023
afb32fa
added support for local traffic in multihost mode
3V3RYONE Jul 11, 2023
5d2b187
collect nbis from raw listener
3V3RYONE Jul 14, 2023
bc9e6a8
chore: add protocol with proxy_sport as key in proxy_sport_to_orig_sp…
3V3RYONE Jul 17, 2023
ca9f992
feat: add prettyPrint() for nbis
3V3RYONE Jul 17, 2023
fe24a6d
docs: add docstrings to methods and revise NBI banner
3V3RYONE Jul 21, 2023
5a63b7d
feat: log nbis from ftp listener
3V3RYONE Jul 24, 2023
59964a8
feat: log nbis from tftp listener
3V3RYONE Jul 25, 2023
7a55b15
chore: update nbi format for ftp and tftp listeners
3V3RYONE Jul 25, 2023
5fff6b3
chore: spawn longer code into multiple lines for better readability
3V3RYONE Jul 26, 2023
f20c990
chore: update code style to match standard indentation
3V3RYONE Jul 26, 2023
4f42b25
chore: collect nbis in the handler of raw listener
3V3RYONE Jul 26, 2023
a4f6348
feat: log nbis from smtp listener
3V3RYONE Jul 27, 2023
8c9b6e4
feat: add an interactive UI to plot nbi data
3V3RYONE Aug 11, 2023
d5463df
feat: log nbis from dns listener
3V3RYONE Aug 12, 2023
1b7944a
chore: support standard copy formats in UI and highlight active element
3V3RYONE Aug 14, 2023
3afda5c
chore: add jinja2 in setup.py
3V3RYONE Aug 23, 2023
3ed2a77
chore: inline css and js to make reports shareable
3V3RYONE Aug 23, 2023
2254673
feat: log nbis from pop listener and irc listener
3V3RYONE Aug 24, 2023
b5f1070
chore: log actual path requested by client in ftp listener
3V3RYONE Aug 25, 2023
be1eae2
chore: normalize command to string before handling request
3V3RYONE Aug 25, 2023
252e7ac
chore: add icmp disclaimer and modify copy format for http nbis in ui
3V3RYONE Aug 25, 2023
a60893e
chore: log ack and err requests in nbis from tftp listener
3V3RYONE Aug 25, 2023
c82b3b6
chore: modify nbi format in dns listener
3V3RYONE Aug 25, 2023
e401d3f
chore: map proxy_sport to orig_sport for udp requests in proxy listener
3V3RYONE Aug 25, 2023
730c5dc
docs: add ui usage and documentation in readme
3V3RYONE Aug 25, 2023
3b9693a
docs: add better documentation and fix code indentation
3V3RYONE Sep 10, 2023
dc45936
fix: remove empty data field from ACK nbis in tftp listener
3V3RYONE Sep 10, 2023
9ee0950
Fix bugs in HTML report template and clean up
tinajohnson Mar 5, 2024
6a2c20e
Fix indentation of report template file
tinajohnson Mar 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions fakenet/diverters/diverterbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,12 @@ def first_packet_new_session(self):
Returns:
True if this pair of endpoints hasn't conversed before, else False
"""
return not (self.diverter.sessions.get(self.pkt.sport) ==
# sessions.get returns (ip, port, pid and comm) or None.
# We just want ip and port for comparison.
s_dst_ip_port = self.diverter.sessions.get(self.pkt.sport)
if s_dst_ip_port:
3V3RYONE marked this conversation as resolved.
Show resolved Hide resolved
s_dst_ip_port = s_dst_ip_port[:2]
return not (s_dst_ip_port ==
(self.pkt.dst_ip, self.pkt.dport))


Expand Down Expand Up @@ -534,6 +539,12 @@ def __init__(self, diverter_config, listeners_config, ip_addrs,
self.logger = logging.getLogger('Diverter')
self.logger.setLevel(logging_level)

# Network Based Indicators
self.nbiDict = {}
3V3RYONE marked this conversation as resolved.
Show resolved Hide resolved

# proxy to original source ports mapping
self.proxy_original_source_ports = {}

# Rate limiting for displaying pid/comm/proto/IP/port
self.last_conn = None

Expand Down Expand Up @@ -1761,7 +1772,8 @@ def addSession(self, pkt):
Returns:
None
"""
self.sessions[pkt.sport] = (pkt.dst_ip, pkt.dport)
pid, comm = self.get_pid_comm(pkt)
self.sessions[pkt.sport] = (pkt.dst_ip, pkt.dport, pid, comm)

def maybeExecuteCmd(self, pkt, pid, comm):
"""Execute any ExecuteCmd associated with this port/listener.
Expand All @@ -1782,3 +1794,24 @@ def maybeExecuteCmd(self, pkt, pid, comm):
self.logger.info('Executing command: %s' % (execCmd))
self.execute_detached(execCmd)

def map_orig_sport_to_proxy_sport(self, orig_sport, proxy_sport):
3V3RYONE marked this conversation as resolved.
Show resolved Hide resolved
self.proxy_original_source_ports[proxy_sport] = orig_sport

def logNBI(self, listener_port, nbi):
proxied_nbi = listener_port in self.proxy_original_source_ports.keys()
3V3RYONE marked this conversation as resolved.
Show resolved Hide resolved
orig_source_port = self.proxy_original_source_ports[listener_port] if proxied_nbi else listener_port
_, __, pid, comm = self.sessions[orig_source_port]

# If it's a new NBI from an exisitng process, append nbi, else create new key
existing_process = (pid, comm) in self.nbiDict.keys()
if existing_process:
# {(123, chrome.exe): {"host": ["www.google.com"], "version": ["HTTP1.1"]}}
for nbi_attributes in nbi.keys():
if nbi_attributes in self.nbiDict[(pid, comm)].keys():
self.nbiDict[(pid, comm)][nbi_attributes].append(nbi[nbi_attributes][0])
3V3RYONE marked this conversation as resolved.
Show resolved Hide resolved
else:
self.nbiDict[(pid, comm)][nbi_attributes] = nbi[nbi_attributes]

else:
self.nbiDict[(pid, comm)] = nbi

31 changes: 30 additions & 1 deletion fakenet/listeners/HTTPListener.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@

INDENT = ' '


def qualify_file_path(filename, fallbackdir):
path = filename
if path:
Expand Down Expand Up @@ -193,6 +192,7 @@ def start(self):
self.server.config = self.config
self.server.webroot_path = self.webroot_path
self.server.extensions_map = self.extensions_map
self.server.diverter = None

if self.config.get('usessl') == 'Yes':
self.logger.debug('Using SSL socket.')
Expand Down Expand Up @@ -246,6 +246,9 @@ def stop(self):
self.server.shutdown()
self.server.server_close()

def acceptDiverter(self, diverter):
self.server.diverter = diverter


class ThreadedHTTPServer(http.server.HTTPServer):

Expand Down Expand Up @@ -284,6 +287,9 @@ def do_HEAD(self):
for line in str(self.headers).split("\n"):
self.server.logger.info(INDENT + line)

# collect nbi
self.collect_nbi(self.requestline, self.headers)

# Prepare response
if not self.doCustomResponse('HEAD'):
self.send_response(200)
Expand All @@ -296,6 +302,9 @@ def do_GET(self):
for line in str(self.headers).split("\n"):
self.server.logger.info(INDENT + line)

# collect nbi
self.collect_nbi(self.requestline, self.headers)

# Prepare response
if not self.doCustomResponse('GET'):
# Get response type based on the requested path
Expand All @@ -322,6 +331,9 @@ def do_POST(self):
for line in post_body.split(b"\n"):
self.server.logger.info(INDENT.encode('utf-8') + line)

# collect nbi
self.collect_nbi(self.requestline, self.headers, post_body)

# Store HTTP Posts
if self.server.config.get('dumphttpposts') and self.server.config['dumphttpposts'].lower() == 'yes':
http_filename = "%s_%s.txt" % (self.server.config.get('dumphttppostsfileprefix', 'http'), time.strftime("%Y%m%d_%H%M%S"))
Expand Down Expand Up @@ -350,6 +362,23 @@ def do_POST(self):
self.end_headers()

self.wfile.write(response)

def collect_nbi(self, requestline, headers, post_data=None):
nbi = {}
method, uri, version = requestline.split(" ")
nbi["method"] = [method]
nbi["uri"] = [uri]
nbi["version"] = [version]

for line in str(headers).rstrip().split("\n"):
key, _, value = line.partition(":")
nbi[key] = [value.lstrip()]

if post_data:
nbi["post_data"] = [post_data]

# report diverter each time when NBI is reported
self.server.diverter.logNBI(self.client_address[1], nbi)

def get_response(self, path):
response = "<html><head><title>FakeNet</title><body><h1>FakeNet</h1></body></html>"
Expand Down
18 changes: 17 additions & 1 deletion fakenet/listeners/ProxyListener.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,20 @@ def __init__(self, ip, port, listener_q, remote_q, config, log):
self.logger = log
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

def connect(self):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like us to document (via comments) why there must exist both a run() and a connect() method.

Your astute mentor reminded me that we documented this in our discussion notes:

The ThreadedTCPClientSocket (which is really a thread!) would implement a connect() method that the ThreadedTCPRequestHandler object could use to get the source port and then call a Diverter-supplied callback informing the diverter of the mapping between the old source port and the proxy source port.

Maintainers would benefit from seeing this represented in the comments for connect() along with commentary on the run() method indicating the expectation that connect() has already been called.

try:
self.sock.connect((self.ip, self.port))
new_sport = self.sock.getsockname()[1]
return new_sport

except Exception as e:
self.logger.debug('Listener socket exception while attempting connection %s' % e.message)

return None

def run(self):

try:
self.sock.connect((self.ip, self.port))
while True:
readable, writable, exceptional = select.select([self.sock],
[], [], .001)
Expand Down Expand Up @@ -225,6 +235,12 @@ def handle(self):
listener_sock = ThreadedTCPClientSocket(self.server.local_ip,
top_listener.port, listener_q, remote_q,
self.server.config, self.server.logger)

# Get proxy initiated source port and report to diverter
new_sport = listener_sock.connect()
if new_sport:
self.server.diverter.map_orig_sport_to_proxy_sport(orig_src_port, new_sport)

listener_sock.daemon = True
listener_sock.start()
remote_sock.setblocking(0)
Expand Down