What would you like to learn?

Try PowerShell, networks, XSS, or Step 138

Browse the full curriculum →

Penetration testing

Step 171. OSINT Advanced — Subdomain and Asset Enumeration

Step 171Estimated practice · 2 hours 30 minutes

Level 2 — Introduction to Security and Attack Skill Basics | Difficulty ★★★☆☆ | Estimated time: 2 hours 30 minutes

Prerequisites: the name-resolution structure from Step 33 (DNS), and Step 170 (OSINT collection principles) completed. You can write Python loops.

  • What you need: Python 3, a Linux lab (WSL or Kali). You can practice the principles without any external queries.
  • ⚠️ All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime.
  • Caution: subdomain enumeration targets are also only organizations you have permission for. In this chapter we don’t query real domains — we learn the principle with a local simulation and tool output as examples.

A company doesn’t know all of its own servers. dev.company.com, old.company.com, vpn.company.com — each had a reason when it was created, but once the person in charge moves on, it’s forgotten. The attacker, though, doesn’t forget. Every subdomain is a door, and a single abandoned test server is the starting point of a breach. Today you learn how an attacker draws an organization’s attack surface, and flipping it around, you confirm the fact that "my asset inventory is the starting point of defense."


1. Learning Objectives

By the end of this chapter, you will be able to:

  • Explain the difference between passive and active enumeration
  • Explain why the Certificate Transparency log (crt.sh) becomes a subdomain list
  • Demonstrate the principle of wordlist brute-force enumeration with Python
  • Build an asset inventory in the structure domain → subdomain → service → version
  • Clearly state the boundary between enumeration and unauthorized access

2. Background Knowledge — Today’s Tools and Concepts

Today’s Tools at a Glance

Category Details
Language/environment Python 3, Linux shell (getent, /etc/hosts)
Today’s tools (principle) wordlist enumeration, (concept introduction) crt.sh · subfinder · httpx
Concepts needed DNS review (Step 33), passive/active enumeration, Certificate Transparency logs, attack surface
Today’s artifact enumeration-principle simulation output + one asset-inventory-structure.md

2-1. Passive Enumeration vs Active Enumeration

There are two roads to finding subdomains.

Passive enumeration never connects to the target even once — it digs through records that are already public. Search engines, DNS collection services, and today’s protagonist, the Certificate Transparency log, belong here. Quiet, but it can only find what’s been recorded.

Active enumeration queries the target directly. The representative method is wordlist brute-forcing — asking "does this name exist?" tens of thousands of times. It finds more, but it leaves traces in the target’s logs. Use it only against targets you have permission for.

2-2. Certificate Transparency Logs — A Public Registry

When an HTTPS certificate is issued, it gets recorded in a public ledger called a Certificate Transparency log. It’s a device for catching fraudulent certificates, but it has a side effect — anyone can search this ledger and see "the list of certificates issued for this domain." Since certificates have subdomains written in them, this ledger becomes a subdomain list. crt.sh is this ledger’s search window.

2-3. Wordlist Brute-Force — Asking About Every Plausible Name

The principle of active enumeration is simple. You prepend a list of common names (www, mail, dev, vpn, test …) to the domain, ask DNS, and collect only the ones that answer. The name resolution you learned in Step 33 is, as-is, the tool’s heart — the tool is nothing special; it’s the repetition of "word list × DNS query × recording results."

2-4. Attack Surface — Our Organization as the Attacker Sees It

The attack surface is the sum of everything reachable from the outside. Subdomains under the domain, services under those, versions inside those — as this tree grows, so do the doors to guard. So an organization’s first defensive task is knowing "how many doors do we have," and that list’s name is the asset inventory. The attacker’s enumeration and the defender’s asset management are two faces of the same act.


3. Follow Along

3-1. Name Resolution Review — The Smallest DNS

Let’s revive Step 33’s structure at your fingertips. In Linux, the first step of a name becoming an IP is the local file /etc/hosts.

Input (in your Linux lab):

cat /etc/hosts
getent hosts localhost
python3 -c "import socket; print(socket.gethostbyname('localhost'))"

Output (measured 2026-09-09, WSL Ubuntu 24.04):

127.0.0.1	localhost
127.0.1.1	XI3492.localdomain	XI3492
...(IPv6 entries)...
::1             localhost
127.0.0.1

How to read it: the name localhost is written in /etc/hosts, and getent and Python return the address according to that rule. Subdomain enumeration is repeating this question for tens of thousands of names — you already know the query method; what’s new today is building the list.

3-2. A Wordlist Simulation — The Heart of Enumeration

Let’s see the principle without touching external DNS. We apply a word list against fictional zone data.

Input: enum_lab.py:

# Fictional DNS zone — in reality this would be the DNS server's answer
zone = {
    "www.lab.example":  "10.0.0.10",
    "mail.lab.example": "10.0.0.25",
    "dev.lab.example":  "10.0.0.99",   # assumed abandoned dev server
    "vpn.lab.example":  "10.0.0.1",
}

wordlist = ["www", "mail", "ftp", "dev", "blog", "vpn", "test", "shop"]
found = []
for word in wordlist:
    name = f"{word}.lab.example"
    if name in zone:                      # in reality a DNS query happens here
        found.append((name, zone[name]))

print(f"tried {len(wordlist)} words → found {len(found)}")
for name, ip in found:
    print(f"  {name:22s} -> {ip}")
print("Candidate the admin forgot:", "dev.lab.example")

Output (measured 2026-09-09):

tried 8 words → found 4
  www.lab.example        -> 10.0.0.10
  mail.lab.example       -> 10.0.0.25
  dev.lab.example        -> 10.0.0.99
  vpn.lab.example        -> 10.0.0.1
Candidate the admin forgot: dev.lab.example

How to read it: eight questions found four. Real tools’ wordlists run to tens or hundreds of thousands. And look at the last line — dev.lab.example is assumed to be a server used briefly during development and then abandoned. Exactly this kind of "door the admin forgot" is what an attacker looks for. Names not in the list (blog, test) come back as silence — if it doesn’t exist, nothing comes out; that’s all there is.

Why do this: "tens of thousands of queries" sounds grand, but the skeleton is these 15 lines. When you know a tool’s principle, your eyes read its output differently.

3-3. Asking for a Name That Doesn’t Exist — The Face of a Real Error

Let’s see what happens when real code asks DNS for a nonexistent name.

Input (in your Linux lab):

python3 -c "import socket; socket.gethostbyname('no-such-host-zzz.invalid')"

Output (measured 2026-09-09):

socket.gaierror: [Errno -2] Name or service not known

How to read it: "name not known" — this is the signal for "doesn’t exist" in wordlist brute-forcing. An enumeration tool uses this error not as an exception but as data. On error, it drops the candidate from the list; when an address comes back, it adds it to the list. When writing a real tool, you catch this signal with try/except socket.gaierror.

3-4. The Windows of Passive Enumeration — Screen Examples

Real lookups require external access, so we learn the output shapes of two tools through screen examples.

What asking crt.sh (certificate log search) with %.domain looks like:

Screen example (what crt.sh lookup results look like):
  Issued to (Common Name)          Registered at
  www.example-corp.com             2025-11-02
  mail.example-corp.com            2025-11-02
  dev-internal.example-corp.com    2026-01-19   ← traces of something built for internal use
  old-shop.example-corp.com        2024-03-30   ← forgotten-service candidate

What subfinder (a tool combining many public sources) looks like:

Screen example (what running subfinder -d domain -silent looks like):
www.example-corp.com
mail.example-corp.com
vpn.example-corp.com
dev-internal.example-corp.com
...
[INF] Found 38 subdomains for example-corp.com in 12 seconds

How to read it: passive enumeration obtains this list without connecting to the target — crt.sh from the certificate ledger, subfinder by merging dozens of public sources. When prefixes like dev, old, test, internal stand out in the list, those are "doors worth examining." The domains and names in the examples are all fabricated, and real lookups are performed only against permitted targets.

3-5. Building the Asset Inventory Structure

We flip the enumeration result into a defender’s document. Write it as a tree structure.

# Asset Inventory Structure (date: ____)

Domain: (target domain)
├── Subdomain: www — Service: web(443) — Version: ____ — Owner: ____
├── Subdomain: mail — Service: SMTP(25) — Version: ____ — Owner: ____
├── Subdomain: dev — Service: web(8080) — Version: ____ — Owner: none ⚠️
└── Subdomain: old — Service: web(80) — Version: outdated ⚠️ — Action: consider decommissioning

How to read it: the fields marked ⚠️ are where defense begins — assets with no owner, outdated versions, or ones nobody remembers exist. The attacker’s enumeration list and the defender’s asset inventory are the same table; the defender just adds the "owner" and "action" columns.


4. Missions & Exercises

Mission — Completing a One-Part Asset Inventory Document

  1. Expand the 3-2 simulation wordlist to 20 or more words and run it
  2. Build asset-inventory-structure.md with the 3-5 tree structure — at least 5 items, each with service, version, and owner fields
  3. Write a two-line summary of passive/active enumeration at the top of the document — distinguishing each by "does it connect to the target?"
  4. Write three of your own rules for picking "door the admin forgot" candidates (prefix, version, blank owner, etc.)
  5. Nowhere in the document include results of querying real third-party domains — examples must be fabricated

Exercises

Exercise 1. Explain why passive enumeration is "quiet," and the limitation it pays as the price.

Exercise 2. Explain why the Certificate Transparency log gets used as an attack-surface map, differently from its original purpose (detecting fraudulent certificates).

Exercise 3. Explain why the "name not known" error in 3-3 is not a failure but data for an enumeration tool.

Exercise 4. Explain what it means that the attacker’s subdomain enumeration and the defender’s asset management are "two faces of the same act."


Answers & completion criteria · expand/collapse

5. Model Answers & Completion Criteria

Mission Model Answer

An example of the two-line summary at the top of the document:

Passive enumeration: digs through public records (certificate logs, search engines) without connecting to the target — quiet, but only what's recorded.
Active enumeration: queries DNS directly with a wordlist — finds more but leaves logs. Permission required.

Example rules for selecting "forgotten doors": ① names with prefixes like dev/test/old/backup. ② services whose versions are older than currently supported ones. ③ items with nobody to fill the owner field. The three rules share one thing: "an asset nobody pays attention to = an asset patches never reach."

How to verify: ① did you run the simulation with a 20-word list? ② does the document have service, version, and owner fields? ③ is the passive/active distinction written as "whether it connects"? ④ are there no measured results on third-party domains? All being "yes" means complete.

Exercise Answers

Answer 1. Passive enumeration sends not a single packet to the target server and only reads third-party public records, so no trace remains in the target’s logs. The price is that "it can only find what’s been recorded" — subdomains that never got certificates and assets search engines don’t know are invisible to passive enumeration.

Answer 2. The transparency log is a ledger that publicly exposes "who received a certificate for which name." Since certificates have subdomains written in them, digging through this ledger alone builds an organization’s name list. Openness built for defense becoming material for information collection — a representative case where a security device’s side effect becomes an attack-surface map.

Answer 3. The purpose of wordlist brute-forcing is separating "what exists" from "what doesn’t." socket.gaierror is a normal answer meaning "doesn’t exist," and the tool receives this signal, drops the candidate, and moves to the next word. It’s not an exception that should halt the program but a branch the loop handles every iteration — the perspective of reading errors as data is the basis of writing network tools.

Answer 4. Both do the same work of turning "what doors does this organization have" into a list. What differs is the purpose and the next action — the attacker picks a weak door from the list and knocks on it, while the defender attaches an owner and patches to every door on the list. So if the defender builds this list first, we know what the attacker knows. The asset inventory is the first page of defense.

