AgentStack
SKILL verified MIT Self-run

Indicator Pivoting

skill-liberty91ltd-cti-skills-indicator-pivoting · by Liberty91LTD

Indicator pivoting methodology — how to use one known indicator to discover related infrastructure across the IOC graph. Decision tree by indicator type with concrete `/lookup-*` commands per pivot, a worked multi-hop example, pivot-quality scoring, and routing into the rigor pipeline. Use when the user asks "what else is connected to this IP / domain / hash / cert / actor?", needs to expand a si…

No reviews yet
0 installs
16 views
0.0% view→install

Install

$ agentstack add skill-liberty91ltd-cti-skills-indicator-pivoting

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Indicator Pivoting? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Indicator Pivoting

Pivoting is the art of using one known indicator to discover related infrastructure, expanding a single seed into an understanding of an adversary's wider operations. Each pivot is a directed-graph walk: the node is an indicator (typed), the edge is a relation derived from a lookup, and the cost is one or more API calls plus the analyst time to interpret the result.

This skill is the canonical "how to walk the IOC graph" reference. The four investigation skills (/ip-investigation, /domain-investigation, /hash-investigation, /url-investigation) chain a fixed set of lookups for a known seed type; this skill takes over when you need to keep walking past their first-hop output.

This skill invokes: /lookup-virustotal, /lookup-otx, /lookup-shodan, /lookup-censys, /lookup-urlscan, /lookup-abuseipdb, /lookup-greynoise, /lookup-misp, /lookup-opencti, /lookup-ransomwarelive, /lookup-reversinglabs, /lookup-crowdstrike; then /score-source, /apply-tlp, /confidence-language. May hand off to /threat-actor-profiling, /campaign-tracking, /malware-analysis, /yara-writing, /sigma-writing, /darkweb-collection.

When to invoke

  • The user has a single seed IOC and asks "what else is related to this?"
  • An investigation skill returned pivot_candidates and you need to walk further than its routing did
  • You suspect a campaign cluster (multiple indicators in incident telemetry) and want to confirm common infrastructure
  • A threat-actor profile mentions an indicator and you want to see the wider footprint
  • You need to clearly document a pivot chain so a reviewer can trace your reasoning end-to-end

Pivot decision trees, with the commands to run

For each starting indicator type: a tree of pivots, then the concrete commands. The /lookup-* form is the skill-invocation; the python3 tools/clis/... form is the underlying CLI. Both are equivalent — pick whichever the surrounding context demands.

Starting from an IP address

IP address
├── Reputation (cheap, parallel first)
│   ├── /lookup-virustotal ip 
│   ├── /lookup-abuseipdb (abuse + categories)
│   └── /lookup-greynoise (noise/scanner classification — filters out internet-wide scanners)
├── Service fingerprint
│   ├── /lookup-shodan host           → ports, banners, products, vulns, hostnames
│   └── /lookup-censys host           → richer service detail, certificate chain, CPE
├── DNS pivots
│   ├── /lookup-shodan dns reverse    → reverse-DNS hostname(s)
│   ├── /lookup-virustotal ip  --relationships resolutions  → passive-DNS history
│   └── /lookup-otx ip                → community-reported domains for this IP
├── Cert pivots
│   ├── /lookup-shodan host           → cert SHA1 in the banner blob
│   └── /lookup-censys host           → cert fingerprint(s) → pivot to other hosts
├── JARM / TLS-fingerprint pivots
│   └── /lookup-shodan search 'ssl.jarm:""'   → other hosts with same JARM (often C2 family)
├── Range pivots (use sparingly — high false-positive risk)
│   └── /lookup-shodan search 'net:'          → neighbours in same /24
└── Malware pivots
    └── /lookup-virustotal ip  --relationships communicating_files,downloaded_files

Concrete commands:

# Reputation triple
python3 tools/clis/virustotal.py ip 185.220.101.45
python3 tools/clis/abuseipdb.py check 185.220.101.45 --verbose
python3 tools/clis/greynoise.py ip 185.220.101.45

# Service fingerprint
python3 tools/clis/shodan.py host 185.220.101.45 --history
python3 tools/clis/censys.py host 185.220.101.45 --at-time 2026-04-01T00:00:00Z

