Skip to content

Commit a0094bd

Browse files
Merge pull request #2717 from blacklanternsecurity/dev
Dev -> Stable 2.7.2
2 parents 6821912 + 162675b commit a0094bd

28 files changed

+1931
-2109
lines changed

.github/workflows/codeql.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ jobs:
6565

6666
# Initializes the CodeQL tools for scanning.
6767
- name: Initialize CodeQL
68-
uses: github/codeql-action/init@v3
68+
uses: github/codeql-action/init@v4
6969
with:
7070
languages: ${{ matrix.language }}
7171
build-mode: ${{ matrix.build-mode }}
@@ -93,6 +93,6 @@ jobs:
9393
exit 1
9494
9595
- name: Perform CodeQL Analysis
96-
uses: github/codeql-action/analyze@v3
96+
uses: github/codeql-action/analyze@v4
9797
with:
9898
category: "/language:${{matrix.language}}"

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ jobs:
9696
tags: "stable,${{ steps.version.outputs.BBOT_VERSION }}"
9797
- name: Docker Hub Description
9898
if: github.event_name == 'push' && github.ref == 'refs/heads/dev'
99-
uses: peter-evans/dockerhub-description@v4
99+
uses: peter-evans/dockerhub-description@v5
100100
with:
101101
username: ${{ secrets.DOCKER_USERNAME }}
102102
password: ${{ secrets.DOCKER_PASSWORD }}

bbot/modules/censys.py

Lines changed: 0 additions & 98 deletions
This file was deleted.

bbot/modules/dnsdumpster.py

Lines changed: 35 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import re
1+
import json
22

33
from bbot.modules.templates.subdomain_enum import subdomain_enum
44

@@ -15,78 +15,61 @@ class dnsdumpster(subdomain_enum):
1515

1616
base_url = "https://dnsdumpster.com"
1717

18+
async def setup(self):
19+
self.apikey_regex = self.helpers.re.compile(r'<form[^>]*data-form-id="mainform"[^>]*hx-headers=\'([^\']*)\'')
20+
return True
21+
1822
async def query(self, domain):
1923
ret = []
20-
# first, get the CSRF tokens
24+
# first, get the JWT token from the main page
2125
res1 = await self.api_request(self.base_url)
2226
status_code = getattr(res1, "status_code", 0)
23-
if status_code in [429]:
24-
self.verbose(f'Too many requests "{status_code}"')
25-
return ret
26-
elif status_code not in [200]:
27+
if status_code not in [200]:
2728
self.verbose(f'Bad response code "{status_code}" from DNSDumpster')
2829
return ret
29-
else:
30-
self.debug(f'Valid response code "{status_code}" from DNSDumpster')
31-
32-
html = self.helpers.beautifulsoup(res1.content, "html.parser")
33-
if html is False:
34-
self.verbose("BeautifulSoup returned False")
35-
return ret
3630

37-
csrftoken = None
38-
csrfmiddlewaretoken = None
31+
# Extract JWT token from the form's hx-headers attribute using regex
32+
jwt_token = None
3933
try:
40-
for cookie in res1.headers.get("set-cookie", "").split(";"):
41-
try:
42-
k, v = cookie.split("=", 1)
43-
except ValueError:
44-
self.verbose("Error retrieving cookie")
45-
return ret
46-
if k == "csrftoken":
47-
csrftoken = str(v)
48-
csrfmiddlewaretoken = html.find("input", {"name": "csrfmiddlewaretoken"}).attrs.get("value", None)
49-
except AttributeError:
50-
pass
34+
# Look for the form with data-form-id="mainform" and extract hx-headers
35+
form_match = await self.helpers.re.search(self.apikey_regex, res1.text)
36+
if form_match:
37+
headers_json = form_match.group(1)
38+
headers_data = json.loads(headers_json)
39+
jwt_token = headers_data.get("Authorization")
40+
except (AttributeError, json.JSONDecodeError, KeyError):
41+
self.log.warning("Error obtaining JWT token")
42+
return ret
5143

52-
# Abort if we didn't get the tokens
53-
if not csrftoken or not csrfmiddlewaretoken:
54-
self.verbose("Error obtaining CSRF tokens")
44+
# Abort if we didn't get the JWT token
45+
if not jwt_token:
46+
self.verbose("Error obtaining JWT token")
5547
self.errorState = True
5648
return ret
5749
else:
58-
self.debug("Successfully obtained CSRF tokens")
50+
self.debug("Successfully obtained JWT token")
5951

6052
if self.scan.stopping:
61-
return
53+
return ret
6254

63-
# Otherwise, do the needful
64-
subdomains = set()
55+
# Query the API with the JWT token
6556
res2 = await self.api_request(
66-
f"{self.base_url}/",
57+
"https://api.dnsdumpster.com/htmld/",
6758
method="POST",
68-
cookies={"csrftoken": csrftoken},
69-
data={
70-
"csrfmiddlewaretoken": csrfmiddlewaretoken,
71-
"targetip": str(domain).lower(),
72-
"user": "free",
73-
},
59+
data={"target": str(domain).lower()},
7460
headers={
75-
"origin": "https://dnsdumpster.com",
76-
"referer": "https://dnsdumpster.com/",
61+
"Authorization": jwt_token,
62+
"Content-Type": "application/x-www-form-urlencoded",
63+
"Origin": "https://dnsdumpster.com",
64+
"Referer": "https://dnsdumpster.com/",
65+
"HX-Request": "true",
66+
"HX-Target": "results",
67+
"HX-Current-URL": "https://dnsdumpster.com/",
7768
},
7869
)
7970
status_code = getattr(res2, "status_code", 0)
8071
if status_code not in [200]:
81-
self.verbose(f'Bad response code "{status_code}" from DNSDumpster')
82-
return ret
83-
html = self.helpers.beautifulsoup(res2.content, "html.parser")
84-
if html is False:
85-
self.verbose("BeautifulSoup returned False")
72+
self.verbose(f'Bad response code "{status_code}" from DNSDumpster API')
8673
return ret
87-
escaped_domain = re.escape(domain)
88-
match_pattern = re.compile(r"^[\w\.-]+\." + escaped_domain + r"$")
89-
for subdomain in html.findAll(text=match_pattern):
90-
subdomains.add(str(subdomain).strip().lower())
9174

92-
return list(subdomains)
75+
return await self.scan.extract_in_scope_hostnames(res2.text)

bbot/modules/emailformat.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,29 @@ class emailformat(BaseModule):
1515

1616
base_url = "https://www.email-format.com"
1717

18+
async def setup(self):
19+
self.cfemail_regex = self.helpers.re.compile(r'data-cfemail="([0-9a-z]+)"')
20+
return True
21+
1822
async def handle_event(self, event):
1923
_, query = self.helpers.split_domain(event.data)
2024
url = f"{self.base_url}/d/{self.helpers.quote(query)}/"
2125
r = await self.api_request(url)
2226
if not r:
2327
return
24-
for email in await self.helpers.re.extract_emails(r.text):
28+
29+
encrypted_emails = await self.helpers.re.findall(self.cfemail_regex, r.text)
30+
31+
for enc in encrypted_emails:
32+
enc_len = len(enc)
33+
34+
if enc_len < 2 or enc_len % 2 != 0:
35+
continue
36+
37+
key = int(enc[:2], 16)
38+
39+
email = "".join([chr(int(enc[i : i + 2], 16) ^ key) for i in range(2, enc_len, 2)]).lower()
40+
2541
if email.endswith(query):
2642
await self.emit_event(
2743
email,

0 commit comments

Comments
 (0)