Completion Criteria Checklist

  • [ ] I can explain passive/active enumeration distinguished by "whether it connects"
  • [ ] I can state the Certificate Transparency log’s principle and side effect
  • [ ] I ran and expanded the wordlist simulation
  • [ ] I measured that socket.gaierror is the signal for "doesn’t exist"
  • [ ] I reviewed /etc/hosts and the name-resolution flow
  • [ ] I completed the domain→subdomain→service→version tree document
  • [ ] I can write the boundary between enumeration and unauthorized access in my own words

6. Common Pitfalls & Fixes

Wall 1. The program halts on socket.gaierror: [Errno -2] Name or service not known

Symptom (measured 2026-09-09):

socket.gaierror: [Errno -2] Name or service not known

Cause: you didn’t handle the exception for the normal response when asking for a nonexistent name. In wordlist brute-forcing, this error occurring tens of thousands of times is normal.
Fix: wrap the query in try/except socket.gaierror, and on error record "not present" and move to the next word. Errors are data (3-3).

Wall 2. crt.sh search fails because I don’t know what % is

Symptom: you typed a domain into crt.sh and no subdomains come out.
Cause: crt.sh’s wildcard is % — you must write %.example.com to search "every name ending in this domain."
Fix: type %.domain format into the search box. % is the SQL wildcard meaning "any characters."

Wall 3. Looking at the collected list, I connect first

Symptom: you immediately connect to a discovered subdomain or knock on its ports.
Cause: you didn’t separate "finding" from "knocking."
Fix: this stage is collection only. Accessing discovered assets is the next stage, requiring separate permission. Enumeration being legal doesn’t make access legal — the hand that builds the list and the hand that knocks on doors carry different permissions.

Wall 4. Zero finds in the simulation

Symptom: you added more words but the found count didn’t change.
Cause: you only added words not in zone, or the spelling of the name combination differs from the zone data — check the junction between dev and dev.lab.example.
Fix: the simulation’s advantage is that you know the answer (the contents of zone). Lay the wordlist and the zone’s names side by side and compare. In the field there’s no answer key, so take away from this exercise the sense that "the quality of the list determines the result."

Wall 5. A big list but no document

Symptom: you have text with hundreds of subdomains and no organization.
Cause: you mistook collection for the goal — the list is raw material, not the deliverable.
Fix: move it into the 3-5 tree. The moment service, version, and owner fields attach, the list becomes an asset document; the moment ⚠️ attaches, it becomes a work plan.


7. Summary

Today’s Concepts

Concept One-line explanation
Passive enumeration Digs only public records — quiet, but only what’s recorded
Active enumeration Queries DNS directly — finds more but leaves traces
Certificate Transparency log A public ledger of certificate issuance — with the side effect of becoming a subdomain list
Wordlist brute-force A common-names list × repeated DNS queries — harvesting only what answers
Attack surface The sum of everything reachable from outside
Asset inventory A defense document attaching owner, version, and action to the attack surface
crt.sh / subfinder Windows of passive enumeration — certificate ledger search / public-source integration

Today’s Commands & Code

Tool What it does
cat /etc/hosts Checking the smallest name-resolution rule (Step 33 review)
getent hosts name A name-resolution query
socket.gethostbyname() Name → address in Python
try/except socket.gaierror Handling the "doesn’t exist" signal as data
f"{word}.{domain}" Combining candidate names from a wordlist
(screen example) subfinder -d domain -silent Multi-source subdomain collection

An Instinct More Important Than Commands

Today’s core is not the tool but the perspective — the fact that "an organization doesn’t know all of its own servers," and the fact that the forgotten door opens first. Having run the 15-line wordlist brute-force yourself, you now know subfinder’s output is not magic but a loop. And the same list, made by a defender, becomes an asset document. The attacker’s first page and the defender’s first page are the same — this is the real conclusion of advanced OSINT.


Once every box is checked, Step 171 is complete.

ONE STEP FURTHER

Finished this lesson?

Check the completion criteria, then mark your progress.

Something wrong with this page or a link? Let us know.
Next