THREAT INTEL · HANDS-ON

Tracking when public IPs change

A public IP address is a lease, not an identity. That one fact breaks the way most teams use blocklists and allowlists — and it's why a simple change-tracker is one of the highest-value things you can build. With a lab you can run right now.

Y
YogeshGlobal SOC · 14 October 2026 · 11 min read

An IP is a lease, not an identity

When we see an IP in an alert, we treat it like a name — "that's the attacker." But public IPv4 space is constantly reassigned, recycled, NAT'd and rotated. Today's malware command-and-control server is, a month later, a legitimate cloud VM rented by a small business. The IP didn't change; who holds the lease changed.

Your instinct in the SOC — "don't block it permanently, it might be clean soon" — is exactly right, and it's the whole point. Two consequences follow:

The core ideaStop treating IP indicators as permanent facts. Treat them as time-boxed, confidence-scored observations. A change-tracker is what lets you do that — it watches the lease, not just the number.

Two blind spots: block rot and allowlist rot

Block rot is the one you already feel: a block list grows forever, and old entries quietly start hitting clean, reassigned space — false positives you never revisit.

Allowlist rot is the dangerous one almost nobody watches. You add a partner's or vendor's IP to a firewall allow rule. Two years later that IP has been released and re-leased to someone else — and your firewall is now trusting a stranger, with a permit rule straight into your network. The scary part: nothing alerts, because the IP never changed. Only the owner did.

The signal that matters mostWhen an IP you trust changes its ASN or registered owner, that's your highest-value event. It means the lease changed hands, and any trust you granted the previous holder no longer applies.

What to track over time

For a watchlist of IPs and the domains you care about, snapshot these fields on a schedule and diff each run against the last:

FieldSourceWhy a change matters
A / AAAA recordsDNSA service moved IPs — migration, failover, or fast-flux malware.
ASN + netblock ownerRIPEstat / RDAPThe lease changed hands — the highest-signal event.
Reverse DNS (PTR)DNSThe name an IP claims changed.
GeolocationIP geo APIsCountry/region drift on a "known" IP.
Reputation / exposureBlocklists, Shodan tagsNewly listed (badness arrived) or delisted (block can expire).
Open ports / servicesShodan InternetDBExposure changed on your own IPs.
TLS certificateCT logsA new certificate appeared on the host.

Store one row per observation, so every field has a history you can graph and alert on. That's the whole trick: collect → diff → alert.

Hands-on lab: snapshot & diff an IP

Let's build the smallest useful version by hand. All of this is passive — it reads public registries, never probes the target. Run it in PowerShell on Windows (the ideas are identical on macOS/Linux with dig/curl).

1

What does the domain resolve to today?

Resolve-DnsName example.com -Type A | Select -Expand IPAddress
2

Who holds the lease on that IP right now?

RIPEstat (free, no key) maps an IP to its ASN and prefix, and the ASN to its holder:

# IP -> ASN + prefix
curl "https://stat.ripe.net/data/network-info/data.json?resource=93.184.216.34"
# ASN -> holder name
curl "https://stat.ripe.net/data/as-overview/data.json?resource=AS15133"
3

Reverse DNS and exposure

Resolve-DnsName 93.184.216.34 -Type PTR
# Shodan InternetDB (passive): ports, tags, CVEs already known for this IP
curl "https://internetdb.shodan.io/93.184.216.34"
4

Snapshot + diff in ~30 lines of Node

Save this as ipwatch.js. First run stores a baseline; every later run prints what changed.

const fs = require("fs");
const dns = require("dns").promises;

async function getJson(u){ const r = await fetch(u); return r.ok ? r.json() : null; }

async function snapshot(domain){
  const ips = await dns.resolve4(domain).catch(()=>[]);
  const out = {};
  for (const ip of ips){
    const ni = await getJson(`https://stat.ripe.net/data/network-info/data.json?resource=${ip}`);
    const asn = ni?.data?.asns?.[0] ? "AS"+ni.data.asns[0] : "";
    const ov = asn && await getJson(`https://stat.ripe.net/data/as-overview/data.json?resource=${asn}`);
    const ptr = (await dns.reverse(ip).catch(()=>[]))[0] || "";
    const idb = await getJson(`https://internetdb.shodan.io/${ip}`);
    out[ip] = { asn, holder: ov?.data?.holder || "", ptr,
                ports:(idb?.ports||[]).join(","), tags:(idb?.tags||[]).join(",") };
  }
  return out;
}

(async () => {
  const domain = process.argv[2] || "example.com";
  const file = `ipwatch_${domain}.json`;
  const now = await snapshot(domain);
  const prev = fs.existsSync(file) ? JSON.parse(fs.readFileSync(file)) : null;
  if (!prev){ console.log("Baseline saved for", domain); }
  else {
    for (const ip of new Set([...Object.keys(prev), ...Object.keys(now)])){
      const a = prev[ip], b = now[ip];
      if (!a) console.log(`NEW IP   ${ip}  ${b.holder}`);
      else if (!b) console.log(`GONE IP  ${ip}  (was ${a.holder})`);
      else for (const k of Object.keys(b))
        if (a[k] !== b[k]) console.log(`CHANGED  ${ip}  ${k}: "${a[k]}" -> "${b[k]}"`);
    }
  }
  fs.writeFileSync(file, JSON.stringify(now, null, 2));
})();
node ipwatch.js example.com     # run 1: baseline
node ipwatch.js example.com     # run later: prints only what changed
# CHANGED  93.184.216.34  holder: "EDGECAST" -> "EDGIO"   <- the lease changed hands

That's the entire concept in one file: resolve, record owner/PTR/ports, and diff. Everything else — scheduling, a watchlist, alerting, scoring — is scaffolding around this core.

Building the tracker (and wiring it to Sentinel)

To make it real, wrap that loop in the same shape as any monitoring pipeline:

Scheduled snapshot Store history Diff vs last Change event-> Sentinel / email
The same collect → store → diff → alert loop behind every detection tool.

The change events worth alerting on

EventWhy it matters
Allow-listed IP changed ASN/ownerYou're now trusting whoever got the lease. Re-verify the rule. (The one nobody watches.)
Blocked IP delisted / owner changedReview and expire the block so you stop over-blocking clean space.
Your own asset IP changed unexpectedlyCloud misconfig, failover, or — worst case — a BGP hijack.
Domain A-record flux > N/hour across ASNsFast-flux: malware / phishing infrastructure, not a CDN.
Monitored IP newly listed on a blocklistA trusted neighbour just went bad on shared hosting.

Video walkthrough

📹 Walkthrough video comingA screen-recorded run of the lab above: resolving an IP, checking its owner on RIPEstat, and watching ipwatch.js catch an ownership change.
What this teaches youBuilding this touches the whole SOC-engineering stack: BGP/ASN & RDAP, passive DNS, reputation systems, time-series diffing, and confidence/decay scoring — plus the collect → diff → alert pattern under every detection tool. It also rewires how you reason about every IP indicator you'll ever see.

Want this built for your environment?

We build lightweight monitoring like this — IP and DNS change tracking wired into Sentinel — for Melbourne businesses.

Try DNS AuditTalk to Yogesh
Y
Yogesh runs Global SOC, a security-first app and IT studio in Glen Waverley, Melbourne, helping small businesses build software and keep it secure.