Skip to content

DNS brute-force canary check is non-functional due to three bugs in brute.py #3004

@aconite33

Description

@aconite33

Summary

The canary-based false-positive detection in bbot/core/helpers/dns/brute.py is completely broken. When brute-forcing a wildcard domain, massdns returns results for every queried subdomain (including canary subdomains). The canary safety net is supposed to detect this and abort, but three bugs prevent it from ever working.

This causes wildcard domains to flood scans with thousands of false-positive DNS_NAME events and wildcard detection log entries.

Bug 1 -- Exhausted generator (brute.py:60-71)

canaries = self.gen_random_subdomains(self.num_canaries)  # generator
canaries_list = list(canaries)                            # exhausts the generator
# ...
if sub in canaries:   # always False -- generator is empty

After list(canaries) on line 61, the generator is fully consumed. The sub in canaries membership test on line 71 iterates an empty generator and always returns False. No canary is ever detected, so canaries_triggered is always empty and the abort at line 76 never fires.

Bug 2 -- Trailing dot in subdomain comparison (brute.py:70)

sub = hostname.split(domain)[0]
# hostname = "testcanary.example.com"
# domain   = "example.com"
# sub      = "testcanary."   <-- trailing dot

Even if Bug 1 were fixed, hostname.split(domain)[0] produces a string with a trailing dot (e.g. "testcanary.") that will never match the canary string "testcanary".

Bug 3 -- Variable shadowing (brute.py:52)

wildcard_domains = await self.parent_helper.dns.is_wildcard_domain(domain, (type, "CNAME"))
wildcard_rdtypes = set()
for domain, rdtypes in wildcard_domains.items():  # shadows the `domain` parameter
    wildcard_rdtypes.update(rdtypes)
if wildcard_domains:
    self.log.hugewarning(
        f"Aborting massdns on {domain} ..."  # uses wrong value
    )

The loop variable domain overwrites the function parameter. If wildcard_domains has multiple entries, the abort warning references the wrong domain name.

Impact

When scanning a domain that has wildcard DNS (e.g. *.example.com with a CNAME to a CDN), the following happens:

  1. is_wildcard_domain() may detect the wildcard and modules with reject_wildcards = "strict" skip it correctly
  2. However, if the wildcard check passes (e.g. due to resolver differences between bbot and massdns), massdns brute-forces thousands of subdomains that all resolve
  3. The canary safety net that should catch this scenario is completely non-functional
  4. All results (including canary subdomains) are emitted as DNS_NAME events
  5. Each event triggers individual wildcard zone detection, flooding logs with thousands of entries

Steps to Reproduce

# Demonstrate Bug 1: exhausted generator
def gen():
    yield "a"
    yield "b"

g = gen()
items = list(g)    # exhausts g
print("a" in g)    # False -- generator is empty

# Demonstrate Bug 2: trailing dot
hostname = "canary.example.com"
domain = "example.com"
sub = hostname.split(domain)[0]
print(repr(sub))   # 'canary.' -- has trailing dot

Suggested Fix

  • Bug 1: Use canaries_list or convert to a set for O(1) lookups instead of the exhausted generator
  • Bug 2: Strip the trailing dot from sub, or use a different extraction method
  • Bug 3: Use a different loop variable name (e.g. _domain or wildcard_domain)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions