Tracing a Path
Follow one alert from a hostname to a choke point you can act on, in five steps, each with a runnable query.
On this page (6)
Tracing a Path Documentation
These recipes take you from the name in an alert to a node you can act on. One job in five steps: start from the name in an alert and finish with a node you can block, sinkhole or report, plus the evidence that justifies it.
- Resolve the name into its routing.
- Find the infrastructure it shares with a second indicator.
- Rank the candidates and pick the choke point.
- Read the evidence on the node you picked.
- Measure what depends on it before you act.
Every query below runs against POST /api/query with your key in the X-API-Key header; sign in and copy it from the console. Anchor every query on an indexed {name: "value"} and bound a high-fan-out step with WITH … LIMIT before you expand it; the hostname and address labels are far too large to scan. Why each step works is on Attack Paths.
1. Resolve the name into its routing
Start where the alert starts — a name — and follow resolution into routing in one statement. The optional hop keeps the row alive when an address has no announcement.
MATCH (h:HOSTNAME {name: "github.com"})-[:RESOLVES_TO]->(ip:IPV4)
OPTIONAL MATCH (ip)-[:ANNOUNCED_BY]->(:ANNOUNCED_PREFIX)-[:ROUTES]->(a:ASN)
RETURN ip.name AS ip, a.name AS asn
LIMIT 5
You now have the addresses the name answers with and the network that carries them. Both are candidate choke points; neither is the best one yet.
2. Find the shared infrastructure
Check the three highest-signal pivots between your indicator and a second one — shared address, shared nameserver, shared registration contact — as explicit hops. OPTIONAL MATCH keeps the row alive when a pivot is empty, which is common with redacted registration data.
MATCH (a:HOSTNAME {name: "google.com"}), (b:HOSTNAME {name: "acount-google.com"})
OPTIONAL MATCH (a)-[:RESOLVES_TO]->(ip:IPV4)<-[:RESOLVES_TO]-(b)
OPTIONAL MATCH (a)<-[:NAMESERVER_FOR]-(ns:HOSTNAME)-[:NAMESERVER_FOR]->(b)
OPTIONAL MATCH (a)-[:HAS_EMAIL]->(e:EMAIL)<-[:HAS_EMAIL]-(b)
RETURN collect(DISTINCT ip.name) AS shared_ips,
collect(DISTINCT ns.name)[0..5] AS shared_nameservers,
collect(DISTINCT e.name) AS shared_registrant
LIMIT 1
shared_ips: []
shared_nameservers: []
shared_registrant: ["contact-admin@google.com"]
Two pivots are empty and the third is the finding: a lookalike and the real name carry the same registration contact. One record names both domains, which is as consistent with a brand buying its own typosquats as with an adversary registering them.
3. Rank the candidates and pick the choke point
Rank a target's nameservers by how many other domains each one serves. Give each branch its own CALL {} block so the fan-out stays bounded.
MATCH (:HOSTNAME {name: "paypal.com"})<-[:NAMESERVER_FOR]-(ns:HOSTNAME)
WITH ns LIMIT 4
CALL { WITH ns MATCH (ns)-[:NAMESERVER_FOR]->(dep:HOSTNAME) WITH dep LIMIT 2000 RETURN count(dep) AS dependents }
CALL { WITH ns MATCH (ns)-[:NAMESERVER_FOR]->(d:HOSTNAME) WITH d LIMIT 6 RETURN collect(d.name) AS sample }
RETURN ns.name AS nameserver, dependents, sample
ORDER BY dependents DESC LIMIT 10
pdns100.ultradns.com | 584 | paypal.ai, hellenicbank.app, paypal.com.ar, mcgraw-hill.asia, …
ns2-pchnet.paypal.com | 257 | paypal.ai, paypal.com.ar, paypal.at, paypal.com.au, …
ns1-pchnet.paypal.com | 257 | paypal.ai, paypal.com.ar, paypal.at, paypal.com.au, …
ppdns.paypal.com | 155 | paypal.at, paypal.com.au, paypal-education.com.au, …
The top row is not the brand's own nameserver, and the sample shows why the ranking matters: an outsourced DNS host answering for this estate also answers for names that have nothing to do with it. Read dependents as a floor rather than a census — the inner LIMIT 2000 bounds the count deliberately.
4. Read the evidence on the node
Before you act, make the node say why. explain() returns the score, the arithmetic behind it and the feeds that contributed, each with its weight and first and last seen timestamps.
CALL explain("185.220.101.1")
YIELD indicator, score, level, explanation, factors, sources
RETURN indicator, score, level, explanation, factors, sources
YIELD the columns you want: a bare call returns the procedure's full set, and the ones that carry nothing for this indicator come back blank beside the ones that do.
Read
coveragebeforeband. Only known-clean — coverage: known-clean. In coverage, no malicious evidence. licenses the word "clean"; no-data — coverage: no-data. Not in coverage. This is not a verdict — nothing was looked at. means unknown, which is a different thing again; malicious-evidenced — coverage: malicious-evidenced. In coverage, with positive evidence of malice. and ambiguous — coverage: ambiguous. In coverage, and the evidence points both ways. mean there is evidence, whatever the band says. Full contract: Coverage — what we looked at.
5. Measure what depends on it
Blocking a choke point has a blast radius. List the domains that lean on it for DNS and count how many nameservers each has in total: a dependent with only one is single-homed on this asset, and that dependent is where an outage becomes an incident.
MATCH (ns:HOSTNAME {name: "ns1.dreamhost.com"})-[:NAMESERVER_FOR]->(d:HOSTNAME)
WITH d LIMIT 40
MATCH (allns:HOSTNAME)-[:NAMESERVER_FOR]->(d)
RETURN d.name AS dependent, count(DISTINCT allns) AS total_nameservers
ORDER BY total_nameservers ASC
LIMIT 25
The same shape works for mail: swap the nameserver edge for the mail edge and a domain whose only mail host is this one loses inbound mail if it fails.
Related pages
- Supply-Chain Dependency Mapping — the same dependency walk, level by level, with single-vendor dependencies flagged for you.
- Best Practices — anchoring, bounding and the rules for variable-length patterns.
- Pivoting Examples — more worked chains, including the physical layer.