Repository navigation
Convert dnsdumpster to the authenticated API - #3458
Conversation
There was a problem hiding this comment.
Catching this from a module-attribution drop of ~57 names to 0, then ruling out scraping detection with a cookie jar and full browser headers, and confirming the endpoint is alive because POST /htmld/ returns the same error for an empty token and no token, is the right way to work a silent failure.
🔴 Not validated against the live API
Your own blocker.
|
|
||
| base_url = "https://dnsdumpster.com" | ||
| class Config(BaseModuleConfig): | ||
| api_key: str | list[str] = Field("", description="DNSDumpster API key", sensitive=True, mandatory=True) |
There was a problem hiding this comment.
A mandatory api_key means subdomain_enum_apikey.setup ends in require_api_key() and the module disables itself for anyone without a key, while still carrying the subdomain-enum flag. dnsdumpster runs for everybody today. You note the free tier is not realistically useful, so this may be correct, but it is a product decision and it is not argued anywhere in the PR.
| base_url = "https://dnsdumpster.com" | ||
| class Config(BaseModuleConfig): | ||
| api_key: str | list[str] = Field("", description="DNSDumpster API key", sensitive=True, mandatory=True) | ||
| max_pages: int = Field(10, description="Maximum result pages to request per domain") |
There was a problem hiding this comment.
max_pages is arbitrary and it is the exposed option. page_size below is the value that actually varies per account (50 free, 200 paid) and it is hardcoded. That is backwards.
Also, the 10 default is duplicated in setup. anubisdb does the same thing so this one is precedent, not your invention.
| break | ||
| results.update(await self.parse_results(r, query)) | ||
| # a short page is the last one | ||
| if self.record_count(data) < self.page_size: |
There was a problem hiding this comment.
record_count sums a/cname/mx/ns and compares the total against a per-page host cap you have not observed. If the cap is per record type, this truncates silently.
A free key also only terminates here by accident, because 50 happens to be less than the hardcoded 200.
# Conflicts: # docs/scanning/configuration.md
|
Addressed the clear-cut items in f7fda55:
Further action on this PR is blocked until we obtain a paid API key. The pagination rework, the mandatory-key decision, and validation against the live API all depend on observing real responses. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## dev #3458 +/- ##
=====================================
+ Coverage 91% 91% +1%
=====================================
Files 461 461
Lines 49520 49565 +45
=====================================
+ Hits 44655 44696 +41
- Misses 4865 4869 +4 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
📊 Performance Benchmark Report
📈 Detailed Results (All Benchmarks)
🎯 Performance Summary+ 2 improvements 🚀
29 unchanged ✅🔍 Significant Changes (>10%)
🐍 Python Version 3.11.16 |
|
Correction to my earlier comment: validated against the live API with a free key, so this is no longer blocked. The response includes |
|
On the mandatory key: the unauthenticated scraping path is dead -- dnsdumpster removed their public HTML interface. There's nothing to fall back to without a key. This is also established precedent: shodan_dns, c99, bufferoverrun, fullhunt, bevigil, subdomainradar, and trickest are all On the template bypass: overriding |
Fixes #3441
The dnsdumpster module has been silently returning nothing. It scrapes a JWT out of the homepage's
hx-headersattribute, and that attribute is now served empty:An empty string is falsy, so the module falls through to
verbose("Error obtaining JWT token")and setserrorState. At verbose level this is invisible in a default scan; it only turned up when a module-attribution comparison showed the module going from ~57 discovered names to 0.This is not transient and not scraping detection. Repeated fetches, a cookie jar, a plain user agent and a full browser header set (including
Sec-Fetch-*andsec-ch-ua) all return an empty token, no cookies are set, and no site JavaScript populates it. The endpoint itself is alive:POST api.dnsdumpster.com/htmld/returns HTTP 200 withError: Invalid or missing token, reload page and try again, identically for an empty token or no header at all.dnsdumpster has moved to a membership model, and there is now a documented API:
a/cname/mx/nsarrays of{host, ips[...]}, atxtarray of strings, andtotal_a_recshostvalues include the priority ("0 mail.example.com"), so hostnames are extracted from the whole response rather than read fromhost?page=2returns401 {"error":"pagination requires plus or max membership"}Changes
subdomain_enum_apikeywith asensitive,mandatoryapi_key. There is no keyless path any more: the public page's token is always empty.X-API-Keyheadertotal_a_recs, up to a configurablemax_pages(default 10), stopping early on a 401/403, an empty page, a missing total, or a parse failureapi_request's 429 handlingping_url, deliberately: a health check would spend one of only 50 daily requests on a free keyValidation
Validated against the live API with a free key: authentication, the response shape above, the 401 on page 2, and a module run that emitted every in-scope A record in one request. Multi-page paging needs a paid key and is covered by mocked tests only.
Note
The free tier is 50 records per domain per request with no pagination, and 50 requests per day. That is thin enough for subdomain enumeration that this module is realistically only useful with a paid key.