Counterfeit certs via hijacked ccTLDs: CAA with accounturi and a CT watch for your domains in 15 minutes
Attackers took over the .gh, .sl and .as registries and got 12 valid certificates for Google domains. CAA would not have stopped the hijack. Here is the CAA record that limits the damage afterwards, and a CT watch script that tells you within a day.
if.if.codesOct 9, 2026 · 6 min read#tls#caa#certificate-transparency#dnsAI-assisted
On 6 October Google disclosed that attackers had compromised the registries for three country-code TLDs, .gh (Ghana), .sl (Sierra Leone) and .as (American Samoa), and used them to get publicly trusted certificates for Google domains and other organisations' domains. According to The Hacker News, there were 12 certificates for seven domains: Let's Encrypt issued 11 and ZeroSSL issued one. They show up in Certificate Transparency logs one ccTLD at a time: .gh on 22 September, .sl on 25 September, .as on 27 September.
Google says it has "no reason to believe" the CAs did anything wrong. The attackers changed the authoritative DNS for the target names, passed domain validation, and got real certificates. Chrome blocked the ones Google found via CRLSets, and the CAs revoked them. Google also says it cannot guarantee it found every affected domain, and Chrome's block does nothing for users of other browsers.
What CAA can and cannot do here
Be honest about this first. CAA would not have stopped these certificates. CAA is a DNS record, and the attacker controlled DNS. They can delete it, replace it, or point the name somewhere with no CAA at all. If your registry or your DNS provider is compromised, no DNS record protects you.
CAA records provide a secondary layer of defense after a DNS hijack is resolved.
What CAA with an account binding does do is limit what happens after the hijack ends. CAs cache successful domain validations for a while. Google's recommendation is to publish "restrictive CAA records (with ACME account bindings)" so that issuance is limited to your accounts and your validation methods. That stops an attacker from "using cached validation state to mint new certificates after a hijack ends". CAA is checked when the certificate is issued, so once your real DNS is back, an accounturi record shuts the attacker's ACME account out, even if it still holds a valid authorisation.
Detection is the other half. Google's first recommendation is CT monitoring: "near real-time alert whenever a certificate is issued for your domains". That is how the extra organisations in this incident were found.
So you need two things: a CAA record that limits issuance to your ACME account, and a CT watch that tells you when anything is issued anyway. That takes about 15 minutes.
Empty output means any CA may issue for the name. CAA lookups climb the tree, so a record on example.com covers subdomains that have none of their own, and they follow CNAMEs.
Step 2: find your ACME account URI (3 minutes)
Let's Encrypt account URIs look like https://acme-v02.api.letsencrypt.org/acme/acct/1234567890. Get yours from whatever client renews your certificates.
If several servers each register their own account, you get several URIs. List them all in step 3, or move the servers onto one account first.
Want AI wired into the systems you already run?I build LLM integrations with costs and quality you can see. The estimate is free.
Step 3: publish the CAA records (5 minutes)
Replace the account number and pick the validation method you actually use (http-01, dns-01 or tls-alpn-01):
Configuring account bindings ensures only your specific servers can request certificates.
example.com. 3600 IN CAA 0 issue "letsencrypt.org; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/1234567890; validationmethods=http-01"
example.com. 3600 IN CAA 0 issuewild ";"
The first record allows Let's Encrypt to issue only to that account, only via that method. The second forbids wildcard certificates from anyone. If you do need a wildcard, add an issuewild line with the same parameters and validationmethods=dns-01, because wildcards require DNS validation.
Before you publish, check two things that will otherwise break your renewals:
Every CA that issues for you must be listed. If a CDN or a managed platform issues edge certificates for your name, its CAs need their own issue lines. Check your provider's CAA documentation. Some issue from more than one CA.
Every account must be listed. A forgotten staging box with its own account will fail at its next renewal. That is CAA working, but you want to find out now.
Verify:
dig +short CAA example.com
Then force one renewal on a non-critical name (sudo certbot renew --force-renewal --cert-name staging.example.com) to prove issuance still works.
Step 4: a CT watch in one script (5 minutes)
An issuer allowlist would not have caught this incident: 11 of the 12 certificates came from Let's Encrypt, which is probably on your allowlist. The useful signal is any new certificate you did not request. For a small set of domains, renewals are rare enough that a daily list of every new certificate is short enough to read.
Certificate Transparency monitoring acts as an early warning system for unauthorized issuance.
This uses SSLMate's Cert Spotter API, which works without a key at low request rates, plus curl and jq. It remembers the last certificate it saw, so each run prints only what is new. We first tried crt.sh's JSON output, but it returned HTTP 502 on every request while this post was being written. Cert Spotter answered straight away.
#!/usr/bin/env bash# ct-watch.sh: print certificates issued for a domain and its subdomains# since the last run, using SSLMate's Cert Spotter API# usage: ./ct-watch.sh example.comset -euo pipefail
DOMAIN="${1:?usage: ct-watch.sh example.com}"
STATE="${STATE_DIR:-$HOME/.ct-watch}/${DOMAIN}.last"mkdir -p "$(dirname "$STATE")"
BASE="https://api.certspotter.com/v1/issuances?domain=${DOMAIN}&include_subdomains=true&expand=dns_names&expand=issuer"while :; do
URL="$BASE"if [[ -s "$STATE" ]]; then
URL="${URL}&after=$(cat "$STATE")"fi
RESP="$(curl -fsS --retry 3 --max-time 60 "$URL")"if [[ "$(echo "$RESP" | jq 'length')" -eq 0 ]]; thenbreakfiecho"$RESP" | jq -r '.[] | [.not_before, .issuer.friendly_name, (.dns_names | join(",")), "https://crt.sh/?q=\(.cert_sha256)"] | @tsv'echo"$RESP" | jq -r 'last | .id' > "$STATE"done
The first run prints every current certificate for the domain. That is your baseline, so read it once:
chmod +x ct-watch.sh
./ct-watch.sh example.com
Each line shows the certificate's start date, the issuer, the names it covers, and a crt.sh link to the certificate. A second run straight after prints nothing. When we ran it against redelay.com, the baseline held Let's Encrypt certificates from our own ACME client and Google Trust Services certificates from the CDN in front of the site. That is exactly the kind of second issuer you need to find before you publish the CAA record in step 3. Then schedule it daily and mail yourself only when there is output:
crontab -e
# add:
15 7 * * * /opt/ct-watch/ct-watch.sh example.com | grep . | mail -s "CT: new certs for example.com" ops@example.com
When a line appears, compare it with your ACME client's log. If you did not request it, revoke through the issuing CA and start working out how someone passed validation for your name. A script on cron has no one watching it. If you need guaranteed alerts, use a hosted CT monitor (Cert Spotter itself offers one) that emails you directly.
If your domain sits under a small ccTLD
This incident is a reminder that your registry and your DNS provider are part of your certificate security. If a critical name lives under a ccTLD run by a small registry, consider keeping the login and API endpoints on a second name under a TLD whose operator you trust more. Watch both in CT. Google and The Hacker News both recommend including regional and parked domains in the watch. Those are the names nobody looks at.