added Malcolm
This commit is contained in:
		
							
								
								
									
										312
									
								
								Vagrant/resources/malcolm/logstash/scripts/ip-to-segment-logstash.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										312
									
								
								Vagrant/resources/malcolm/logstash/scripts/ip-to-segment-logstash.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,312 @@ | ||||
| #!/usr/bin/env python2 | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| # Copyright (c) 2021 Battelle Energy Alliance, LLC.  All rights reserved. | ||||
|  | ||||
| from __future__ import print_function | ||||
|  | ||||
| import sys | ||||
| import os | ||||
| import re | ||||
| import argparse | ||||
| import struct | ||||
| import ipaddress | ||||
| import itertools | ||||
| import json | ||||
| import pprint | ||||
| import uuid | ||||
| from collections import defaultdict | ||||
|  | ||||
| UNSPECIFIED_TAG = '<~<~<none>~>~>' | ||||
| HOST_LIST_IDX = 0 | ||||
| SEGMENT_LIST_IDX = 1 | ||||
|  | ||||
| JSON_MAP_TYPE_SEGMENT = 'segment' | ||||
| JSON_MAP_TYPE_HOST = 'host' | ||||
| JSON_MAP_KEY_ADDR = 'address' | ||||
| JSON_MAP_KEY_NAME = 'name' | ||||
| JSON_MAP_KEY_TAG = 'tag' | ||||
| JSON_MAP_KEY_TYPE = 'type' | ||||
|  | ||||
| ################################################################################################### | ||||
| # print to stderr | ||||
| def eprint(*args, **kwargs): | ||||
|   print(*args, file=sys.stderr, **kwargs) | ||||
|  | ||||
| ################################################################################################### | ||||
| # recursively convert unicode strings to utf-8 strings | ||||
| def byteify(input): | ||||
|   if isinstance(input, dict): | ||||
|     return {byteify(key): byteify(value) | ||||
|       for key, value in input.iteritems()} | ||||
|   elif isinstance(input, list): | ||||
|     return [byteify(element) for element in input] | ||||
|   elif isinstance(input, unicode): | ||||
|     return input.encode('utf-8') | ||||
|   else: | ||||
|     return input | ||||
|  | ||||
| ################################################################################################### | ||||
| # main | ||||
| def main(): | ||||
|  | ||||
|   # extract arguments from the command line | ||||
|   # print (sys.argv[1:]); | ||||
|   parser = argparse.ArgumentParser(description='Logstash IP address to Segment Filter Creator', add_help=False, usage='ip-to-segment-logstash.py <arguments>') | ||||
|   parser.add_argument('-m', '--mixed', dest='mixedInput', metavar='<STR>', type=str, nargs='*', default='', help='Input mixed JSON mapping file(s)') | ||||
|   parser.add_argument('-s', '--segment', dest='segmentInput', metavar='<STR>', type=str, nargs='*', default='', help='Input segment mapping file(s)') | ||||
|   parser.add_argument('-h', '--host', dest='hostInput', metavar='<STR>', type=str, nargs='*', default='', help='Input host mapping file(s)') | ||||
|   parser.add_argument('-o', '--output', dest='output', metavar='<STR>', type=str, default='-', help='Output file') | ||||
|   try: | ||||
|     parser.error = parser.exit | ||||
|     args = parser.parse_args() | ||||
|   except SystemExit: | ||||
|     parser.print_help() | ||||
|     exit(2) | ||||
|  | ||||
|   # read each input file into its own list | ||||
|   segmentLines = [] | ||||
|   hostLines = [] | ||||
|   mixedEntries = [] | ||||
|  | ||||
|   for inFile in args.segmentInput: | ||||
|     if os.path.isfile(inFile): | ||||
|       segmentLines.extend([line.strip() for line in open(inFile)]) | ||||
|  | ||||
|   for inFile in args.hostInput: | ||||
|     if os.path.isfile(inFile): | ||||
|       hostLines.extend([line.strip() for line in open(inFile)]) | ||||
|  | ||||
|   for inFile in args.mixedInput: | ||||
|     try: | ||||
|       tmpMixedEntries = json.load(open(inFile, 'r')) | ||||
|       if isinstance(tmpMixedEntries, list): | ||||
|         mixedEntries.extend(byteify(tmpMixedEntries)); | ||||
|     except: | ||||
|       pass | ||||
|  | ||||
|   # remove comments | ||||
|   segmentLines = list(filter(lambda x: (len(x) > 0) and (not x.startswith('#')), segmentLines)) | ||||
|   hostLines = list(filter(lambda x: (len(x) > 0) and (not x.startswith('#')), hostLines)) | ||||
|  | ||||
|   if (len(segmentLines) > 0) or (len(hostLines) > 0) or (len(mixedEntries) > 0): | ||||
|  | ||||
|     filterId = 0 | ||||
|     addedFields = set() | ||||
|  | ||||
|     outFile = open(args.output, 'w+') if (args.output and args.output != '-') else sys.stdout | ||||
|     try: | ||||
|       print('filter {', file=outFile) | ||||
|       print("", file=outFile) | ||||
|       print("  # this file was automatically generated by {}".format(os.path.basename(__file__)), file=outFile) | ||||
|       print("", file=outFile) | ||||
|  | ||||
|       # process segment mappings into a dictionary of two dictionaries of lists (one for hosts, one for segments) | ||||
|       # eg., tagListMap[required tag name][HOST_LIST_IDX|SEGMENT_LIST_IDX][network segment name] = [172.16.0.0/12, 192.168.0.0/24, 10.0.0.41] | ||||
|       tagListMap = defaultdict(lambda: [defaultdict(list), defaultdict(list)]) | ||||
|  | ||||
|       # handle segment mappings | ||||
|       for line in segmentLines: | ||||
|         # CIDR to network segment format: | ||||
|         #   IP(s)|segment name|required tag | ||||
|         # | ||||
|         # where: | ||||
|         #   IP(s): comma-separated list of CIDR-formatted network IP addresses | ||||
|         #          eg., 10.0.0.0/8, 169.254.0.0/16, 172.16.10.41 | ||||
|         # | ||||
|         #   segment name: segment name to be assigned when event IP address(es) match | ||||
|         # | ||||
|         #   required tag (optional): only check match and apply segment name if the event | ||||
|         #                            contains this tag | ||||
|         values = [x.strip() for x in line.split('|')] | ||||
|         if len(values) >= 2: | ||||
|           networkList = [] | ||||
|           for ip in ''.join(values[0].split()).split(','): | ||||
|             try: | ||||
|               networkList.append(str(ipaddress.ip_network(unicode(ip))).lower() if ('/' in ip) else str(ipaddress.ip_address(unicode(ip))).lower()) | ||||
|             except ValueError: | ||||
|               eprint('"{}" is not a valid IP address, ignoring'.format(ip)) | ||||
|           segmentName = values[1] | ||||
|           tagReq = values[2] if ((len(values) >= 3) and (len(values[2]) > 0)) else UNSPECIFIED_TAG | ||||
|           if (len(networkList) > 0) and (len(segmentName) > 0): | ||||
|             tagListMap[tagReq][SEGMENT_LIST_IDX][segmentName].extend(networkList) | ||||
|           else: | ||||
|             eprint('"{}" is not formatted correctly, ignoring'.format(line)) | ||||
|         else: | ||||
|           eprint('"{}" is not formatted correctly, ignoring'.format(line)) | ||||
|  | ||||
|       # handle hostname mappings | ||||
|       macAddrRegex = re.compile(r'([a-fA-F0-9]{2}[:|\-]?){6}') | ||||
|       for line in hostLines: | ||||
|         # IP or MAC address to host name map: | ||||
|         #   address|host name|required tag | ||||
|         # | ||||
|         # where: | ||||
|         #   address: comma-separated list of IPv4, IPv6, or MAC addresses | ||||
|         #          eg., 172.16.10.41, 02:42:45:dc:a2:96, 2001:0db8:85a3:0000:0000:8a2e:0370:7334 | ||||
|         # | ||||
|         #   host name: host name to be assigned when event address(es) match | ||||
|         # | ||||
|         #   required tag (optional): only check match and apply host name if the event | ||||
|         #                            contains this tag | ||||
|         # | ||||
|         values = [x.strip() for x in line.split('|')] | ||||
|         if len(values) >= 2: | ||||
|           addressList = [] | ||||
|           for addr in ''.join(values[0].split()).split(','): | ||||
|             try: | ||||
|               # see if it's an IP address | ||||
|               addressList.append(str(ipaddress.ip_address(unicode(addr))).lower()) | ||||
|             except ValueError: | ||||
|               # see if it's a MAC address | ||||
|               if re.match(macAddrRegex, addr): | ||||
|                 # prepend _ temporarily to distinguish a mac address | ||||
|                 addressList.append("_{}".format(addr.replace('-', ':').lower())) | ||||
|               else: | ||||
|                 eprint('"{}" is not a valid IP or MAC address, ignoring'.format(ip)) | ||||
|           hostName = values[1] | ||||
|           tagReq = values[2] if ((len(values) >= 3) and (len(values[2]) > 0)) else UNSPECIFIED_TAG | ||||
|           if (len(addressList) > 0) and (len(hostName) > 0): | ||||
|             tagListMap[tagReq][HOST_LIST_IDX][hostName].extend(addressList) | ||||
|           else: | ||||
|             eprint('"{}" is not formatted correctly, ignoring'.format(line)) | ||||
|         else: | ||||
|           eprint('"{}" is not formatted correctly, ignoring'.format(line)) | ||||
|  | ||||
|       # handle mixed entries from the JSON-formatted file | ||||
|       for entry in mixedEntries: | ||||
|  | ||||
|         # the entry must at least contain type, address, name; may optionally contain tag | ||||
|         if (isinstance(entry, dict) and | ||||
|             all(key in entry for key in (JSON_MAP_KEY_TYPE, JSON_MAP_KEY_NAME, JSON_MAP_KEY_ADDR)) and | ||||
|             entry[JSON_MAP_KEY_TYPE] in (JSON_MAP_TYPE_SEGMENT, JSON_MAP_TYPE_HOST) and | ||||
|             (len(entry[JSON_MAP_KEY_NAME]) > 0) and | ||||
|             (len(entry[JSON_MAP_KEY_ADDR]) > 0)): | ||||
|  | ||||
|           addressList = [] | ||||
|           networkList = [] | ||||
|  | ||||
|           tagReq = entry[JSON_MAP_KEY_TAG] if (JSON_MAP_KEY_TAG in entry) and (len(entry[JSON_MAP_KEY_TAG]) > 0) else UNSPECIFIED_TAG | ||||
|  | ||||
|           # account for comma-separated multiple addresses per 'address' value | ||||
|           for addr in ''.join(entry[JSON_MAP_KEY_ADDR].split()).split(','): | ||||
|  | ||||
|             if (entry[JSON_MAP_KEY_TYPE] == JSON_MAP_TYPE_SEGMENT): | ||||
|               # potentially interpret address as a CIDR-formatted subnet | ||||
|               try: | ||||
|                 networkList.append(str(ipaddress.ip_network(unicode(addr))).lower() if ('/' in addr) else str(ipaddress.ip_address(unicode(addr))).lower()) | ||||
|               except ValueError: | ||||
|                 eprint('"{}" is not a valid IP address, ignoring'.format(addr)) | ||||
|  | ||||
|             else: | ||||
|               # should be an IP or MAC address | ||||
|               try: | ||||
|                 # see if it's an IP address | ||||
|                 addressList.append(str(ipaddress.ip_address(unicode(addr))).lower()) | ||||
|               except ValueError: | ||||
|                 # see if it's a MAC address | ||||
|                 if re.match(macAddrRegex, addr): | ||||
|                   # prepend _ temporarily to distinguish a mac address | ||||
|                   addressList.append("_{}".format(addr.replace('-', ':').lower())) | ||||
|                 else: | ||||
|                   eprint('"{}" is not a valid IP or MAC address, ignoring'.format(ip)) | ||||
|  | ||||
|           if (len(networkList) > 0): | ||||
|             tagListMap[tagReq][SEGMENT_LIST_IDX][entry[JSON_MAP_KEY_NAME]].extend(networkList) | ||||
|  | ||||
|           if (len(addressList) > 0): | ||||
|             tagListMap[tagReq][HOST_LIST_IDX][entry[JSON_MAP_KEY_NAME]].extend(addressList) | ||||
|  | ||||
|       # go through the lists of segments/hosts, which will now be organized by required tag first, then | ||||
|       # segment/host name, then the list of addresses | ||||
|       for tag, nameMaps in tagListMap.iteritems(): | ||||
|         print("", file=outFile) | ||||
|  | ||||
|         # if a tag name is specified, print the IF statement verifying the tag's presence | ||||
|         if tag != UNSPECIFIED_TAG: | ||||
|           print('  if ("{}" in [tags]) {{'.format(tag), file=outFile) | ||||
|         try: | ||||
|  | ||||
|           # for the host names(s) to be checked, create two filters, one for source IP|MAC and one for dest IP|MAC | ||||
|           for hostName, addrList in nameMaps[HOST_LIST_IDX].iteritems(): | ||||
|  | ||||
|             # ip addresses mapped to hostname | ||||
|             ipList = list(set([a for a in addrList if not a.startswith('_')])) | ||||
|             if (len(ipList) >= 1): | ||||
|               for source in ['orig', 'resp']: | ||||
|                 filterId += 1 | ||||
|                 fieldName = "{}_h".format(source) | ||||
|                 newFieldName = "{}_hostname".format(source) | ||||
|                 print("", file=outFile) | ||||
|                 print('    if ([zeek][{}]) and ({}) {{ '.format(fieldName, ' or '.join(['([zeek][{}] == "{}")'.format(fieldName, ip) for ip in ipList])), file=outFile) | ||||
|                 print('      mutate {{ id => "mutate_add_autogen_{}_ip_hostname_{}"'.format(source, filterId), file=outFile) | ||||
|                 print('        add_field => {{ "[zeek][{}]" => "{}" }}'.format(newFieldName, hostName), file=outFile) | ||||
|                 print("      }", file=outFile) | ||||
|                 print("    }", file=outFile) | ||||
|                 addedFields.add("[zeek][{}]".format(newFieldName)) | ||||
|  | ||||
|             # mac addresses mapped to hostname | ||||
|             macList = list(set([a for a in addrList if a.startswith('_')])) | ||||
|             if (len(macList) >= 1): | ||||
|               for source in ['orig', 'resp']: | ||||
|                 filterId += 1 | ||||
|                 fieldName = "{}_l2_addr".format(source) | ||||
|                 newFieldName = "{}_hostname".format(source) | ||||
|                 print("", file=outFile) | ||||
|                 print('    if ([zeek][{}]) and ({}) {{ '.format(fieldName, ' or '.join(['([zeek][{}] == "{}")'.format(fieldName, mac[1:]) for mac in macList])), file=outFile) | ||||
|                 print('      mutate {{ id => "mutate_add_autogen_{}_mac_hostname_{}"'.format(source, filterId), file=outFile) | ||||
|                 print('        add_field => {{ "[zeek][{}]" => "{}" }}'.format(newFieldName, hostName), file=outFile) | ||||
|                 print("      }", file=outFile) | ||||
|                 print("    }", file=outFile) | ||||
|                 addedFields.add("[zeek][{}]".format(newFieldName)) | ||||
|  | ||||
|           # for the segment(s) to be checked, create two cidr filters, one for source IP and one for dest IP | ||||
|           for segmentName, ipList in nameMaps[SEGMENT_LIST_IDX].iteritems(): | ||||
|             ipList = list(set(ipList)) | ||||
|             for source in ['orig', 'resp']: | ||||
|               filterId += 1 | ||||
|               # ip addresses/ranges mapped to network segment names | ||||
|               fieldName = "{}_h".format(source) | ||||
|               newFieldName = "{}_segment".format(source) | ||||
|               print("", file=outFile) | ||||
|               print("    if ([zeek][{}]) {{ cidr {{".format(fieldName), file=outFile) | ||||
|               print('      id => "cidr_autogen_{}_segment_{}"'.format(source, filterId), file=outFile) | ||||
|               print('      address => [ "%{{[zeek][{}]}}" ]'.format(fieldName), file=outFile) | ||||
|               print('      network => [ {} ]'.format(', '.join('"{}"'.format(ip) for ip in ipList)), file=outFile) | ||||
|               print('      add_tag => [ "{}" ]'.format(segmentName), file=outFile) | ||||
|               print('      add_field => {{ "[zeek][{}]" => "{}" }}'.format(newFieldName, segmentName), file=outFile) | ||||
|               print("    } }", file=outFile) | ||||
|               addedFields.add("[zeek][{}]".format(newFieldName)) | ||||
|  | ||||
|         finally: | ||||
|           # if a tag name is specified, close the IF statement verifying the tag's presence | ||||
|           if tag != UNSPECIFIED_TAG: | ||||
|             print("", file=outFile) | ||||
|             print('  }} # end (if "{}" in [tags])'.format(tag), file=outFile) | ||||
|  | ||||
|     finally: | ||||
|       # deduplicate any added fields | ||||
|       if addedFields: | ||||
|         print("", file=outFile) | ||||
|         print('  # deduplicate any added fields', file=outFile) | ||||
|         for field in list(itertools.product(['orig', 'resp'], ['hostname', 'segment'])): | ||||
|           newFieldName = "[zeek][{}_{}]".format(field[0], field[1]) | ||||
|           if newFieldName in addedFields: | ||||
|             print("", file=outFile) | ||||
|             print('  if ({}) {{ '.format(newFieldName), file=outFile) | ||||
|             print('    ruby {{ id => "ruby{}deduplicate"'.format(''.join(c for c, _ in itertools.groupby(re.sub('[^0-9a-zA-Z]+', '_', newFieldName)))), file=outFile) | ||||
|             print('      code => "', file=outFile) | ||||
|             print("        fieldVals = event.get('{}')".format(newFieldName), file=outFile) | ||||
|             print("        if fieldVals.kind_of?(Array) then event.set('{}', fieldVals.uniq) end".format(newFieldName), file=outFile) | ||||
|             print('      "', file=outFile) | ||||
|             print('  } }', file=outFile) | ||||
|  | ||||
|       # close out filter with ending } | ||||
|       print("", file=outFile) | ||||
|       print('} # end Filter', file=outFile) | ||||
|  | ||||
|     if outFile is not sys.stdout: | ||||
|       outFile.close() | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|   main() | ||||
							
								
								
									
										156
									
								
								Vagrant/resources/malcolm/logstash/scripts/ja3_build_list.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										156
									
								
								Vagrant/resources/malcolm/logstash/scripts/ja3_build_list.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,156 @@ | ||||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| from __future__ import print_function | ||||
