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 '''