# Passive DNS + community
python3 tools/clis/virustotal.py ip 185.220.101.45 --relationships resolutions,communicating_files
python3 tools/clis/otx.py ip 185.220.101.45

# JARM-pivot to find C2 siblings
python3 tools/clis/shodan.py search 'ssl.jarm:"3fd21b20d00000021c43d21b21b43d41226ab2434c2cd1a5e1b3c2bc5f4f4e7"'

Starting from a domain

Domain
├── Reputation (parallel)
│   ├── /lookup-virustotal domain            → detection ratio + categories + WHOIS-ish
│   ├── /lookup-otx domain                   → pulses + passive DNS
│   └── /lookup-urlscan domain               → existing scans (don't re-submit by default)
├── DNS resolution (always)
│   ├── /lookup-shodan dns resolve           → current A/AAAA records
│   └── /lookup-virustotal domain  --relationships resolutions  → historical
│       └── pivot the resolved IPs as IPs (above)
├── Subdomain enumeration
│   ├── /lookup-shodan dns domain            → subdomains Shodan knows about
│   └── /lookup-censys search 'names: '      → CT-derived names (paid plan only)
├── Certificate transparency
│   ├── /lookup-censys search 'services.tls.certificates.leaf_data.subject.common_name:'
│   │   → other hosts presenting a cert for this name
│   └── /lookup-virustotal domain            → cert SAN list in the response
│       └── cert SAN siblings → pivot each as a domain
├── WHOIS / registrant
│   └── /lookup-virustotal domain            → registrar, creation_date, registrant_email if not redacted
│       └── registrant email → /lookup-virustotal search …  (paid VT Intel only)
│       └── reverse-WHOIS via vendor (DomainTools, WhoisXMLAPI) — outside the bundled lookups
├── Ransomware-claim sweep
│   └── /lookup-ransomwarelive search --q 
│       → derive org-candidate by stripping public suffix and `www.`; see /domain-investigation §3
└── Content
    └── /lookup-urlscan domain               → screenshots, DOM, page tech

Concrete commands:

# Reputation + content
python3 tools/clis/virustotal.py domain evil.example.com
python3 tools/clis/otx.py domain evil.example.com
python3 tools/clis/urlscan.py search "domain:evil.example.com"

# DNS now + historical
python3 tools/clis/shodan.py dns resolve evil.example.com
python3 tools/clis/virustotal.py domain evil.example.com --relationships resolutions

# Cert-CN pivot (paid Censys plan)
python3 tools/clis/censys.py search 'services.tls.certificates.leaf_data.subject.common_name:"evil.example.com"'

# Ransomware-claim sweep
python3 tools/clis/ransomwarelive.py search --q "example"

Starting from a URL

URL
├── Live scan (when a fresh capture is wanted)
│   └── /lookup-urlscan submit             → DOM, network log, screenshot, verdict
├── Existing scans
│   └── /lookup-urlscan search 'page.url:' → past captures (faster, no re-submit)
├── Reputation
│   └── /lookup-virustotal url             → detection ratio + redirect chain
├── Hostname pivot
│   └── extract hostname → pivot as a domain (above)
└── Path/kit fingerprint
    └── /lookup-urlscan search 'page.url:""'  → other URLs with same kit path

Concrete commands:

python3 tools/clis/urlscan.py submit "https://login-acme[.]net/secure/portal" --tags phishing
python3 tools/clis/urlscan.py search 'page.url:"/secure/portal/"'
python3 tools/clis/virustotal.py url "https://login-acme[.]net/secure/portal"

Starting from a file hash

Hash (md5 / sha1 / sha256)
├── Reputation + family
│   ├── /lookup-virustotal file           → detection stats, family tags, sandbox verdicts
│   └── /lookup-otx file                  → community pulses, campaign attribution
├── C2 extraction (highest-value pivot)
│   └── /lookup-virustotal file  --relationships contacted_ips,contacted_domains,contacted_urls
│       → pivot each contacted indicator
├── Family lineage
│   └── /lookup-virustotal file  --relationships dropped_files,parent_files,similar_files
│       → walk dropper/payload chain
├── Submitter/source
│   └── /lookup-virustotal file  --relationships submissions   (premium)
├── Compilation artefacts
│   └── /lookup-virustotal file           → response includes pdb_path, compiler, packers
│       → distinctive PDB path → /lookup-virustotal search …  (premium)
└── Internal correlation
    ├── /lookup-misp search-attributes --value    → events that already include this hash
    └── /lookup-opencti lookup                    → observables/indicators already in your knowledge base

Concrete commands:

# Detection + family
python3 tools/clis/virustotal.py file 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8

# C2 extraction (highest-value)
python3 tools/clis/virustotal.py file 5e884898... \
  --relationships contacted_ips,contacted_domains,contacted_urls --limit 25

# Family lineage
python3 tools/clis/virustotal.py file 5e884898... \
  --relationships dropped_files,parent_files,similar_files --limit 10

# OTX community attribution
python3 tools/clis/otx.py file 5e884898...

# Internal correlation via MISP / OpenCTI
python3 tools/clis/misp.py search-attributes --value 5e884898...
python3 tools/clis/opencti.py lookup 5e884898...

Starting from a TLS certificate (SHA-256 or CN)

Certificate
├── Hosts presenting this cert
│   ├── /lookup-shodan search 'ssl.cert.fingerprint:'    → IPs serving the cert
│   └── /lookup-censys search 'services.tls.certificates.leaf_data.fingerprint_sha256:'
└── Other certs by same Subject
    └── /lookup-censys certs view      → issuer, validity, sibling SANs

Concrete commands:

python3 tools/clis/shodan.py search 'ssl.cert.fingerprint:"5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"'
python3 tools/clis/censys.py certs view 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8

Starting from a registrant email or org name

Registrant identity
├── Reverse-WHOIS (premium / outside the bundled lookups — DomainTools, WhoisXMLAPI, Constella)
├── Internal MISP correlation
│   └── /lookup-misp search-attributes --type whois-registrant-email --value 
└── Domains attributed to the same actor in OTX pulses
    └── /lookup-otx pulse-search ""

Starting from an actor name, malware family, or campaign label

Actor / family / campaign string
├── Ransomware group (if applicable)
│   ├── /lookup-ransomwarelive group             → group profile, victims, IOCs
│   ├── /lookup-ransomwarelive iocs              → IOC dump for this group
│   └── /lookup-ransomwarelive yara              → YARA rules attributed to the group
├── Community pulses
│   └── /lookup-otx pulse-search ""   → IOCs from public pulses
├── MISP events
│   └── /lookup-misp search-events --tag "actor=" → previously catalogued IOCs
├── OpenCTI knowledge base
│   └── /lookup-opencti search ""      → intrusion sets, malware, reports, campaigns; then `get ` for relationships
└── Surface-web mentions (forums + Telegram)
    └── /darkweb-collection (with selectors built from the actor's known aliases)

Starting from a Telegram channel handle or onion mirror

Channel handle / .onion address
├── Telegram channel handle
│   └── scripts/telegram_monitor.py --once --channels @  --selectors-file 
│       → match channel content against your IOC selectors
└── .onion address
    └── scripts/onion_search.py --query ""
        → resolve other clearnet/onion mirrors that mention the same string

(See /darkweb-collection for OPSEC and access posture before running these.)

Pivot quality scoring

Not all pivots are equal. Score each before reporting:

| Pivot | Confidence | False-positive risk | Why | |---|---|---|---| | C2 IP/domain extracted from a reverse-engineered sample | High | Low | Direct, sample-grounded | | TLS cert SHA-256 reused across hosts | High | Low | Cert reuse is intentional; the actor pinned it | | Cert subject CN reused across hosts | Medium-High | Low-Medium | CN can be reused legitimately on shared certs | | JARM fingerprint match | Medium-High | Medium | Strong for unusual JARMs; weak for default-config tools | | Passive DNS (domain → IP, current) | Medium-High | Low-Medium | Direct; check the dates | | Passive DNS (domain → IP, historical only) | Medium | Medium | Stale; could pre-date the campaign | | Same WHOIS registrant email | Medium | Medium | Many real users share an email across domains | | Same name server | Low | High | Popular NS providers host millions of unrelated domains | | Same /24 IP range | Low | High | Cloud and CDN sharing | | Same registrar | Very low | Very high | Registrars are infrastructure; near-meaningless alone | | Same JA3/JA3S TLS client fingerprint | Medium | Medium | Strong for custom clients; weak for OS-default stacks | | Same MITRE technique | Very low | Very high | Technique-level overlap is not infrastructure overlap |

Rule of thumb: a pivot chain is only as strong as its weakest link. Three High pivots in series ≈ High overall. One High + two Mediums ≈ Medium. Anything with a Low link ≈ Low overall, regardless of length.

Pivot routing — by goal

| Goal | First-choice lookup | Second | |---|---|---| | IP reputation | /lookup-virustotal | /lookup-abuseipdb, /lookup-greynoise | | Filter out internet-wide scanners | /lookup-greynoise | — | | Service banner / vuln view | /lookup-shodan | /lookup-censys (richer, slower) | | Cert fingerprint pivot | /lookup-censys | /lookup-shodan | | JARM pivot | /lookup-shodan | /lookup-censys | | Passive DNS | /lookup-virustotal --relationships resolutions | /lookup-otx | | Subdomain enumeration | /lookup-shodan dns domain | /lookup-censys (paid) | | Live URL analysis | /lookup-urlscan submit | /lookup-virustotal url | | Existing URL captures | /lookup-urlscan search | — | | File reputation | /lookup-virustotal | /lookup-otx, /lookup-reversinglabs hash | | Authoritative classification + MITRE ATT&CK mapping | /lookup-reversinglabs report --detailed | /malware-analysis | | Pivot to siblings by malware family / threat name | /lookup-reversinglabs search 'threatname:' | /lookup-otx pulse-search | | Parent container / extracted children | /lookup-reversinglabs containers / extracted | — | | C2 extraction from a hash | /lookup-virustotal --relationships contacted_ips,contacted_domains,contacted_urls | /lookup-reversinglabs report --fields networkthreatintelligence | | Community/actor attribution | /lookup-otx | /lookup-misp search-events --tag, /lookup-crowdstrike indicator (actors + malware families linked to the IOC) | | Actor → TTPs / reports / related infrastructure | /lookup-crowdstrike actor "" then ttps / reports --actor | /threat-actor-profiling | | Internal correlation against own catalogued IOCs | /lookup-misp | /lookup-opencti lookup (observables + indicators, then get for relationships) | | Ransomware-group profile / IOCs / YARA | /lookup-ransomwarelive | — | | Ransomware-victim sweep on org name | /lookup-ransomwarelive search --q | — | | Underground-forum / Telegram chatter | /darkweb-collection | — |

Worked example — phish URL → APT cluster

A walkthrough of a realistic four-hop chain. Source rating in brackets.

Seed. https://login-acme[.]net/secure/portal/login.html reported by an internal user as a suspicious password-reset email. Source: internal phishing inbox (B2).

Hop 1 — characterise the URL.

python3 tools/clis/urlscan.py search 'page.url:"login-acme.net"'
python3 tools/clis/virustotal.py url "https://login-acme.net/secure/portal/login.html"

Result: URLScan shows two prior submissions in the last 14 days (both flagged phishing), screenshot is an Acme-cloned login page, JS posts credentials to https://api.login-acme[.]net/auth. VT detection ratio 12/89 — phishing. Pivot candidate: domain login-acme.net (B2).

Hop 2 — domain → resolved IP and cert.

python3 tools/clis/shodan.py dns resolve login-acme.net api.login-acme.net
python3 tools/clis/virustotal.py domain login-acme.net --relationships resolutions

Result: both names currently resolve to 198.51.100.42; passive DNS shows the names appeared together on 2026-04-12 (10 days ago — newly weaponised). Pivot candidates: IP 198.51.100.42 (B2), creation cluster of *.login-acme.net subdomains.

Hop 3 — IP → service fingerprint and cert SHA-256.

python3 tools/clis/shodan.py host 198.51.100.42

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Liberty91LTD](https://github.com/Liberty91LTD)
- **Source:** [Liberty91LTD/cti-skills](https://github.com/Liberty91LTD/cti-skills)
- **License:** MIT
- **Homepage:** https://liberty91.com/cti-skills

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.