|  | ||||
| import argparse | ||||
| import datetime | ||||
| import hashlib | ||||
| import json | ||||
| import os | ||||
| import platform | ||||
| import pprint | ||||
| import re | ||||
| import requests | ||||
| import string | ||||
| import sys | ||||
| import yaml | ||||
| from collections import defaultdict | ||||
|  | ||||
| ################################################################################################### | ||||
| debug = False | ||||
| PY3 = (sys.version_info.major >= 3) | ||||
| scriptName = os.path.basename(__file__) | ||||
| scriptPath = os.path.dirname(os.path.realpath(__file__)) | ||||
| origPath = os.getcwd() | ||||
|  | ||||
| ################################################################################################### | ||||
| if not PY3: | ||||
|   if hasattr(__builtins__, 'raw_input'): input = raw_input | ||||
|  | ||||
| try: | ||||
|   FileNotFoundError | ||||
| except NameError: | ||||
|   FileNotFoundError = IOError | ||||
|  | ||||
| ################################################################################################### | ||||
| # print to stderr | ||||
| def eprint(*args, **kwargs): | ||||
|   print(*args, file=sys.stderr, **kwargs) | ||||
|  | ||||
| ################################################################################################### | ||||
| # convenient boolean argument parsing | ||||
| def str2bool(v): | ||||
|   if v.lower() in ('yes', 'true', 't', 'y', '1'): | ||||
|     return True | ||||
|   elif v.lower() in ('no', 'false', 'f', 'n', '0'): | ||||
|     return False | ||||
|   else: | ||||
|     raise argparse.ArgumentTypeError('Boolean value expected.') | ||||
|  | ||||
| ################################################################################################### | ||||
| # main | ||||
| def main(): | ||||
|   global debug | ||||
|  | ||||
|   parser = argparse.ArgumentParser(description=scriptName, add_help=False, usage='{} <arguments>'.format(scriptName)) | ||||
|   parser.add_argument('-v', '--verbose', dest='debug', type=str2bool, nargs='?', const=True, default=False, help="Verbose output") | ||||
|   parser.add_argument('-o', '--output', required=True, dest='output', metavar='<STR>', type=str, default='', help='Output file') | ||||
|   try: | ||||
|     parser.error = parser.exit | ||||
|     args = parser.parse_args() | ||||
|   except SystemExit: | ||||
|     parser.print_help() | ||||
|     exit(2) | ||||
|  | ||||
|   debug = args.debug | ||||
|   if debug: | ||||
|     eprint(os.path.join(scriptPath, scriptName)) | ||||
|     eprint("Arguments: {}".format(sys.argv[1:])) | ||||
|     eprint("Arguments: {}".format(args)) | ||||
|   else: | ||||
|     sys.tracebacklimit = 0 | ||||
|  | ||||
|   ja3Map = defaultdict(list) | ||||
|   fingerprint = None | ||||
|  | ||||
|   urls = ['https://ja3er.com/getAllUasJson'] | ||||
|   for url in urls: | ||||
|     try: | ||||
|       for fingerprint in requests.get(url).json(): | ||||
|         if ('md5' in fingerprint) and fingerprint['md5'] and ('User-Agent' in fingerprint) and fingerprint['User-Agent']: | ||||
|           ja3Map[fingerprint['md5']].append(fingerprint['User-Agent'].strip('"').strip("'")) | ||||
|     except Exception as e: | ||||
|       eprint('"{}" raised for "{}"'.format(str(e), fingerprint)) | ||||
|  | ||||
|   try: | ||||
|     url = 'https://raw.githubusercontent.com/LeeBrotherston/tls-fingerprinting/master/fingerprints/fingerprints.json' | ||||
|     keys = ['record_tls_version', 'ciphersuite', 'extensions', 'e_curves', 'ec_point_fmt'] | ||||
|     for fingerprint in [x for x in requests.get(url).text.splitlines() if (len(x) > 0) and (not x.startswith('#'))]: | ||||
|       try: | ||||
|         values = list() | ||||
|         tmpMap = defaultdict(str) | ||||
|         tmpMap.update(json.loads(fingerprint)) | ||||
|         for key in keys: | ||||
|           values.append('-'.join([str(int(x, 0)) for x in tmpMap[key].split()])) | ||||
|         if PY3: | ||||
|           ja3Map[hashlib.md5(','.join(values).encode()).hexdigest()].extend(tmpMap['desc'].strip('"').strip("'").split(' / ')) | ||||
|         else: | ||||
|           ja3Map[hashlib.md5(','.join(values)).hexdigest()].extend(tmpMap['desc'].strip('"').strip("'").split(' / ')) | ||||
|       except Exception as e: | ||||
|         eprint('"{}" raised for "{}"'.format(str(e), fingerprint)) | ||||
|   except Exception as e: | ||||
|     eprint('"{}" raised for "{}"'.format(str(e), fingerprint)) | ||||
|  | ||||
|   urls = ['https://raw.githubusercontent.com/trisulnsm/ja3prints/master/ja3fingerprint.json'] | ||||
|   for url in urls: | ||||
|     try: | ||||
|       for fingerprint in [x for x in requests.get(url).text.splitlines() if (len(x) > 0) and (not x.startswith('#'))]: | ||||
|         try: | ||||
|           values = list() | ||||
|           tmpMap = defaultdict(str) | ||||
|           tmpMap.update(json.loads(fingerprint)) | ||||
|           ja3Map[tmpMap['ja3_hash'].strip()].append(tmpMap['desc'].strip('"').strip("'")) | ||||
|         except Exception as e: | ||||
|           eprint('"{}" raised for "{}"'.format(str(e), fingerprint)) | ||||
|     except Exception as e: | ||||
|       eprint('"{}" raised for "{}"'.format(str(e), fingerprint)) | ||||
|  | ||||
|   # this one has desc and ja3_hash backwards from the previous one | ||||
|   urls = ['https://raw.githubusercontent.com/trisulnsm/ja3prints/master/newprints.json'] | ||||
|   for url in urls: | ||||
|     try: | ||||
|       for fingerprint in [x for x in requests.get(url).text.splitlines() if (len(x) > 0) and (not x.startswith('#'))]: | ||||
|         try: | ||||
|           values = list() | ||||
|           tmpMap = defaultdict(str) | ||||
|           tmpMap.update(json.loads(fingerprint)) | ||||
|           ja3Map[tmpMap['desc'].strip()].append(tmpMap['ja3_hash'].strip('"').strip("'")) | ||||
|         except Exception as e: | ||||
|           eprint('"{}" raised for "{}"'.format(str(e), fingerprint)) | ||||
|     except Exception as e: | ||||
|       eprint('"{}" raised for "{}"'.format(str(e), fingerprint)) | ||||
|  | ||||
|   # this one is csv (and overlaps the previous one a lot) | ||||
|   try: | ||||
|     url = 'https://raw.githubusercontent.com/salesforce/ja3/master/lists/osx-nix-ja3.csv' | ||||
|     for fingerprint in [x for x in requests.get(url).text.splitlines() if (len(x) > 0) and (not x.startswith('#'))]: | ||||
|       vals = ' '.join(fingerprint.split()).split(',', 1) | ||||
|       if (len(vals) == 2) and (len(vals[0]) == 32): | ||||
|         ja3Map[vals[0].strip()].append(vals[1].strip('"').strip("'")) | ||||
|   except Exception as e: | ||||
|     eprint('"{}" raised for "{}"'.format(str(e), fingerprint)) | ||||
|  | ||||
|   finalMap = dict() | ||||
|   for k, v in ja3Map.items(): | ||||
|     if (len(k) == 32) and all(c in string.hexdigits for c in k): | ||||
|       finalMap[k] = list(set([element.strip('"').strip("'").strip() for element in v])) | ||||
|  | ||||
|   with open(args.output, 'w+') as outfile: | ||||
|     if PY3: | ||||
|       yaml.dump(finalMap, outfile) | ||||
|     else: | ||||
|       yaml.safe_dump(finalMap, outfile, default_flow_style=False) | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|   main() | ||||
							
								
								
									
										89
									
								
								Vagrant/resources/malcolm/logstash/scripts/logstash-start.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										89
									
								
								Vagrant/resources/malcolm/logstash/scripts/logstash-start.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| #!/usr/bin/env bash | ||||
