Campaign Pivoting
Turn one indicator into the whole campaign: co-tenancy, registrant, nameserver, TLS-fingerprint, and CT pivots as copy-paste Cypher recipes.
Campaign Pivoting Documentation
You hold one indicator — a phishing domain, a C2 IP, a suspicious nameserver — and you need the rest of the campaign: every sibling domain, the shared registrant, the co-tenant hosts, and how the infrastructure moved over time. With flat lookup tools that is a dozen tabs and a spreadsheet. WhisperGraph pre-joins DNS, WHOIS/RDAP, BGP, threat verdicts, Tor/TLS egress, and Certificate Transparency into one surface, so each pivot is a single hop and the whole campaign falls out of one traversal.
Every recipe below is copy-paste against the Cypher/REST endpoint at https://graph.whisper.security/api/query. Anonymous access runs 2-hop queries; a free key raises that to 3 hops, and paid plans go deeper. New to the graph? Start with Getting Started, keep the Graph Schema and Procedures open, and pull more patterns from Cross-Layer Patterns.
Run it live: Investigate an Indicator · Digital Infrastructure Mapping · Build the takedown evidence package — each opens with a live result you can rerun on your own indicator.
Key concepts: Co-hosted domains · Infrastructure pivoting · Passive DNS · C2 infrastructure.
Quick triage
For a full triage workflow (verdict, feeds, posture, escalation), see Indicator Triage (SOC). The two reads below are the minimum you need before pivoting.
Triage one indicator on the reconciled verdict
Why it's hard with flat tools: you query five block lists, get five disagreeing answers, and still have to decide whether to block.
What the graph does: every threat-listed node carries a reconciled verdict — one blocking-aware answer rolled up across all 43 feeds — plus the boolean flags that tell you what kind of bad it is.
// Reconciled verdict + what-kind-of-bad flags, in one read
MATCH (ip:IPV4 {name: "185.220.101.1"})
RETURN ip.verdictScore AS score,
ip.verdictLevel AS level,
ip.verdictBlocking AS block,
ip.isC2, ip.isMalware, ip.isTor, ip.isProxy, ip.isScanner
Tip: prefer
verdictScoreover the rawthreatScore— it is the cross-feed reconciliation, not a single list's opinion. For the full evidence chain (which feeds, with weights and first/last-seen), useCALL explain(...).
Get the scored evidence chain for any indicator
Why it's hard with flat tools: a reputation score with no factors is unappealable — you can't paste "0.91, trust me" into a ticket.
What the graph does: explain returns the arithmetic — every contributing feed, its weight, and first/last-seen — for an IP, IPv6, hostname, ASN, or CIDR.
CALL explain("paypal-account-verify.com")
YIELD indicator, type, found, score, level, explanation, factors, sources
RETURN indicator, type, found, score, level, explanation, factors, sources
Tip:
explainworks on a whole ASN or CIDR too —CALL explain("AS_number")quantifies a malicious neighborhood. A clean result means "not listed", not "safe": no data is not the same as known clean.
Pivot one indicator into the whole campaign
Blast radius — pivot from one flagged IP to every co-hosted domain, the feeds that name it, and the network that routes it.
Co-tenancy: every domain on the same IP
Try it on a live IP — every hostname currently resolving to it:
Why it's hard with flat tools: reverse-IP lookup is its own paid product, and it doesn't join to anything else.
What the graph does: one hop in, one hop back out. RESOLVES_TO is HOSTNAME → IP, so the sibling hosts hang off the reverse arrow.
// Domains co-hosted with a suspicious host on the same IP
MATCH (seed:HOSTNAME {name: "paypal-account-verify.com"})-[:RESOLVES_TO]->(ip:IPV4)
WITH seed, ip LIMIT 10
MATCH (ip)<-[:RESOLVES_TO]-(sibling:HOSTNAME)
WHERE sibling <> seed
RETURN ip.name AS shared_ip, sibling.name AS co_tenant
LIMIT 25
Tip: one shared-hosting IP can carry thousands of unrelated tenants, so the
WITH ... LIMIT 10bounds the IP fan-out first. If the seed no longer resolves, pullCALL whisper.history(...)for the IPs it used to resolve to.
Registrant pivot: every domain sharing a WHOIS email
Why it's hard with flat tools: WHOIS is per-domain, so you can't ask "what else did this registrant register" without scraping.
What the graph does: the registrant email is a first-class node. Pivot through it to the actor's whole portfolio. HAS_EMAIL is HOSTNAME → EMAIL, so the siblings are on the reverse arrow.
// All domains registered with the same WHOIS contact email
MATCH (seed:HOSTNAME {name: "cloudflare.com"})-[:HAS_EMAIL]->(e:EMAIL)
WITH seed, e LIMIT 5
MATCH (e)<-[:HAS_EMAIL]-(sibling:HOSTNAME)
WHERE sibling <> seed
RETURN e.name AS shared_email, sibling.name AS related_domain
LIMIT 25
Tip: registrar privacy services replace the real registrant with a proxy address (
domains@cloudflare.com,contact@privacyguardian.org). A proxy email clusters by registrar, not by actor — confirm the email isn't a privacy service before drawing attribution conclusions.
Shared-nameserver siblings
Why it's hard with flat tools: passive-DNS products show you the NS records but won't enumerate the reverse — every other domain that delegates to the same server.
What the graph does: NAMESERVER_FOR points server → domain, so a custom nameserver fans straight out to its whole client list — a strong clustering signal when the actor runs their own DNS.
// Every domain delegating DNS to a specific nameserver
MATCH (ns:HOSTNAME {name: "ns1.example-bulletproof.net"})-[:NAMESERVER_FOR]->(domain:HOSTNAME)
RETURN domain.name AS delegated_domain
LIMIT 25
Tip: filter out the giants. Domains on
ns1.google.comor Cloudflare's nameservers tell you nothing. A private or oddly-named nameserver shared across a handful of suspicious domains is the find.
One query, three pivots: the campaign in a single traversal
Why it's hard with flat tools: co-tenancy, shared registrant, and shared nameserver are three separate products with three exports you'd have to reconcile by hand.
What the graph does: they are three edge types on the same node. UNION them into one campaign view, tagged by pivot type. Three pivots in one statement can push past the anonymous 2-hop cap; a free key covers it.
// Blast radius: union co-tenants, registrant siblings, and NS siblings
MATCH (seed:HOSTNAME {name: "paypal-account-verify.com"})-[:RESOLVES_TO]->(ip:IPV4)
WITH seed, ip LIMIT 10
MATCH (ip)<-[:RESOLVES_TO]-(s:HOSTNAME) WHERE s <> seed
RETURN "co-tenant IP" AS pivot, s.name AS related, ip.name AS via
LIMIT 25
UNION
MATCH (seed:HOSTNAME {name: "paypal-account-verify.com"})-[:HAS_EMAIL]->(e:EMAIL)
WITH seed, e LIMIT 5
MATCH (e)<-[:HAS_EMAIL]-(s:HOSTNAME) WHERE s <> seed
RETURN "registrant email" AS pivot, s.name AS related, e.name AS via
LIMIT 25
UNION
MATCH (seed:HOSTNAME {name: "paypal-account-verify.com"})<-[:NAMESERVER_FOR]-(ns:HOSTNAME)
WITH ns LIMIT 5
MATCH (ns)-[:NAMESERVER_FOR]->(s:HOSTNAME)
WHERE s.name <> "paypal-account-verify.com"
RETURN "shared nameserver" AS pivot, s.name AS related, ns.name AS via
LIMIT 25
Tip: the
pivotcolumn tells your analyst why each host joined the cluster — co-tenancy is the weakest signal (shared hosting), shared registrant and private nameserver are stronger. Feed therelatedlist back throughexplain()to rank the cluster by verdict.
Typosquats & lookalikes
The recipe below covers the campaign-pivoting angle: generate lookalikes as fresh pivot seeds. For the full brand playbook (anchored prefix sweeps, TLD sweeps, parked-vs-weaponized, takedown evidence), see Lookalike Hunting.
Generate the lookalike set for a brand
Why it's hard with flat tools: you'd hand-write homoglyph and bitsquat permutations, then check each one's registration.
What the graph does: whisper.variants runs many generation algorithms and returns only the variants that exist as nodes — registered lookalikes, ready to pivot.
CALL whisper.variants("paypal.com")
YIELD variant, method, exists, confidenceLabel
WHERE exists
RETURN variant, method, confidenceLabel
LIMIT 25
Tip:
exists: truemeans registered, not malicious. A parked typo and an active phishing kit look identical here — pivot each hit throughexplain()for the verdict, then feed the flagged ones into the campaign pivots above.
Actor attribution & MITRE ATT&CK
The actor recipes moved to their own page: Actor Attribution & ATT&CK covers mapping a named actor to its techniques, finding actors that share a technique, and narrowing attribution by rare-technique convergence. The ACTOR and ATTACK_PATTERN nodes live in the same graph as the infrastructure you just mapped, so a campaign cluster pivots straight into attribution.
Egress & fingerprint pivots
Identify Tor-exit infrastructure that survives IP rotation
Why it's hard with flat tools: an exit node's IP changes; the relay identity (its fingerprint) doesn't, and most tools only see the IP.
What the graph does: OPERATES_EXIT_NODE ties an IP to a stable TOR_RELAY identity, so you can confirm an IP is a Tor exit and recover the relay behind it.
// Is this IP a Tor exit, and which relay identity operates it?
MATCH (ip:IPV4 {name: "185.220.101.1"})-[:OPERATES_EXIT_NODE]->(relay:TOR_RELAY)
RETURN ip.name AS ip, ip.isTor AS flagged_tor, relay.name AS relay_fingerprint
LIMIT 10
Tip: Tor egress isn't malicious by itself, but it changes how you weight other signals. Cross-check
ip.isAnonymizerandip.isProxyon the same node.
Track C2 across changing domains with a TLS fingerprint
Why it's hard with flat tools: when an actor rotates domains and IPs, the only stable thread is how the server speaks TLS — and that is invisible to DNS-based tooling.
What the graph does: EMITS_TLS_FINGERPRINT ties an IP to a JA3/JARM fingerprint. Find the fingerprint your seed C2 emits, then pivot to every other IP emitting the same one. Coverage is still rolling out, so read an empty result as "not observed yet".
// Other IPs emitting the same JA3/JARM fingerprint as a known C2 IP
MATCH (seed:IPV4 {name: "185.220.101.1"})-[:EMITS_TLS_FINGERPRINT]->(fp:TLS_FINGERPRINT)
WITH fp LIMIT 5
MATCH (fp)<-[:EMITS_TLS_FINGERPRINT]-(other:IPV4)
WHERE other.name <> "185.220.101.1"
RETURN fp.name AS fingerprint, other.name AS same_stack_ip, other.verdictLevel AS level
LIMIT 25
Tip: common server stacks share fingerprints across millions of hosts, so the pivot is only meaningful when the JARM is distinctive — typically a bespoke C2 framework. Rank hits by
verdictLevelto surface the ones already flagged.
Discover subdomains and SANs from Certificate Transparency
Why it's hard with flat tools: an actor's staging subdomains may never resolve publicly, but they leak into CT logs the moment a cert is issued.
What the graph does: SEEN_IN_CT connects a host to its Certificate Transparency observations, surfacing names that DNS alone would miss.
// CT observations for a domain — surfaces SANs / staging subdomains
MATCH (h:HOSTNAME {name: "paypal-account-verify.com"})-[:SEEN_IN_CT]->(ct:CT_OBSERVATION)
RETURN ct.name AS ct_observation
LIMIT 25
Tip: CT discovery pairs well with the campaign pivots above — a SAN found here is a new seed host. Run it back through the co-tenancy and registrant pivots to extend the cluster.
Separate the operator from the WHOIS owner
Why it's hard with flat tools: WHOIS says who owns a netblock; it rarely says who operates it — and attackers abuse the gap.
What the graph does: DELEGATED_TO records the vendor actually running address space, distinct from the registrant ORGANIZATION.
// Which vendor operates the address space this IP sits in?
MATCH (ip:IPV4 {name: "185.220.101.1"})-[:DELEGATED_TO]->(v:VENDOR)
RETURN ip.name AS ip, v.name AS operated_by
LIMIT 10
Tip: a "consumer" netblock delegated to a cloud vendor, or the reverse, is a mismatch worth a second look. Combine with the host → network-owner walk below for the full ownership picture.
Infrastructure attribution & identity
Who hosts this domain, on whose network
Why it's hard with flat tools: host → IP → prefix → ASN → network name → country is five lookups across four products.
What the graph does: one traversal. ANNOUNCED_BY covers the IP, ROUTES reaches the ASN, HAS_NAME resolves the network name, and LOCATED_IN/HAS_COUNTRY geolocate it. This chain exceeds the anonymous 2-hop cap — bring a free key.
// Domain → IP → announced prefix → ASN → network name, plus country
MATCH (h:HOSTNAME {name: "paypal-account-verify.com"})-[:RESOLVES_TO]->(ip:IPV4)
WITH h, ip LIMIT 5
MATCH (ip)-[:ANNOUNCED_BY]->(ap:ANNOUNCED_PREFIX)-[:ROUTES]->(a:ASN)-[:HAS_NAME]->(n:ASN_NAME)
OPTIONAL MATCH (ip)-[:LOCATED_IN]->(city:CITY)-[:HAS_COUNTRY]->(c:COUNTRY)
RETURN ip.name AS ip, ap.name AS prefix, a.name AS asn, n.name AS network, c.name AS country
LIMIT 10
Tip:
RESOLVES_TOandANNOUNCED_BYare forward edges (HOSTNAME → IP,IP → prefix);ROUTESmatches from either direction. The ASN's number lives ona.name(AS13335); the human-readable network name lives on the separateASN_NAMEnode. Hosting identity is a separate question from the threat verdict — answer the second withexplain().
History & change tracking
Watch how a domain's registration moved over time
Why it's hard with flat tools: current WHOIS is a single snapshot; the changes are the actual signal, and they're gone unless someone archived them.
What the graph does: whisper.history returns the timestamped WHOIS snapshots so you can see exactly when the registration shifted. (Requires an API key — not on the anonymous tier.)
CALL whisper.history("paypal-account-verify.com")
YIELD queryTime, createDate, updateDate, registrar, nameServers
RETURN queryTime, createDate, updateDate, registrar, nameServers
LIMIT 10
Tip: a registrar transfer and a nameserver change in the same week is a classic ownership-handoff marker. For an IP, ASN, or prefix the same call returns BGP routing history instead — keep a
LIMIT, large networks take seconds. Seewhisper.history()for the full procedure reference.
De-cloak the real origin behind a CDN
Why it's hard with flat tools: the domain resolves to Cloudflare; the actual origin server is hidden behind it.
What the graph does: whisper.origins reconstructs candidate origin IPs from MX/SPF, sibling, and crawl signals — highest confidence first.
CALL whisper.origins("paypal-account-verify.com")
YIELD ip, confidence, methods, asnName
RETURN ip, confidence, methods, asnName
LIMIT 5
Tip: a de-cloaked origin is a fresh seed. Run it back through co-tenancy and
explain()— the origin's neighbors often aren't behind the CDN and expose the rest of the campaign. Guided version: Find the real infrastructure behind the CDN.
Batch & evidence collection
Triage a list of indicators in one request
Why it's hard with flat tools: a batch lookup is N API calls and N rate-limit windows.
What the graph does: UNWIND a list and let the graph fan it out in a single round-trip.
// Batch verdict + registrar for a list of suspect domains
UNWIND ["paypal-account-verify.com", "secure-paypaI.com", "paypal.com"] AS name
MATCH (h:HOSTNAME {name: name})
OPTIONAL MATCH (h)-[:HAS_REGISTRAR]->(r:REGISTRAR)
RETURN name,
h.verdictLevel AS level,
h.verdictScore AS score,
collect(DISTINCT r.name) AS registrars
LIMIT 100
Tip: a domain absent from the result set didn't match the
MATCH— it's unknown to the graph, which is different from "assessed clean." Surface the gap to your analyst rather than implying a verdict.
Inbound web links: who points at the target
Why it's hard with flat tools: the open web's hyperlink graph isn't something you can query alongside DNS and WHOIS.
What the graph does: LINKS_TO is the Common Crawl hyperlink graph in the same surface. Phishing pages often link to the legitimate brand to look credible — inbound links surface them.
// Sites that link to a target host
MATCH (source:HOSTNAME)-[:LINKS_TO]->(h:HOSTNAME {name: "paypal-account-verify.com"})
RETURN source.name AS linking_site
LIMIT 25
Tip: flip the arrow —
(h)-[:LINKS_TO]->(target)— to see what the suspect domain references outbound, which can expose shared kit, redirectors, or affiliate tracking tied to the campaign.
Where to go next
- Indicator Triage (SOC) — the verdict-first triage workflow that feeds these pivots.
- Actor Attribution & ATT&CK — map campaign clusters to named actors and techniques.
- Cross-Layer Patterns — the reusable multi-layer query patterns behind every recipe here.
- Procedures — full signatures for
explain,whisper.variants,whisper.history, andwhisper.origins. - Graph Schema — every label, edge, and property, with direction notes.
- Threat Feeds & Categories — the 43 feeds and 25 categories behind the verdicts.
- AI & Agents — point an MCP client at the graph and let your agent run these pivots itself, mid-investigation.
For inline enrichment in SPL, see Splunk Use Cases for Infrastructure Intel and Enterprise Security Integration.