dnsrecon/providers/dns_provider.py
overcuriousity ce0e11cf0b progress
2025-09-10 15:17:17 +02:00

338 lines
12 KiB
Python

"""
DNS resolution provider for DNSRecon.
Discovers domain relationships through DNS record analysis.
"""
import socket
import dns.resolver
import dns.reversename
from typing import List, Dict, Any, Tuple, Optional
from .base_provider import BaseProvider
from core.graph_manager import RelationshipType, NodeType
class DNSProvider(BaseProvider):
"""
Provider for standard DNS resolution and reverse DNS lookups.
Discovers domain-to-IP and IP-to-domain relationships through DNS records.
"""
def __init__(self):
"""Initialize DNS provider with appropriate rate limiting."""
super().__init__(
name="dns",
rate_limit=100, # DNS queries can be faster
timeout=10
)
# Configure DNS resolver
self.resolver = dns.resolver.Resolver()
self.resolver.timeout = 5
self.resolver.lifetime = 10
def get_name(self) -> str:
"""Return the provider name."""
return "dns"
def is_available(self) -> bool:
"""DNS is always available - no API key required."""
return True
def query_domain(self, domain: str) -> List[Tuple[str, str, RelationshipType, float, Dict[str, Any]]]:
"""
Query DNS records for the domain to discover relationships.
Args:
domain: Domain to investigate
Returns:
List of relationships discovered from DNS analysis
"""
if not self._is_valid_domain(domain):
return []
relationships = []
# Query A records
relationships.extend(self._query_a_records(domain))
# Query AAAA records (IPv6)
relationships.extend(self._query_aaaa_records(domain))
# Query CNAME records
relationships.extend(self._query_cname_records(domain))
# Query MX records
relationships.extend(self._query_mx_records(domain))
# Query NS records
relationships.extend(self._query_ns_records(domain))
return relationships
def query_ip(self, ip: str) -> List[Tuple[str, str, RelationshipType, float, Dict[str, Any]]]:
"""
Query reverse DNS for the IP address.
Args:
ip: IP address to investigate
Returns:
List of relationships discovered from reverse DNS
"""
if not self._is_valid_ip(ip):
return []
relationships = []
try:
# Perform reverse DNS lookup
reverse_name = dns.reversename.from_address(ip)
response = self.resolver.resolve(reverse_name, 'PTR')
for ptr_record in response:
hostname = str(ptr_record).rstrip('.')
if self._is_valid_domain(hostname):
raw_data = {
'query_type': 'PTR',
'ip_address': ip,
'hostname': hostname,
'ttl': response.ttl
}
relationships.append((
ip,
hostname,
RelationshipType.A_RECORD, # Reverse relationship
RelationshipType.A_RECORD.default_confidence,
raw_data
))
self.log_relationship_discovery(
source_node=ip,
target_node=hostname,
relationship_type=RelationshipType.A_RECORD,
confidence_score=RelationshipType.A_RECORD.default_confidence,
raw_data=raw_data,
discovery_method="reverse_dns_lookup"
)
except Exception as e:
self.logger.logger.debug(f"Reverse DNS lookup failed for {ip}: {e}")
return relationships
def _query_a_records(self, domain: str) -> List[Tuple[str, str, RelationshipType, float, Dict[str, Any]]]:
"""Query A records for the domain."""
relationships = []
#if not DNS_AVAILABLE:
# return relationships
try:
response = self.resolver.resolve(domain, 'A')
for a_record in response:
ip_address = str(a_record)
raw_data = {
'query_type': 'A',
'domain': domain,
'ip_address': ip_address,
'ttl': response.ttl
}
relationships.append((
domain,
ip_address,
RelationshipType.A_RECORD,
RelationshipType.A_RECORD.default_confidence,
raw_data
))
self.log_relationship_discovery(
source_node=domain,
target_node=ip_address,
relationship_type=RelationshipType.A_RECORD,
confidence_score=RelationshipType.A_RECORD.default_confidence,
raw_data=raw_data,
discovery_method="dns_a_record"
)
except Exception as e:
self.logger.logger.debug(f"A record query failed for {domain}: {e}")
return relationships
def _query_aaaa_records(self, domain: str) -> List[Tuple[str, str, RelationshipType, float, Dict[str, Any]]]:
"""Query AAAA records (IPv6) for the domain."""
relationships = []
#if not DNS_AVAILABLE:
# return relationships
try:
response = self.resolver.resolve(domain, 'AAAA')
for aaaa_record in response:
ip_address = str(aaaa_record)
raw_data = {
'query_type': 'AAAA',
'domain': domain,
'ip_address': ip_address,
'ttl': response.ttl
}
relationships.append((
domain,
ip_address,
RelationshipType.A_RECORD, # Using same type for IPv6
RelationshipType.A_RECORD.default_confidence,
raw_data
))
self.log_relationship_discovery(
source_node=domain,
target_node=ip_address,
relationship_type=RelationshipType.A_RECORD,
confidence_score=RelationshipType.A_RECORD.default_confidence,
raw_data=raw_data,
discovery_method="dns_aaaa_record"
)
except Exception as e:
self.logger.logger.debug(f"AAAA record query failed for {domain}: {e}")
return relationships
def _query_cname_records(self, domain: str) -> List[Tuple[str, str, RelationshipType, float, Dict[str, Any]]]:
"""Query CNAME records for the domain."""
relationships = []
#if not DNS_AVAILABLE:
# return relationships
try:
response = self.resolver.resolve(domain, 'CNAME')
for cname_record in response:
target_domain = str(cname_record).rstrip('.')
if self._is_valid_domain(target_domain):
raw_data = {
'query_type': 'CNAME',
'source_domain': domain,
'target_domain': target_domain,
'ttl': response.ttl
}
relationships.append((
domain,
target_domain,
RelationshipType.CNAME_RECORD,
RelationshipType.CNAME_RECORD.default_confidence,
raw_data
))
self.log_relationship_discovery(
source_node=domain,
target_node=target_domain,
relationship_type=RelationshipType.CNAME_RECORD,
confidence_score=RelationshipType.CNAME_RECORD.default_confidence,
raw_data=raw_data,
discovery_method="dns_cname_record"
)
except Exception as e:
self.logger.logger.debug(f"CNAME record query failed for {domain}: {e}")
return relationships
def _query_mx_records(self, domain: str) -> List[Tuple[str, str, RelationshipType, float, Dict[str, Any]]]:
"""Query MX records for the domain."""
relationships = []
#if not DNS_AVAILABLE:
# return relationships
try:
response = self.resolver.resolve(domain, 'MX')
for mx_record in response:
mx_host = str(mx_record.exchange).rstrip('.')
if self._is_valid_domain(mx_host):
raw_data = {
'query_type': 'MX',
'domain': domain,
'mx_host': mx_host,
'priority': mx_record.preference,
'ttl': response.ttl
}
relationships.append((
domain,
mx_host,
RelationshipType.MX_RECORD,
RelationshipType.MX_RECORD.default_confidence,
raw_data
))
self.log_relationship_discovery(
source_node=domain,
target_node=mx_host,
relationship_type=RelationshipType.MX_RECORD,
confidence_score=RelationshipType.MX_RECORD.default_confidence,
raw_data=raw_data,
discovery_method="dns_mx_record"
)
except Exception as e:
self.logger.logger.debug(f"MX record query failed for {domain}: {e}")
return relationships
def _query_ns_records(self, domain: str) -> List[Tuple[str, str, RelationshipType, float, Dict[str, Any]]]:
"""Query NS records for the domain."""
relationships = []
#if not DNS_AVAILABLE:
# return relationships
try:
response = self.resolver.resolve(domain, 'NS')
for ns_record in response:
ns_host = str(ns_record).rstrip('.')
if self._is_valid_domain(ns_host):
raw_data = {
'query_type': 'NS',
'domain': domain,
'ns_host': ns_host,
'ttl': response.ttl
}
relationships.append((
domain,
ns_host,
RelationshipType.NS_RECORD,
RelationshipType.NS_RECORD.default_confidence,
raw_data
))
self.log_relationship_discovery(
source_node=domain,
target_node=ns_host,
relationship_type=RelationshipType.NS_RECORD,
confidence_score=RelationshipType.NS_RECORD.default_confidence,
raw_data=raw_data,
discovery_method="dns_ns_record"
)
except Exception as e:
self.logger.logger.debug(f"NS record query failed for {domain}: {e}")
return relationships