9  DNS, Subdomains, and Safe Fixture Requests

Introduction

In this lesson, you’ll connect the project to the networking ideas behind subdomain enumeration without sending traffic to the internet. The examples use deterministic fixtures that behave like small pieces of requests and DNS-adjacent infrastructure.

Normally when we talk about subdomain brute forcing we also talk about enumeration. Enumeration can draw from search engines, DNS records, certificate transparency logs, and other sources, which makes it more complex very quickly. Those are all things you can build in later, but first we’ll start with the basics.

Concepts Covered

  • Domain names and subdomains
  • Documentation-only IP addresses
  • Common DNS record types
  • Safe fixture-backed HTTP requests
  • Handling missing hosts

Why This Matters

Subdomain discovery is a reconnaissance technique, and reconnaissance can touch real systems quickly. Fixture-backed practice lets you learn the programming shape before running anything against an authorised target.

Objective

Build fixture-backed URLs, request them safely, and recognise when a candidate host responds.

DNS and Subdomains

Before we talk about enumerating subdomains, we need to have a chat about DNS. Long story short, DNS is the phone book of the internet: clients can look up names instead of memorising IP addresses.

A subdomain is a name below another domain. In api.example.com, api is a label under example.com. In real reconnaissance, subdomains can reveal forgotten applications, staging systems, alternate front doors, or services that weren’t supposed to see the light of day.

Common records you’ll see in reconnaissance notes include:

DNS Record Usage
A Maps a hostname to one or more IPv4 addresses
AAAA Maps a hostname to one or more IPv6 addresses
CNAME Aliases one hostname to another hostname
MX Identifies mail exchangers for a domain
NS Identifies authoritative name servers for a zone

Fixture Addresses

This course uses documentation and fixture addresses instead of live targets. 203.0.113.42, for example, comes from a documentation-only range reserved by RFC 5737.

In the original workshop we had a training web server set up specifically for practice. In this edition, the same idea lives inside the repository so the book can render safely and you can experiment without making surprise internet traffic.

from fixtures.local_targets import fake_public_ip

external_ip = fake_public_ip()
print(f"Documentation address: {external_ip}")
Documentation address: 203.0.113.42

Building a Candidate URL

The bruteforcer combines three pieces: a URL scheme, a candidate subdomain label, and the authorised parent domain.

You’ve got a few ways to build that string: Python supports concatenation with +, the older format() method, and f-strings. In Python 3.6 and higher, f-strings are usually the clearest option, and yes, they’re my personal favourite.

scheme = "https"
label = "www"
domain = "example.test"

url = f"{scheme}://{label}.{domain}"
print(url)
1
Store the URL scheme separately so the tool can later choose http or https.
2
Take one candidate label from the wordlist and keep it separate from the domain.
3
Keep the authorised fixture domain separate from the candidate label.
4
Use an f-string to build the URL in one place.
https://www.example.test

Here’s what’s happening: without the scheme, a web request doesn’t know whether it should use http or https. Keeping the scheme explicit avoids the classic “almost a URL” problem.

Safe HTTP Requests

The real function in a live script is requests.get(). In this notebook, fake_requests_get() accepts the same kind of URL and returns a small response object without leaving your machine.

Requests is a third-party Python library with a much friendlier interface than the lower-level tools in the standard library. For this course we keep the interface shape but replace the network with a fixture, because learning the control flow shouldn’t require poking a real domain.

from fixtures.local_targets import fake_requests_get

response = fake_requests_get("https://www.example.test", timeout=2)
print(response.status_code)
print(response.text)
200
www.example.test is alive

Missing Hosts

A bruteforcer should expect many candidates not to answer. Handle that as normal control flow, not as a surprising crash.

If you recall Python’s motto, this is “ask for forgiveness, not permission” in action. We try the request, and if the candidate doesn’t exist in the fixture, we catch the connection error and move on.

import requests

try:
    fake_requests_get("https://missing.example.test", timeout=2)
except requests.exceptions.ConnectionError:
    print("No response from fixture")
No response from fixture

Checkpoint

Write a check_subdomain(label, domain) function. It should build a fixture URL, call fake_requests_get(), and return True only when the fixture returns status code 200.

A quick test should produce:

True
False

Run it with one label you expect to exist and one you expect to miss. If everything went the way we expected, your function should be calm in both cases.

from fixtures.local_targets import fake_requests_get
import requests

def check_subdomain(label: str, domain: str) -> bool:
    # Build the URL, call fake_requests_get(), and handle ConnectionError.
    pass

Extension Activity

Return the full hostname for successful checks instead of only True. For example, www.example.test is more useful in final output than True.

Once that works, think about where you would add a timeout option in the real command-line parser. Slow or missing hosts are normal in this kind of tool, and waiting forever isn’t a strategy.

Answer

References