For six days and fifteen hours, a Let's Encrypt certificate covering google.sl and *.google.sl was valid, publicly logged, and not Google's. Another covered youtube.sl. Four more covered the American Samoa and Ghana equivalents. Every one of them was issued correctly, by a certificate authority that followed the rules, to an applicant who proved control of the domain.
The applicant proved it because they had taken over the DNS. On October 6 the Chrome Secure Web and Networking Team published its response to hijacks at three country-code registries: .gh (Ghana), .sl (Sierra Leone) and .as (American Samoa). Attackers changed authoritative DNS records for selected names, answered the domain-validation challenge the CA sent, and collected trusted certificates for Google and YouTube properties. Chrome blocked them with CRLSets, and Google worked with the issuing CAs to revoke them so that clients other than Chrome were covered too. Google says its own systems were never touched, and that it has no reason to believe the CAs acted improperly.
Who is in range, and who can stop reading
If you own one domain, at one registrar, renewing through one ACME account you administer, this story costs you about ten minutes of checking and no changes. You would notice a certificate you did not request, because there are only ever two or three of them.
You are in range if your organisation holds a domain portfolio nobody owns day to day. The specific shapes: country-code and regional variants bought defensively and parked; names inherited through an acquisition and never folded into the main registrar account; a brand-protection registrar whose console somebody logs into once a year; and anything delegated to a DNS provider that a third party operates on your behalf. Those are the names where an unauthorised certificate sits for a week without a single person looking at it.
Here is the part worth being blunt about. No configuration on your side prevents issuance while an attacker holds authoritative DNS for your domain. CAA records live in that same DNS. Domain validation reads that same DNS. An attacker who can rewrite one can rewrite the other, and Let's Encrypt's multi-perspective validation does not help either, because every vantage point queries the same poisoned authority and gets the same answer. Detection is the control you actually get. Certificate Transparency is where the detection lives.
What the logs show that the coverage does not
Certificate Transparency means every publicly trusted certificate is published to append-only logs, and anybody can read them. So rather than take the count on faith, I pulled the full issuance history for all seven affected names through the Cert Spotter search API on October 8 and filtered out everything issued by Google Trust Services, which issues Google's real certificates.
google.com.gh- 307 certificates in the logs, 1 of them foreign. Issued 2026-09-22 11:01 UTC, revoked 2026-09-26 02:41 UTC. Exposure: 3 days 15 hours.youtube.com.gh- 151 certificates, 1 foreign. Issued 2026-09-22 10:04 UTC, revoked 2026-09-26 02:41 UTC. Exposure: 3 days 16 hours.google.sl- 98 certificates, 2 foreign. Issued 2026-09-25 03:38 UTC, revoked 2026-10-01 19:36 UTC. Exposure: 6 days 15 hours.google.com.sl- 308 certificates, 2 foreign. One Let's Encrypt, revoked 2026-10-01 19:36 UTC; one Sectigo, revoked 2026-09-26 14:56 UTC.youtube.sl- 2 certificates, both foreign.google.as- 309 certificates, 3 foreign. Issued across 2026-09-27 02:35 to 03:18 UTC, revoked 2026-10-01 19:18 UTC. Exposure: 4 days 16 hours.youtube.as- 1 certificate, foreign.
Twelve certificates across seven names, matching the count The Hacker News reported on October 7. Eleven from Let's Encrypt, one from Sectigo. All domain-validated.
Two details in that list change how you build a detection. The first is the ratio. Three rogue certificates hid inside 309 legitimate ones on google.as. A human skimming a CT search page finds nothing, every time, forever. The second is the revocation reason codes. The Let's Encrypt certificates carry CRLReason 5, cessationOfOperation, and the Sectigo one carries 9, privilegeWithdrawn. None of them carry 1, keyCompromise. If you were planning to alert on a revocation reason that says "mis-issued", there is no such code and nobody used an adjacent one.
The signal that works is simpler: an issuer you did not authorise, appearing on a name you own.
Build the monitor against an issuer allowlist
Cert Spotter's API returns every logged issuance for a domain and its subdomains, with an after cursor so repeat runs only fetch what is new. The free tier is rate limited and does not need a key for light use. Put the domain list and the state file in a repository, run this from cron, and alert on anything whose issuer is not on the list.
#!/usr/bin/env python3
# ct_watch.py - alert on certificates issued by anyone you did not authorise.
# Usage: python3 ct_watch.py domains.txt allowed-issuers.txt state.json
import json, sys, time, urllib.request, urllib.error
API = "https://api.certspotter.com/v1/issuances"
FIELDS = "&expand=dns_names&expand=issuer&expand=revocation"
def fetch(domain, after):
url = f"{API}?domain={domain}&include_subdomains=true{FIELDS}"
if after:
url += f"&after={after}"
req = urllib.request.Request(url, headers={"User-Agent": "ct-watch/1.0"})
with urllib.request.urlopen(req, timeout=60) as r:
return json.load(r)
domains = [l.strip() for l in open(sys.argv[1]) if l.strip()]
allowed = {l.strip().lower() for l in open(sys.argv[2]) if l.strip()}
try:
state = json.load(open(sys.argv[3]))
except FileNotFoundError:
state = {}
alerts = []
for d in domains:
after, page = state.get(d), True
while page:
page = fetch(d, after)
for cert in page:
issuer = (cert.get("issuer") or {}).get("friendly_name", "unknown")
if issuer.lower() not in allowed:
alerts.append({
"domain": d,
"issuer": issuer,
"not_before": cert.get("not_before"),
"names": cert.get("dns_names", [])[:8],
"revoked": (cert.get("revocation") or {}).get("time"),
})
after = cert["id"]
if len(page) < 100:
break
time.sleep(0.5)
state[d] = after
json.dump(state, open(sys.argv[3], "w"))
for a in alerts:
print(json.dumps(a))
sys.exit(1 if alerts else 0)
How to run it and what to expect
- Seed it first. The opening run walks the entire history of every domain and will print every certificate from an issuer you forgot about. That first pass is the inventory, not the alert. Read it, add the legitimate issuers to
allowed-issuers.txt, and commit the state file. - Keep the allowlist short. Mine for a typical small environment is three lines: the ACME provider, the CDN that terminates TLS, and the registrar's bundled certificate product. Anything outside those three is worth a phone call.
- Exit code 1 means alert. Cron mails you the output on a nonzero exit, which is the entire notification system you need. A Slack webhook is a four-line addition if you want one.
- Do not build this on crt.sh. It is the better interactive search, and it returned HTTP 502 every time I called it while writing this. Unattended alerting needs an endpoint that answers.
- Watch wildcards and subdomains.
include_subdomains=trueis what catches a certificate minted forvpn.yourdomain.exampleby someone who never touched the apex.
Set CAA so that recovery holds
CAA tells CAs which of them may issue for your name. Google's advice in the same post is to publish restrictive CAA records with ACME account bindings, and Google is explicit about the limit: CAA cannot stop issuance while the hijack is live, because the attacker is serving your DNS. What it does is close the door afterwards. Once you have control back, an attacker holding cached domain validation cannot turn it into a new certificate.
The binding parameters come from RFC 8657, on top of the CAA record itself in RFC 8659. Let's Encrypt documents both: validationmethods restricts which challenge types count, and accounturi restricts issuance to one ACME account.
; Only Let's Encrypt, only the DNS-01 challenge, only this ACME account.
example.com. 3600 IN CAA 0 issue "letsencrypt.org;validationmethods=dns-01;accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/123456789"
example.com. 3600 IN CAA 0 issuewild "letsencrypt.org;validationmethods=dns-01;accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/123456789"
example.com. 3600 IN CAA 0 iodef "mailto:security@example.com"
; Parked domain that should never have a certificate at all:
parked.example. 3600 IN CAA 0 issue ";"
parked.example. 3600 IN CAA 0 issuewild ";"
; Verify what is published, including the inherited record on a subdomain:
; dig +short CAA example.com
; dig +short CAA vpn.example.com
The 0 issue ";" pair on a parked domain is the one most people miss. A name you bought defensively and never intend to serve should forbid issuance outright, which turns any attempt into a refusal at the CA and an iodef report to your mailbox. All three hijacked Google names now publish a strict CAA naming only pki.goog; a dig +short CAA google.sl shows it today.
Pair this with registrar lock and registry lock on anything that matters. Neither helps when the registry operator itself is the compromised party, which is what happened here, and both remove the far more common path where somebody phishes a registrar login and moves your nameservers in the afternoon.
Build the domain list before you build the monitor
The script above is only as good as domains.txt, and the certificates that went unnoticed for six days were on names that nobody had on a list. Spend the first hour there rather than on the tooling. Pull every domain from each registrar account you can find, then cross-check against the DNS provider's zone list, the finance system's renewal charges, and the subject alternative names on the certificates you already hold. Acquisitions and marketing campaigns are where the surprises come from.
Then set the monitor running on the whole list, not the handful you serve traffic from. Red Hound does this as continuous external attack surface management when a team does not have the hours, and we publish the tooling we use at github.com/redhoundinfosec. Either way, the work is the same: know every name you own, know who is allowed to issue for it, and get told the same day when somebody else does.
Want the external-footprint checks we run for clients?
We publish the tooling we use at github.com/redhoundinfosec, and we run continuous external attack surface management for teams without the hours to watch a domain portfolio themselves. Book a session if you want help building the list this monitor runs against.
