mirror of
https://github.com/wahyd4/links.git
synced 2026-08-09 05:06:16 +10:00
87 lines
2.9 KiB
Python
87 lines
2.9 KiB
Python
import socket
|
|
import struct
|
|
import logging
|
|
from .base import Finding
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
CHECK_NAME = 'dns_resolver'
|
|
TIMEOUT = 5
|
|
|
|
|
|
def _build_dns_query(domain: str) -> bytes:
|
|
"""Build a minimal DNS A query packet."""
|
|
header = struct.pack('>HHHHHH', 0xAAAA, 0x0100, 1, 0, 0, 0)
|
|
parts = domain.encode().split(b'.')
|
|
question = b''.join(bytes([len(p)]) + p for p in parts) + b'\x00'
|
|
question += struct.pack('>HH', 1, 1) # type A, class IN
|
|
return header + question
|
|
|
|
|
|
def _parse_dns_response(data: bytes) -> dict:
|
|
"""Return basic info from a DNS response header."""
|
|
if len(data) < 12:
|
|
return {'error': 'response too short'}
|
|
txid, flags, qdcount, ancount, nscount, arcount = struct.unpack('>HHHHHH', data[:12])
|
|
rcode = flags & 0x000F
|
|
return {
|
|
'txid': txid,
|
|
'flags': flags,
|
|
'rcode': rcode,
|
|
'ancount': ancount,
|
|
}
|
|
|
|
|
|
def run(profile) -> list:
|
|
host = profile.public_ip
|
|
raw = {'public_ip': host}
|
|
|
|
query = _build_dns_query('google.com')
|
|
try:
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
sock.settimeout(TIMEOUT)
|
|
sock.sendto(query, (host, 53))
|
|
data, _ = sock.recvfrom(512)
|
|
sock.close()
|
|
|
|
parsed = _parse_dns_response(data)
|
|
raw['response'] = parsed
|
|
|
|
if parsed.get('rcode') == 0 and parsed.get('ancount', 0) > 0:
|
|
return [Finding(
|
|
check_name=CHECK_NAME,
|
|
severity='critical',
|
|
title=f'Open DNS resolver detected on {host}:53',
|
|
detail=(
|
|
'Your public IP responds to recursive DNS queries from external hosts. '
|
|
'This can be abused for DNS amplification attacks. '
|
|
'Note: NAT hairpin may cause false positive — verify from off-LAN.'
|
|
),
|
|
raw=raw,
|
|
)]
|
|
else:
|
|
return [Finding(
|
|
check_name=CHECK_NAME,
|
|
severity='ok',
|
|
title='DNS port 53 does not appear to be an open resolver',
|
|
detail=f'DNS query to {host}:53 returned rcode={parsed.get("rcode")} with {parsed.get("ancount", 0)} answers.',
|
|
raw=raw,
|
|
)]
|
|
except socket.timeout:
|
|
return [Finding(
|
|
check_name=CHECK_NAME,
|
|
severity='ok',
|
|
title='DNS port 53 timed out (not an open resolver)',
|
|
detail=f'No response from {host}:53 within {TIMEOUT}s. Port is likely closed or filtered.',
|
|
raw={**raw, 'error': 'timeout'},
|
|
)]
|
|
except Exception as e:
|
|
logger.warning(f'DNS check error: {e}')
|
|
return [Finding(
|
|
check_name=CHECK_NAME,
|
|
severity='info',
|
|
title='DNS resolver check failed',
|
|
detail=f'Could not complete DNS probe to {host}:53 — {e}',
|
|
raw={**raw, 'error': str(e)},
|
|
)]
|