eris- Elasticsearch Recon Ingestion Scripts (ERIS) 🔎 |
git clone git://git.acid.vegas/eris.git |
Log | Files | Refs | Archive | README | LICENSE |
ingest_massdns.py (5247B)
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 printf "\nsession required pam_limits.so" >> /etc/pam.d/su 144 printf "acidvegas hard nofile 65535\nacidvegas soft nofile 65535" >> /etc/security/limits.conf 145 echo "net.netfilter.nf_conntrack_max = 131072" >> /etc/sysctl.conf 146 echo "net.netfilter.nf_conntrack_udp_timeout = 30" >> /etc/sysctl.conf 147 echo "net.netfilter.nf_conntrack_udp_timeout_stream = 120" >> /etc/sysctl.conf 148 echo "net.netfilter.nf_conntrack_tcp_timeout_established = 300" >> /etc/sysctl.conf 149 sysctl -p 150 151 sudo apt-get install build-essential gcc make python3 python3-pip 152 pip install aiofiles aiohttp elasticsearch 153 git clone --depth 1 https://github.com/acidvegas/eris.git $HOME/eris 154 155 git clone --depth 1 https://github.com/blechschmidt/massdns.git $HOME/massdns && cd $HOME/massdns && make 156 wget -O $HOME/massdns/resolvers.txt https://raw.githubusercontent.com/trickest/resolvers/refs/heads/main/resolvers.txt 157 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 158 159 screen -S eris 160 python3 $HOME/eris/eris.py --massdns 161 162 163 164 Output: 165 0.6.229.47.in-addr.arpa. PTR 047-229-006-000.res.spectrum.com. 166 0.6.228.75.in-addr.arpa. PTR 0.sub-75-228-6.myvzw.com. 167 0.6.207.73.in-addr.arpa. PTR c-73-207-6-0.hsd1.ga.comcast.net. 168 169 170 Input: 171 { 172 '_id' : '47.229.6.0' 173 '_index' : 'eris-massdns', 174 '_source' : { 175 'ip' : '47.229.6.0', 176 'record' : ['047-229-006-000.res.spectrum.com'], # We will store as a list for IP addresses with multiple PTR records 177 'seen' : '2021-06-30T18:31:00Z' 178 } 179 } 180 181 182 Notes: 183 Why do some IP addresses return a A/CNAME from a PTR request 184 What is dns-servfail.net (Frequent CNAME response from PTR requests) 185 '''