|  | ||||
| # Copyright (c) 2021 Battelle Energy Alliance, LLC.  All rights reserved. | ||||
|  | ||||
| set -e | ||||
|  | ||||
| # if any pipelines are volume-mounted inside this docker container, they should belong to subdirectories under this path | ||||
| HOST_PIPELINES_DIR="/usr/share/logstash/malcolm-pipelines.available" | ||||
|  | ||||
| # runtime pipelines parent directory | ||||
| export PIPELINES_DIR="/usr/share/logstash/malcolm-pipelines" | ||||
|  | ||||
| # runtime pipeliens configuration file | ||||
| export PIPELINES_CFG="/usr/share/logstash/config/pipelines.yml" | ||||
|  | ||||
| # for each pipeline in /usr/share/logstash/malcolm-pipelines, append the contents of this file to the dynamically-generated | ||||
| # pipeline section in pipelines.yml (then delete 00_config.conf before starting) | ||||
| export PIPELINE_EXTRA_CONF_FILE="00_config.conf" | ||||
|  | ||||
| # files defining IP->host and MAC->host mapping | ||||
| INPUT_CIDR_MAP="/usr/share/logstash/config/cidr-map.txt" | ||||
| INPUT_HOST_MAP="/usr/share/logstash/config/host-map.txt" | ||||
| INPUT_MIXED_MAP="/usr/share/logstash/config/net-map.json" | ||||
|  | ||||
| # the name of the enrichment pipeline subdirectory under $PIPELINES_DIR | ||||
| ENRICHMENT_PIPELINE=${LOGSTASH_ENRICHMENT_PIPELINE:-"enrichment"} | ||||
|  | ||||
| # the name of the pipeline(s) to which input will send logs for parsing (comma-separated list, no quotes) | ||||
| PARSE_PIPELINE_ADDRESSES=${LOGSTASH_PARSE_PIPELINE_ADDRESSES:-"zeek-parse"} | ||||
|  | ||||
| # pipeline addresses for forwarding from Logstash to Elasticsearch (both "internal" and "external" pipelines) | ||||
| export ELASTICSEARCH_PIPELINE_ADDRESS_INTERNAL=${LOGSTASH_ELASTICSEARCH_PIPELINE_ADDRESS_INTERNAL:-"internal-es"} | ||||
| export ELASTICSEARCH_PIPELINE_ADDRESS_EXTERNAL=${LOGSTASH_ELASTICSEARCH_PIPELINE_ADDRESS_EXTERNAL:-"external-es"} | ||||
| ELASTICSEARCH_OUTPUT_PIPELINE_ADDRESSES=${LOGSTASH_ELASTICSEARCH_OUTPUT_PIPELINE_ADDRESSES:-"$ELASTICSEARCH_PIPELINE_ADDRESS_INTERNAL,$ELASTICSEARCH_PIPELINE_ADDRESS_EXTERNAL"} | ||||
|  | ||||
| # ip-to-segment-logstash.py translate $INPUT_CIDR_MAP, $INPUT_HOST_MAP, $INPUT_MIXED_MAP into this logstash filter file | ||||
| NETWORK_MAP_OUTPUT_FILTER="$PIPELINES_DIR"/"$ENRICHMENT_PIPELINE"/16_host_segment_filters.conf | ||||
|  | ||||
| #################################################################################################################### | ||||
|  | ||||
| # copy over pipeline filters from host-mapped volumes (if any) into their final resting places | ||||
| find "$HOST_PIPELINES_DIR" -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null | sort -z | \ | ||||
|   xargs -0 -n 1 -I '{}' bash -c ' | ||||
|   PIPELINE_NAME="$(basename "{}")" | ||||
|   PIPELINES_DEST_DIR="$PIPELINES_DIR"/"$PIPELINE_NAME" | ||||
|   mkdir -p "$PIPELINES_DEST_DIR" | ||||
|   cp -f "{}"/* "$PIPELINES_DEST_DIR"/ | ||||
| ' | ||||
|  | ||||
| # dynamically generate final pipelines.yml configuration file from all of the pipeline directories | ||||
| > "$PIPELINES_CFG" | ||||
| find "$PIPELINES_DIR" -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null | sort -z | \ | ||||
|   xargs -0 -n 1 -I '{}' bash -c ' | ||||
|   PIPELINE_NAME="$(basename "{}")" | ||||
|   PIPELINE_ADDRESS_NAME="$(cat "{}"/*.conf | sed -e "s/:[\}]*.*\(}\)/\1/" | envsubst | grep -P "\baddress\s*=>" | awk "{print \$3}" | sed "s/[\"'']//g" | head -n 1)" | ||||
|   if [[ -n "$ES_EXTERNAL_HOSTS" ]] || [[ "$PIPELINE_ADDRESS_NAME" != "$ELASTICSEARCH_PIPELINE_ADDRESS_EXTERNAL" ]]; then | ||||
|     echo "- pipeline.id: malcolm-$PIPELINE_NAME"       >> "$PIPELINES_CFG" | ||||
|     echo "  path.config: "{}""                         >> "$PIPELINES_CFG" | ||||
|     cat "{}"/"$PIPELINE_EXTRA_CONF_FILE" 2>/dev/null   >> "$PIPELINES_CFG" | ||||
|     rm -f "{}"/"$PIPELINE_EXTRA_CONF_FILE" | ||||
|     echo                                               >> "$PIPELINES_CFG" | ||||
|     echo                                               >> "$PIPELINES_CFG" | ||||
|   fi | ||||
| ' | ||||
|  | ||||
| # create filters for network segment and host mapping in the enrichment directory | ||||
| rm -f "$NETWORK_MAP_OUTPUT_FILTER" | ||||
| /usr/local/bin/ip-to-segment-logstash.py --mixed "$INPUT_MIXED_MAP" --segment "$INPUT_CIDR_MAP" --host "$INPUT_HOST_MAP" -o "$NETWORK_MAP_OUTPUT_FILTER" | ||||
|  | ||||
| if [[ -z "$ES_EXTERNAL_HOSTS" ]]; then | ||||
|   # external ES host destination is not specified, remove external destination from enrichment pipeline output | ||||
|   ELASTICSEARCH_OUTPUT_PIPELINE_ADDRESSES="$(echo "$ELASTICSEARCH_OUTPUT_PIPELINE_ADDRESSES" | sed "s/,[[:blank:]]*$ELASTICSEARCH_PIPELINE_ADDRESS_EXTERNAL//")" | ||||
| fi | ||||
|  | ||||
| # insert quotes around the elasticsearch parsing and output pipeline list | ||||
| MALCOLM_PARSE_PIPELINE_ADDRESSES=$(printf '"%s"\n' "${PARSE_PIPELINE_ADDRESSES//,/\",\"}") | ||||
| MALCOLM_ELASTICSEARCH_OUTPUT_PIPELINES=$(printf '"%s"\n' "${ELASTICSEARCH_OUTPUT_PIPELINE_ADDRESSES//,/\",\"}") | ||||
|  | ||||
| # do a manual global replace on these particular values in the config files, as Logstash doesn't like the environment variables with quotes in them | ||||
| find "$PIPELINES_DIR" -type f -name "*.conf" -exec sed -i "s/_MALCOLM_ELASTICSEARCH_OUTPUT_PIPELINES_/${MALCOLM_ELASTICSEARCH_OUTPUT_PIPELINES}/g" "{}" \; 2>/dev/null | ||||
| find "$PIPELINES_DIR" -type f -name "*.conf" -exec sed -i "s/_MALCOLM_PARSE_PIPELINE_ADDRESSES_/${MALCOLM_PARSE_PIPELINE_ADDRESSES}/g" "{}" \; 2>/dev/null | ||||
|  | ||||
| # import trusted CA certificates if necessary | ||||
| /usr/local/bin/jdk-cacerts-auto-import.sh || true | ||||
|  | ||||
| # start logstash (adapted from docker-entrypoint) | ||||
| env2yaml /usr/share/logstash/config/logstash.yml | ||||
| export LS_JAVA_OPTS="-Dls.cgroup.cpuacct.path.override=/ -Dls.cgroup.cpu.path.override=/ $LS_JAVA_OPTS" | ||||
| exec logstash | ||||
		Reference in New Issue
	
	Block a user