eris- Elasticsearch Recon Ingestion Scripts (ERIS) 🔎 |
git clone git://git.acid.vegas/eris.git |
Log | Files | Refs | Archive | README | LICENSE |
ingest_massdns.py (4664B)
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 async with aiofiles.open(input_path) as input_file: 46 47 # Cache the last document to avoid creating a new one for the same IP address 48 last = None 49 50 try: 51 # Read the input file line by line 52 async for line in input_file: 53 line = line.strip() 54 55 # Sentinel value to indicate the end of a process (for closing out a FIFO stream) 56 if line == '~eof': 57 yield last 58 break 59 60 # Skip empty lines (doubtful we will have any, but just in case) 61 if not line: 62 continue 63 64 # Split the line into its parts 65 parts = line.split() 66 67 # Ensure the line has at least 3 parts 68 if len(parts) < 3: 69 logging.warning(f'Invalid PTR record: {line}') 70 continue 71 72 # Split the PTR record into its parts 73 name, record_type, record = parts[0].rstrip('.'), parts[1], ' '.join(parts[2:]).rstrip('.') 74 75 # Do not index other records 76 if record_type != 'PTR': 77 continue 78 79 # Do not index PTR records that do not have a record 80 if not record: 81 continue 82 83 # Do not index PTR records that have the same record as the in-addr.arpa domain 84 if record == name: 85 continue 86 87 # Get the IP address from the in-addr.arpa domain 88 ip = '.'.join(name.replace('.in-addr.arpa', '').split('.')[::-1]) 89 90 # Check if we are still processing the same IP address 91 if last: 92 if ip == last['_id']: # This record is for the same IP address as the cached document 93 last_records = last['doc']['record'] 94 if record not in last_records: # Do not index duplicate records 95 last['doc']['record'].append(record) 96 continue 97 else: 98 yield last # Return the last document and start a new one 99 100 # Cache the document 101 last = { 102 '_op_type' : 'update', 103 '_id' : ip, 104 '_index' : default_index, 105 'doc' : { 106 'ip' : ip, 107 'record' : [record], 108 'seen' : time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) 109 }, 110 'doc_as_upsert' : True # Create the document if it does not exist 111 } 112 113 except Exception as e: 114 logging.error(f'Error processing data: {e}') 115 116 117 async def test(input_path: str): 118 ''' 119 Test the ingestion process 120 121 :param input_path: Path to the input file 122 ''' 123 124 async for document in process_data(input_path): 125 print(document) 126 127 128 129 if __name__ == '__main__': 130 import argparse 131 import asyncio 132 133 parser = argparse.ArgumentParser(description='Ingestor for ERIS') 134 parser.add_argument('input_path', help='Path to the input file or directory') 135 args = parser.parse_args() 136 137 asyncio.run(test(args.input_path)) 138 139 140 141 ''' 142 Deployment: 143 sudo apt-get install build-essential gcc make python3 python3-pip 144 pip install aiofiles aiohttp elasticsearch 145 git clone --depth 1 https://github.com/acidvegas/eris.git $HOME/eris 146 147 git clone --depth 1 https://github.com/blechschmidt/massdns.git $HOME/massdns && cd $HOME/massdns && make 148 curl -s https://public-dns.info/nameservers.txt | grep -v ':' > $HOME/massdns/nameservers.txt 149 while true; do python3 ./scripts/ptr.py | ./bin/massdns -r $HOME/massdns/nameservers.txt -t PTR --filter NOERROR -o S -w $HOME/eris/FIFO; done 150 151 152 Output: 153 0.6.229.47.in-addr.arpa. PTR 047-229-006-000.res.spectrum.com. 154 0.6.228.75.in-addr.arpa. PTR 0.sub-75-228-6.myvzw.com. 155 0.6.207.73.in-addr.arpa. PTR c-73-207-6-0.hsd1.ga.comcast.net. 156 157 158 Input: 159 { 160 '_id' : '47.229.6.0' 161 '_index' : 'eris-massdns', 162 '_source' : { 163 'ip' : '47.229.6.0', 164 'record' : ['047-229-006-000.res.spectrum.com'], # We will store as a list for IP addresses with multiple PTR records 165 'seen' : '2021-06-30T18:31:00Z' 166 } 167 } 168 169 170 Notes: 171 Why do some IP addresses return a A/CNAME from a PTR request 172 What is dns-servfail.net (Frequent CNAME response from PTR requests) 173 '''