Imagine your team just renewed all your domains. Six months later, one expires because the credit card on file failed. A week after that, someone else registers it. They now control a domain that your certificate authority, package repository, or DNS system still trusts.
Researchers at USC and the University of Twente have identified this issue across Web PKI, Maven Central, and Ethereum Name Service. They found that trust records persist long after domains change hands—what they call "zombie linkages." Your monitoring likely checks certificate expiration, but does it verify domain ownership?
This script addresses that gap. Run it weekly to ensure every domain your organization claims is actually registered to you.
Script Functionality
The script performs three key checks:
- WHOIS Verification — It queries domain registration data and compares the registrant organization against your expected value.
- DNS Authority Check — It confirms your nameservers are still authoritative for each domain.
- Certificate Transparency Log Scan — It lists active certificates for domains you might have forgotten about.
The script outputs a CSV with status flags and expiration dates, which you can feed into your ticketing system or SIEM.
Prerequisites
You will need:
- Python 3.8 or later
- The
whois,dnspython, andrequestslibraries (pip install python-whois dnspython requests) - A list of domains your organization uses (see "How to Build Your Domain Inventory" below)
- Your organization's legal name as it appears in WHOIS records
- Read access to your certificate inventory (or use crt.sh for discovery)
For SOC 2 Type II compliance, this script supports the CC6.1 control (logical access controls) by verifying that trust relationships remain valid. For ISO 27001, it addresses Annex A 5.19 (information security in supplier relationships) when those suppliers are validated via domain ownership.
The Script
Save this as check_domain_ownership.py:
#!/usr/bin/env python3
"""
Domain Ownership Verification Script
Checks WHOIS, DNS authority, and certificate transparency logs
"""
import whois
import dns.resolver
import requests
import csv
import sys
from datetime import datetime
# Configuration
YOUR_ORG_NAME = "Your Company Inc" # Must match WHOIS registrant
YOUR_NAMESERVERS = ["ns1.yourhost.com", "ns2.yourhost.com"]
DOMAINS_FILE = "domains.txt"
OUTPUT_FILE = f"domain_audit_{datetime.now().strftime('%Y%m%d')}.csv"
def check_whois_ownership(domain):
"""Verify domain is registered to your organization."""
try:
w = whois.whois(domain)
registrant = w.org if hasattr(w, 'org') else w.registrant_name
expiration = w.expiration_date
if isinstance(expiration, list):
expiration = expiration[0]
days_until_expiry = (expiration - datetime.now()).days if expiration else None
return {
'owned_by_you': YOUR_ORG_NAME.lower() in str(registrant).lower(),
'registrant': registrant,
'expiration': expiration,
'days_until_expiry': days_until_expiry
}
except Exception as e:
return {'error': str(e)}
def check_dns_authority(domain):
"""Verify your nameservers are authoritative."""
try:
answers = dns.resolver.resolve(domain, 'NS')
nameservers = [str(rdata).rstrip('.') for rdata in answers]
your_ns_active = any(
ns.lower() in [expected.lower() for expected in YOUR_NAMESERVERS]
for ns in nameservers
)
return {
'your_ns_active': your_ns_active,
'active_nameservers': nameservers
}
except Exception as e:
return {'error': str(e)}
def check_certificate_transparency(domain):
"""Query crt.sh for active certificates."""
try:
url = f"https://crt.sh/?q=%.{domain}&output=json"
response = requests.get(url, timeout=10)
if response.status_code == 200:
certs = response.json()
recent = [c for c in certs if
(datetime.now() - datetime.strptime(
c['entry_timestamp'], '%Y-%m-%dT%H:%M:%S'
)).days < 90]
return {'recent_cert_count': len(recent)}
return {'recent_cert_count': 0}
except Exception as e:
return {'error': str(e)}
def main():
results = []
with open(DOMAINS_FILE, 'r') as f:
domains = [line.strip() for line in f if line.strip()]
for domain in domains:
print(f"Checking {domain}...", file=sys.stderr)
whois_result = check_whois_ownership(domain)
dns_result = check_dns_authority(domain)
ct_result = check_certificate_transparency(domain)
risk = "LOW"
if not whois_result.get('owned_by_you', False):
risk = "CRITICAL"
elif not dns_result.get('your_ns_active', False):
risk = "HIGH"
elif whois_result.get('days_until_expiry', 999) < 30:
risk = "MEDIUM"
results.append({
'domain': domain,
'risk_level': risk,
'owned_by_you': whois_result.get('owned_by_you', False),
'registrant': whois_result.get('registrant', 'UNKNOWN'),
'expiration': whois_result.get('expiration', 'UNKNOWN'),
'days_until_expiry': whois_result.get('days_until_expiry', 'UNKNOWN'),
'your_ns_active': dns_result.get('your_ns_active', False),
'nameservers': '; '.join(dns_result.get('active_nameservers', [])),
'recent_certs': ct_result.get('recent_cert_count', 0),
'errors': '; '.join([
whois_result.get('error', ''),
dns_result.get('error', ''),
ct_result.get('error', '')
]).strip('; ')
})
with open(OUTPUT_FILE, 'w', newline='') as f:
writer = csv.DictWriter(f, fieldnames=results[0].keys())
writer.writeheader()
writer.writerows(results)
print(f"\nResults written to {OUTPUT_FILE}", file=sys.stderr)
critical = [r for r in results if r['risk_level'] == 'CRITICAL']
if critical:
print("\n⚠️ CRITICAL: These domains are not owned by your organization:")
for r in critical:
print(f" - {r['domain']} (registrant: {r['registrant']})")
if __name__ == "__main__":
main()
How to Customize It
Build your domain inventory first. Create domains.txt with one domain per line:
example.com
api.example.com
legacy-app.example.com
To discover domains you've forgotten about:
- Export all DNS records from your authoritative nameservers.
- Pull domains from your certificate management system.
- Search your code repositories for
https://patterns. - Query certificate transparency logs:
curl "https://crt.sh/?q=%.yourcompany.com&output=json"
Set your organization name. Change YOUR_ORG_NAME to match your WHOIS registrant field exactly. Check an existing domain:
whois yourcompany.com | grep -i "registrant org"
Configure your nameservers. List all nameservers your organization uses in YOUR_NAMESERVERS. Include both primary and secondary.
Adjust the risk thresholds. The script flags domains expiring in under 30 days as MEDIUM risk. If your renewal process is slower, increase this to 60 or 90 days.
Add to your SIEM. The CSV output includes a risk_level column. Configure your SIEM to alert on any CRITICAL or HIGH findings.
Validation Steps
Run the script against a test domain you control:
python3 check_domain_ownership.py
Verify the output:
- WHOIS check — Confirm
owned_by_youisTrueandregistrantmatches your organization. - DNS check — Confirm
your_ns_activeisTrue. - Expiration warning — If a domain expires in under 30 days,
risk_levelshould be MEDIUM or higher.
Test a failure scenario. Add a domain you don't own (like example.com) to domains.txt. The script should flag it as CRITICAL.
Schedule it. Add to cron for weekly runs:
0 9 * * 1 /usr/bin/python3 /path/to/check_domain_ownership.py && mail -s "Domain Audit" [email protected] < /path/to/domain_audit_*.csv
What to do when you find a zombie. If the script reports a domain you thought you owned but don't:
- Check whether it expired and was re-registered.
- Revoke any certificates for that domain immediately.
- Remove it from allow-lists in your API gateways, Maven settings, or ENS configurations.
- File an incident report—someone may already be using it to impersonate your services.
This won't catch every trust relationship. Some systems cache domain ownership decisions or don't expose APIs for verification. But it closes the gap for the most common vectors: TLS certificates, package repositories, and DNS-based authentication.



