eris

- Elasticsearch Recon Ingestion Scripts (ERIS) 🔎
git clone git://git.acid.vegas/eris.git
Log | Files | Refs | Archive | README | LICENSE

ingest_massdns.py (5116B)

      1 #!/usr/bin/env python
      2 # Elasticsearch Recon Ingestion Scripts (ERIS) - Developed by Acidvegas (https://git.acid.vegas/eris)
      3 # ingest_massdns.py
      4 
      5 import logging
      6 import time
      7 
      8 try:
      9 	import aiofiles
     10 except ImportError:
     11 	raise ImportError('Missing required \'aiofiles\' library. (pip install aiofiles)')
     12 
     13 
     14 # Set a default elasticsearch index if one is not provided
     15 default_index = 'eris-massdns'
     16 
     17 
     18 def construct_map() -> dict:
     19 	'''Construct the Elasticsearch index mapping for records'''
     20 
     21 	# Match on exact value or full text search
     22 	keyword_mapping = { 'type': 'text', 'fields': { 'keyword': { 'type': 'keyword', 'ignore_above': 256 } } }
     23 
     24 	# Construct the index mapping
     25 	mapping = {
     26 		'mappings': {
     27 			'properties': {
     28 				'ip'     : { 'type': 'ip' },
     29 				'record' : keyword_mapping,
     30 				'seen'   : { 'type': 'date' }
     31 			}
     32 		}
     33 	}
     34 
     35 	return mapping
     36 
     37 
     38 async def process_data(input_path: str):
     39 	'''
     40 	Read and process the input file
     41 
     42 	:param input_path: Path to the input file
     43 	'''
     44 
     45 	# Open the input file
     46 	async with aiofiles.open(input_path) as input_file:
     47 
     48 		# Cache the last document to avoid creating a new one for the same IP address
     49 		last = None
     50 
     51 		# Read the input file line by line
     52 		async for line in input_file:
     53 
     54 			# Strip whitespace
     55 			line = line.strip()
     56 
     57 			# Skip empty lines
     58 			if not line:
     59 				continue
     60 
     61 			# Sentinel value to indicate the end of a process (for closing out a FIFO stream)
     62 			if line == '~eof':
     63 				yield last
     64 				break
     65 
     66 			# Split the line into its parts
     67 			parts = line.split()
     68 
     69 			# Ensure the line has at least 3 parts
     70 			if len(parts) < 3:
     71 				logging.warning(f'Invalid PTR record: {line}')
     72 				continue
     73 
     74 			# Split the PTR record into its parts
     75 			name, record_type, record = parts[0].rstrip('.'), parts[1], ' '.join(parts[2:]).rstrip('.')
     76 
     77 			# Do not index other records
     78 			if record_type != 'PTR':
     79 				continue
     80 
     81 			# Do not index PTR records that do not have a record
     82 			if not record:
     83 				continue
     84 
     85 			# Do not index PTR records that have the same record as the in-addr.arpa domain
     86 			if record == name:
     87 				continue
     88 
     89 			# Get the IP address from the in-addr.arpa domain
     90 			ip = '.'.join(name.replace('.in-addr.arpa', '').split('.')[::-1])
     91 
     92 			# Check if we are still processing the same IP address
     93 			if last:
     94 				if ip == last['_id']: # This record is for the same IP address as the cached document
     95 					last_records = last['doc']['record']
     96 					if record not in last_records: # Do not index duplicate records
     97 						last['doc']['record'].append(record)
     98 					continue
     99 				else:
    100 					yield last # Return the last document and start a new one
    101 
    102 			# Cache the document
    103 			last = {
    104 				'_op_type' : 'update',
    105 				'_id'      : ip,
    106 				'_index'   : default_index,
    107 				'doc'      : {
    108 					'ip'     : ip,
    109 					'record' : [record],
    110 					'seen'   : time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
    111 				},
    112 				'doc_as_upsert' : True # Create the document if it does not exist
    113 			}
    114 
    115 
    116 async def test(input_path: str):
    117 	'''
    118 	Test the ingestion process
    119 
    120 	:param input_path: Path to the input file
    121 	'''
    122 
    123 	async for document in process_data(input_path):
    124 		print(document)
    125 
    126 
    127 
    128 if __name__ == '__main__':
    129 	import argparse
    130 	import asyncio
    131 
    132 	parser = argparse.ArgumentParser(description='Ingestor for ERIS')
    133 	parser.add_argument('input_path', help='Path to the input file or directory')
    134 	args = parser.parse_args()
    135 
    136 	asyncio.run(test(args.input_path))
    137 
    138 
    139 
    140 '''
    141 Deployment:
    142 	printf "\nsession    required   pam_limits.so" >> /etc/pam.d/su
    143 	printf "acidvegas    hard    nofile    65535\nacidvegas    soft    nofile    65535" >> /etc/security/limits.conf
    144 	echo "net.netfilter.nf_conntrack_max = 131072" >> /etc/sysctl.conf
    145 	echo "net.netfilter.nf_conntrack_udp_timeout = 30" >> /etc/sysctl.conf
    146 	echo "net.netfilter.nf_conntrack_udp_timeout_stream = 120" >> /etc/sysctl.conf
    147 	echo "net.netfilter.nf_conntrack_tcp_timeout_established = 300" >> /etc/sysctl.conf
    148 	sysctl -p
    149 
    150 	sudo apt-get install build-essential gcc make python3 python3-pip
    151 	pip install aiofiles aiohttp elasticsearch
    152 	git clone --depth 1 https://github.com/acidvegas/eris.git $HOME/eris
    153 
    154 	git clone --depth 1 https://github.com/blechschmidt/massdns.git $HOME/massdns && cd $HOME/massdns && make
    155 	wget -O $HOME/massdns/resolvers.txt https://raw.githubusercontent.com/trickest/resolvers/refs/heads/main/resolvers.txt
    156 	while true; do python3 ./scripts/ptr.py | ./bin/massdns -r $HOME/massdns/resolvers.txt -t PTR --filter NOERROR -s 5000 -o S -w $HOME/eris/FIFO; done
    157 
    158 	screen -S eris
    159 	python3 $HOME/eris/eris.py --massdns
    160 
    161 
    162 
    163 Output:
    164 	0.6.229.47.in-addr.arpa. PTR 047-229-006-000.res.spectrum.com.
    165 	0.6.228.75.in-addr.arpa. PTR 0.sub-75-228-6.myvzw.com.
    166 	0.6.207.73.in-addr.arpa. PTR c-73-207-6-0.hsd1.ga.comcast.net.
    167 
    168 
    169 Input:
    170 	{
    171 		'_id'     : '47.229.6.0'
    172 		'_index'  : 'eris-massdns',
    173 		'_source' : {
    174 			'ip'     : '47.229.6.0',
    175 			'record' : ['047-229-006-000.res.spectrum.com'], # We will store as a list for IP addresses with multiple PTR records
    176 			'seen'   : '2021-06-30T18:31:00Z'
    177 		}
    178 	}
    179 
    180 
    181 Notes:
    182 	Why do some IP addresses return a A/CNAME from a PTR request
    183 	What is dns-servfail.net (Frequent CNAME response from PTR requests)
    184 '''