dns-audit.sh 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. #!/usr/bin/env bash
  2. # net-ops :: macos/dns-audit.sh
  3. # Deep DNS forensics for macOS. Use when probe.sh shows rung 4 (dig) PASS
  4. # but rung 5 (dscacheutil) FAIL — that signature points at a hook in the
  5. # macOS resolver chain.
  6. set -u
  7. # shellcheck source=../_lib/redact.sh
  8. source "$(dirname "$0")/../_lib/redact.sh"
  9. parse_redact_flag "$@"
  10. maybe_redact_self "$@"
  11. echo "=== scutil --dns (FULL) ==="
  12. scutil --dns 2>/dev/null
  13. echo
  14. echo "=== /etc/resolver/* (per-domain DNS overrides — VPN clients use these) ==="
  15. if [[ -d /etc/resolver ]] && [[ -n "$(ls -A /etc/resolver 2>/dev/null)" ]]; then
  16. for f in /etc/resolver/*; do
  17. [[ -f "$f" ]] || continue
  18. echo "--- $f ---"
  19. echo " modified: $(stat -f '%Sm' "$f" 2>/dev/null || stat -c '%y' "$f" 2>/dev/null)"
  20. cat "$f" | sed 's/^/ /'
  21. done
  22. else
  23. echo "/etc/resolver/ empty or missing — no per-domain overrides."
  24. fi
  25. echo
  26. echo "=== Configuration profiles with DNS settings ==="
  27. profiles list -type configuration 2>/dev/null | head -40
  28. echo
  29. echo " (run 'sudo profiles show -type configuration' for full payloads)"
  30. echo
  31. echo "=== /etc/hosts (non-comment lines) ==="
  32. grep -vE '^\s*(#|$)' /etc/hosts 2>/dev/null || echo " (no custom entries)"
  33. echo
  34. echo "=== /etc/resolv.conf (legacy, usually a stub on macOS) ==="
  35. if [[ -f /etc/resolv.conf ]]; then
  36. cat /etc/resolv.conf
  37. else
  38. echo " not present"
  39. fi
  40. echo
  41. echo "=== mDNSResponder state ==="
  42. if pgrep -x mDNSResponder >/dev/null; then
  43. pid=$(pgrep -x mDNSResponder | head -1)
  44. echo "PID: $pid"
  45. ps -o pid,etime,command -p "$pid" 2>/dev/null
  46. fi
  47. echo
  48. echo "=== Network services priority order ==="
  49. networksetup -listnetworkserviceorder 2>/dev/null | head -30
  50. echo
  51. echo "=== DNS servers per active service ==="
  52. networksetup -listallnetworkservices 2>/dev/null | tail -n +2 | while read -r svc; do
  53. [[ "$svc" == \** ]] && continue # disabled
  54. dns=$(networksetup -getdnsservers "$svc" 2>/dev/null)
  55. echo " $svc: $dns"
  56. done
  57. echo
  58. echo "=== Search domains per active service ==="
  59. networksetup -listallnetworkservices 2>/dev/null | tail -n +2 | while read -r svc; do
  60. [[ "$svc" == \** ]] && continue
  61. sd=$(networksetup -getsearchdomains "$svc" 2>/dev/null)
  62. echo " $svc: $sd"
  63. done
  64. echo
  65. echo "=== Third-party network kexts loaded ==="
  66. kextstat 2>/dev/null | grep -iE 'cisco|anyconnect|proton|mullvad|nord|littlesnitch|lulu|nextdns|warp' || echo " (none detected)"
  67. echo
  68. echo "=== ATTRIBUTION HINTS ==="
  69. # Aggregate every nameserver we can see across all resolver surfaces, then
  70. # pattern-match each unique entry to a known VPN/DNS client signature.
  71. ns_list=$( {
  72. [[ -d /etc/resolver ]] && grep -h '^nameserver' /etc/resolver/* 2>/dev/null | awk '{print $2}'
  73. scutil --dns 2>/dev/null | awk '/nameserver\[[0-9]+\]/{print $3}'
  74. networksetup -listallnetworkservices 2>/dev/null | tail -n +2 | while read -r svc; do
  75. [[ "$svc" == \** ]] && continue
  76. networksetup -getdnsservers "$svc" 2>/dev/null | grep -E '^[0-9a-f:.]+$' || true
  77. done
  78. } | sort -u | grep -v '^$' )
  79. if [[ -z "$ns_list" ]]; then
  80. echo " (no nameservers found)"
  81. fi
  82. while read -r n; do
  83. [[ -z "$n" ]] && continue
  84. case "$n" in
  85. 10.2.0.*) echo " $n :: likely Proton VPN gateway" ;;
  86. 10.64.0.*) echo " $n :: likely Mullvad gateway" ;;
  87. 10.211.*|10.212.*) echo " $n :: likely Cisco AnyConnect" ;;
  88. 10.5.0.*) echo " $n :: likely NordVPN gateway" ;;
  89. 100.100.100.100) echo " $n :: Tailscale MagicDNS (expected)" ;;
  90. 127.0.0.1|127.0.0.2|::1) echo " $n :: local DNS proxy (NextDNS, AdGuard, dnsmasq, etc.)" ;;
  91. 1.1.1.1|1.0.0.1) echo " $n :: Cloudflare public DNS" ;;
  92. 8.8.8.8|8.8.4.4) echo " $n :: Google public DNS" ;;
  93. 9.9.9.9|149.112.112.112) echo " $n :: Quad9 public DNS" ;;
  94. esac
  95. done <<< "$ns_list"