# Subdomain Takeover Auditor — Find Claimable DNS Records (`alaudinburki/subdomain-takeover-auditor`) Actor

Find dangling DNS records pointing at decommissioned third-party services — the kind anyone can claim to hijack a subdomain of your brand for phishing. Discovers subdomains passively from certificate transparency, resolves CNAMEs, and confirms matches against a public takeover-fingerprint database.

- **URL**: https://apify.com/alaudinburki/subdomain-takeover-auditor.md
- **Developed by:** [alaudin burki](https://apify.com/alaudinburki) (community)
- **Categories:** Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Subdomain Takeover Auditor — Find Claimable DNS Records

A team decommissions a service — an S3 bucket, a Heroku app, a GitHub Pages site — and
forgets the DNS record pointing at it. That record now **dangles**: it resolves, but to
a third-party resource nobody at the company owns anymore. Whoever claims that resource
on the other service now controls a subdomain of a trusted brand.

That is used for credential phishing, cookie theft, and bypassing domain allowlists —
and it happens constantly, because **nobody owns stale DNS**. AWS has published a threat
spotlight on exactly this pattern.

**Entirely passive. No scanning, no login, no port probing.**

### How it works — three legal steps

1. **Enumerate subdomains from certificate transparency** (crt.sh). Public logs, not a
   scan — every subdomain that ever got a TLS certificate shows up here.
2. **Resolve CNAMEs over DNS-over-HTTPS.** A lookup, not a probe.
3. **Match against a public takeover-fingerprint database** ([EdOverflow's
   can-i-take-over-xyz](https://github.com/EdOverflow/can-i-take-over-xyz), 76 catalogued
   services). Only when a CNAME matches a known-vulnerable pattern is the already-public
   page fetched once, to confirm the exact string that means "unclaimed."

### The three verdicts

| Risk | Meaning |
|---|---|
| **`claimable`** | The fingerprint is confirmed on the live page (or DNS returns NXDOMAIN for a service known to signal that way). High confidence — fix this. |
| **`suspicious`** | The CNAME matches a vulnerable service's pattern, but the page couldn't confirm it. Needs a human look — not claimed as a finding. |
| `ok` | No match, or the resource is clearly still in use. |

**Confidence is never claimed without evidence.** A pattern match with an inconclusive
page fetch is `suspicious`, not `claimable` — this actor will not tell you something is
exploitable unless it actually confirmed it.

### Input

```json
{ "domains": ["yourcompany.com"] }
```

That's it. You do not list subdomains — they are discovered automatically.

### Sample output

```json
[
  {
    "subdomain": "old-blog.yourcompany.com",
    "apexDomain": "yourcompany.com",
    "cname": "yourcompany.github.io",
    "matchedService": "GitHub Pages",
    "risk": "claimable",
    "verdict": "CNAME points at GitHub Pages (yourcompany.github.io), and the page confirms the \"not claimed\" fingerprint (\"There isn't a GitHub Pages site here.\"). This subdomain can very likely be claimed by anyone right now. Delete the DNS record, or claim the resource yourself.",
    "confidence": "high"
  }
]
```

### Typical uses

- **Attack-surface hygiene** — the classic use. Run it monthly; services get
  decommissioned continuously and this never stops finding new ones.
- **Pre-acquisition / vendor due diligence** — audit a company's subdomains before you
  inherit their DNS debt.
- **Agency deliverable** — a "3 claimable subdomains on your own domain" finding is a
  concrete, demonstrable result that sells security or DevOps engagement work far
  better than a generic pitch.
- **Bug bounty recon** — this is the same technique bounty hunters use, done for your
  own domains.

### Pricing

**$2.50 / 1,000 subdomains checked** (`$0.0025` per result), plus a near-zero start fee.
A typical domain with a few hundred subdomains costs well under a dollar. Never charged
beyond `maxItems`.

### ⚠️ Read before you act

- **This is detection, not proof of active exploitation.** A `claimable` finding means
  the resource can very likely be claimed — it does not mean anyone already has.
  Investigate before publicly disclosing or escalating.
- **Only audit domains you own or are authorized to test.** Certificate transparency
  and DNS are public, so no permission is needed to *look* — but acting on what you
  find on a domain you don't control is a different matter entirely.
- **The fingerprint database is community-maintained** and does not cover every
  service. A clean result reduces risk; it does not guarantee zero exposure.
- **`suspicious` rows need a human.** They exist so the actor never inflates a maybe
  into a confirmed finding.

### FAQ

- **Does this scan ports or bypass authentication?** No. Every step reads public
  certificate logs, resolves DNS, or fetches a page that anyone's browser could load.
- **Will this get me blocked or flagged?** No — there is no scanning behavior to
  trigger a WAF or IDS. It behaves like a normal browser visiting a public page.
- **Why is `confirmWithPageCheck` optional?** Turning it off is faster (no page
  fetches) but every match is then reported as `suspicious` rather than `claimable`,
  since nothing confirmed it.
- **What if my domain has thousands of subdomains?** Raise `maxSubdomainsPerDomain`
  and `maxItems`. Certificate transparency logs can be large for big companies.

### Related actors

- **Typosquat Monitor** — the same idea from the other direction: lookalike domains
  registered to impersonate you, rather than your own DNS pointing at nothing.
- **SSL Certificate Checker** — expiry and validity for certificates you do control.
- **Broken Link Resurrector** — dead links on your own site, with the archived fix.

# Actor input Schema

## `domains` (type: `array`):

Apex domains to audit, e.g. yourcompany.com. Subdomains are discovered automatically from public certificate transparency logs — you do not need to list them.

## `maxSubdomainsPerDomain` (type: `integer`):

Cap on how many discovered subdomains to check per domain.

## `confirmWithPageCheck` (type: `boolean`):

For a CNAME that matches a known-vulnerable service, fetch the page once to confirm the takeover fingerprint. Without this, matches are reported as 'suspicious' rather than 'claimable' — turning it off is faster but less certain.

## `onlyRisky` (type: `boolean`):

Return only claimable and suspicious subdomains. Turn off to get every subdomain checked, including the ones that are fine — useful for a full inventory.

## `concurrency` (type: `integer`):

How many subdomains to check at once.

## `maxItems` (type: `integer`):

Hard cap on rows returned. You are never charged beyond this.

## Actor input object example

```json
{
  "domains": [
    "github.com"
  ],
  "maxSubdomainsPerDomain": 500,
  "confirmWithPageCheck": true,
  "onlyRisky": true,
  "concurrency": 8,
  "maxItems": 3000
}
```

# Actor output Schema

## `results` (type: `string`):

Claimable and suspicious subdomains with a plain-language verdict.

## `qualityReport` (type: `string`):

Counts by risk level and any lookup problems.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "domains": [
        "github.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("alaudinburki/subdomain-takeover-auditor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "domains": ["github.com"] }

# Run the Actor and wait for it to finish
run = client.actor("alaudinburki/subdomain-takeover-auditor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "domains": [
    "github.com"
  ]
}' |
apify call alaudinburki/subdomain-takeover-auditor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,alaudinburki/subdomain-takeover-auditor"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/X0j7JCwCbJ1miPxQC/builds/BVfOoMarcp1Vo9xqw/openapi.json
