From ef9f0b2e5d5494ed9eabd031ce77d10d155f1cb3 Mon Sep 17 00:00:00 2001 From: Jared Jennings Date: Mon, 31 Aug 2020 20:13:06 +0000 Subject: [PATCH 1/2] add DHCPInfo analyzer --- analyzers/DHCPInfo/DHCPInfo.json | 22 +++++ analyzers/DHCPInfo/README.md | 28 ++++++ analyzers/DHCPInfo/dhcp_info_analyzer.py | 85 +++++++++++++++++++ analyzers/DHCPInfo/sources/ad-example.csv | 3 + .../DHCPInfo/sources/minimal-example.csv | 2 + 5 files changed, 140 insertions(+) create mode 100644 analyzers/DHCPInfo/DHCPInfo.json create mode 100644 analyzers/DHCPInfo/README.md create mode 100644 analyzers/DHCPInfo/dhcp_info_analyzer.py create mode 100644 analyzers/DHCPInfo/sources/ad-example.csv create mode 100644 analyzers/DHCPInfo/sources/minimal-example.csv diff --git a/analyzers/DHCPInfo/DHCPInfo.json b/analyzers/DHCPInfo/DHCPInfo.json new file mode 100644 index 000000000..a48e1ce36 --- /dev/null +++ b/analyzers/DHCPInfo/DHCPInfo.json @@ -0,0 +1,22 @@ +{ + "name": "DHCPInfo", + "author": "Jared Jennings ", + "license": "AGPL-V3", + "url": "https://github.com/TheHive-Project/Cortex-Analyzers", + "version": "1.0", + "description": "Find DHCP scope name corresponding to an internal IP address", + "dataTypeList": ["ip"], + "command": "DHCPInfo/dhcp_info_analyzer.py", + "baseConfig": "DHCPInfo", + "max_tlp": 3, + "configurationItems": [ + { + "name": "dhcp_info_directory", + "description": "Directory on the Cortex server where the DHCP scope info is to be found", + "type": "string", + "multi": false, + "required": true, + "defaultValue": "/path/to/csv-sources" + } + ] +} diff --git a/analyzers/DHCPInfo/README.md b/analyzers/DHCPInfo/README.md new file mode 100644 index 000000000..0ac57062e --- /dev/null +++ b/analyzers/DHCPInfo/README.md @@ -0,0 +1,28 @@ +# DHCP scope analyzer + +Find which subnet a local IP address belongs to. + + +## Rationale + +When you have multiple subnets on an internal network, the particular +subnet someone is on may convey some information, like where someone +is in a building. If you have an IP observable containing a user's +workstation IP address, and an incident is going on, you want to pull +in that extra information without having to think about it. This +analyzer does that for you. + + +## Scope information + +Put your DHCP scope information in CSV files (see examples in sources +directory). Set the configuration item `dhcp_info_directory` to the +location of the directory with the CSV files in it. + +In particular, if you use Microsoft DHCP, you can directly use +exported DHCP scope information. Just save it to a CSV file like so: + +``` + Get-DhcpServerv4Scope -ComputerName mydc.example.com | + Export-CSV mydc.csv +``` diff --git a/analyzers/DHCPInfo/dhcp_info_analyzer.py b/analyzers/DHCPInfo/dhcp_info_analyzer.py new file mode 100644 index 000000000..64d77d3ad --- /dev/null +++ b/analyzers/DHCPInfo/dhcp_info_analyzer.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 + +import csv +import glob +import os.path +import ipaddress +from cortexutils.analyzer import Analyzer + + +def rows_of_csvs(csv_dir): + for csvfn in glob.glob(os.path.join(csv_dir, '*.csv')): + with open(csvfn, 'rt') as csvf: + first_line = next(csvf) + if not first_line.startswith('#TYPE'): + # oops, let's not skip that. start over + csvf.seek(0) + # at this point, type info or no, we should be on the line + # with the headers + d = csv.DictReader(csvf) + for i, row in enumerate(d, start=1): + row['_File'] = csvfn + row['_Row'] = i + yield row + + +def subnets(rows): + for d in rows: + # by using these titles here, we are assuming they are written + # at the top of columns in the CSV files we parsed + yield { + 'net': ipaddress.ip_network(d['ScopeId'] + '/' + d['SubnetMask']), + 'first_ip': ipaddress.ip_address(d['StartRange']), + 'last_ip': ipaddress.ip_address(d['EndRange']), + 'name': d['Name'], + } + + +def get_name_of_subnet(scopes, ip_str): + a = ipaddress.ip_address(ip_str) + for scope in scopes: + if a in scope['net']: + if (a >= scope['first_ip']) and (a <= scope['last_ip']): + return scope['name'] + raise KeyError(ip_str) + + +class DHCPInfoAnalyzer(Analyzer): + def __init__(self): + super().__init__() + self.dhcp_info_directory = self.get_param( + 'config.dhcp_info_directory', None, + 'DHCP Info directory is missing') + + def summary(self, raw): + taxonomies = [] + if 'dhcp_scope_name' in raw: + taxonomies.append(self.build_taxonomy( + 'info', 'DHCP', 'ScopeName', raw['dhcp_scope_name'])) + return {'taxonomies': taxonomies} + + def run(self): + super().run() + if self.data_type == 'ip': + all_rows = rows_of_csvs(self.dhcp_info_directory) + scopes = list(subnets(all_rows)) + try: + data = self.get_data() + subnet_name = get_name_of_subnet(scopes, data) + self.report({ + 'dhcp_scope_name': subnet_name, + }) + except KeyError: + self.report({ + 'not_found': ('address {} not found ' + 'in any of the {} known ' + 'DHCP scopes'.format(data, len(scopes))), + }) + except Exception as e: + self.unexpectedError(repr(e)) + else: + self.notSupported() + + +if __name__ == '__main__': + DHCPInfoAnalyzer().run() diff --git a/analyzers/DHCPInfo/sources/ad-example.csv b/analyzers/DHCPInfo/sources/ad-example.csv new file mode 100644 index 000000000..cb86c9b36 --- /dev/null +++ b/analyzers/DHCPInfo/sources/ad-example.csv @@ -0,0 +1,3 @@ +#TYPE Microsoft.Management.Infrastructure.CimInstance#root/Microsoft/Windows/DHCP/DhcpServerv4Scope +"ScopeId","SubnetMask","StartRange","EndRange","ActivatePolicies","Delay","Description","LeaseDuration","MaxBootpClients","Name","NapEnable","NapProfile","State","SuperscopeName","Type","PSComputerName" +"192.0.2.0","255.255.255.0","192.0.2.51","192.0.2.254","True","0","Office1","08:00:00","4294967295","Office Floor 1","False","","Active","","Dhcp", diff --git a/analyzers/DHCPInfo/sources/minimal-example.csv b/analyzers/DHCPInfo/sources/minimal-example.csv new file mode 100644 index 000000000..c90b2bc39 --- /dev/null +++ b/analyzers/DHCPInfo/sources/minimal-example.csv @@ -0,0 +1,2 @@ +"ScopeId","SubnetMask","StartRange","EndRange","Name" +"192.0.2.0","255.255.255.0","192.0.2.51","192.0.2.254","Office1" From 4df0db9fe91c6f7bfbc07635e32bf1f2d940212f Mon Sep 17 00:00:00 2001 From: Jared Jennings Date: Mon, 31 Aug 2020 20:24:41 +0000 Subject: [PATCH 2/2] add Hive report templates --- thehive-templates/DHCPInfo_1_0/long.html | 26 +++++++++++++++++++++++ thehive-templates/DHCPInfo_1_0/short.html | 3 +++ 2 files changed, 29 insertions(+) create mode 100644 thehive-templates/DHCPInfo_1_0/long.html create mode 100644 thehive-templates/DHCPInfo_1_0/short.html diff --git a/thehive-templates/DHCPInfo_1_0/long.html b/thehive-templates/DHCPInfo_1_0/long.html new file mode 100644 index 000000000..af440d7de --- /dev/null +++ b/thehive-templates/DHCPInfo_1_0/long.html @@ -0,0 +1,26 @@ +
+
+ {{(artifact.data || artifact.attachment.name) | fang}} +
+
+ {{content.errorMessage}} +
+
+ +
+
+ DHCP scope information +
+
+
+
Scope name
+
{{content.dhcp_scope_name}}
+
+
+
+
+
Not found
+
{{content.not_found}}
+
+
+
diff --git a/thehive-templates/DHCPInfo_1_0/short.html b/thehive-templates/DHCPInfo_1_0/short.html new file mode 100644 index 000000000..57f9d29cf --- /dev/null +++ b/thehive-templates/DHCPInfo_1_0/short.html @@ -0,0 +1,3 @@ + + {{t.namespace}}:{{t.predicate}}={{t.value}} +