Skip to content

Commit cb182de

Browse files
Merge pull request #1072 from blacklanternsecurity/newsletters
Newsletters Module
2 parents 159ef6e + 0e34a80 commit cb182de

File tree

12 files changed

+509
-398
lines changed

12 files changed

+509
-398
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
__pycache__/
2+
.coverage*

README.md

+22-22
Large diffs are not rendered by default.

bbot/core/event/base.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
split_host_port,
3030
tagify,
3131
validators,
32+
truncate_string,
3233
)
3334

3435

@@ -489,7 +490,7 @@ def data_human(self):
489490
return self._data_human()
490491

491492
def _data_human(self):
492-
return str(self.data)
493+
return truncate_string(str(self.data), n=2000)
493494

494495
def _data_load(self, data):
495496
"""

bbot/modules/newsletters.py

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Created a new module called 'newsletters' that will scrape the websites (or recursive websites,
2+
# thanks to BBOT's sub-domain enumeration) looking for the presence of an 'email type' that also
3+
# contains a 'placeholder'. The combination of these two HTML items usually signify the presence
4+
# of an "Enter Your Email Here" type Newsletter Subscription service. This module could be used
5+
# to find newsletters for a future email bombing attack and/or find user-input fields that could
6+
# be be susceptible to overflows or injections.
7+
8+
from .base import BaseModule
9+
import re
10+
from bs4 import BeautifulSoup
11+
12+
# Known Websites with Newsletters
13+
# https://futureparty.com/
14+
# https://www.marketingbrew.com/
15+
# https://buffer.com/
16+
# https://www.milkkarten.net/
17+
# https://geekout.mattnavarra.com/
18+
19+
deps_pip = ["beautifulsoup4"]
20+
21+
22+
class newsletters(BaseModule):
23+
watched_events = ["HTTP_RESPONSE"]
24+
produced_events = ["FINDING"]
25+
flags = ["active", "safe"]
26+
meta = {"description": "Searches for Newsletter Submission Entry Fields on Websites"}
27+
28+
# Parse through Website to find a Text Entry Box of 'type = email'
29+
# and ensure that there is placeholder text within it.
30+
def find_type(self, soup):
31+
email_type = soup.find(type="email")
32+
if email_type:
33+
regex = re.compile(r"placeholder")
34+
if regex.search(str(email_type)):
35+
return True
36+
return False
37+
38+
async def handle_event(self, event):
39+
if event.data["status_code"] == 200:
40+
soup = BeautifulSoup(event.data["body"], "html.parser")
41+
result = self.find_type(soup)
42+
if result:
43+
description = f"Found a Newsletter Submission Form that could be used for email bombing attacks"
44+
data = {"host": str(event.host), "description": description, "url": event.data["url"]}
45+
46+
await self.emit_event(data, "FINDING", event)

bbot/test/test_step_2/module_tests/base.py

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class ModuleTestBase:
4747
module_name = None
4848
config_overrides = {}
4949
modules_overrides = []
50+
log = logging.getLogger("bbot")
5051

5152
class ModuleTest:
5253
def __init__(self, module_test_base, httpx_mock, httpserver, httpserver_ssl, monkeypatch, request):

bbot/test/test_step_2/module_tests/test_module_excavate.py

-1
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,6 @@ async def setup_before_prep(self, module_test):
313313
)
314314

315315
def check(self, module_test, events):
316-
317316
for serialize_type in ["Java", ".NET", "PHP (Array)", "PHP (String)", "PHP (Object)", "Possible Compressed"]:
318317
assert any(
319318
e.type == "FINDING" and serialize_type in e.data["description"] for e in events
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from .base import ModuleTestBase
2+
3+
# import logging
4+
5+
6+
class TestNewsletters(ModuleTestBase):
7+
found_tgt = "http://127.0.0.1:8888/found"
8+
missing_tgt = "http://127.0.0.1:8888/missing"
9+
targets = [found_tgt, missing_tgt]
10+
modules_overrides = ["speculate", "httpx", "newsletters"]
11+
12+
html_with_newsletter = """
13+
<input aria-required="true"
14+
class="form-input form-input-text required"
15+
data-at="form-email"
16+
data-describedby="form-validation-error-box-element-5"
17+
data-label-inside="Enter your email"
18+
id="field-5f329905b4bfe1027b44513f94b50363-0"
19+
name="Enter your email"
20+
placeholder="Enter your email"
21+
required=""
22+
title="Enter your email"
23+
type="email" value=""/>
24+
"""
25+
26+
html_without_newsletter = """
27+
<div>
28+
<h1>Example Domain</h1>
29+
<p>This domain is for use in illustrative examples in documents. You may use this
30+
domain in literature without prior coordination or asking for permission.</p>
31+
<p><a href="https://www.iana.org/domains/example">More information...</a></p>
32+
</div>
33+
"""
34+
35+
async def setup_after_prep(self, module_test):
36+
request_args = dict(uri="/found", headers={"test": "header"})
37+
respond_args = dict(response_data=self.html_with_newsletter)
38+
module_test.set_expect_requests(request_args, respond_args)
39+
request_args = dict(uri="/missing", headers={"test": "header"})
40+
respond_args = dict(response_data=self.html_without_newsletter)
41+
module_test.set_expect_requests(request_args, respond_args)
42+
43+
def check(self, module_test, events):
44+
found = False
45+
missing = True
46+
for event in events:
47+
# self.log.info(f"event type: {event.type}")
48+
if event.type == "FINDING":
49+
# self.log.info(f"event data: {event.data}")
50+
# Verify Positive Result
51+
if event.data["url"] == self.found_tgt:
52+
found = True
53+
# Verify Negative Result (should skip this statement if correct)
54+
elif event.data["url"] == self.missing_tgt:
55+
missing = False
56+
assert found, f"NEWSLETTER 'Found' Error - Expect status of True but got False"
57+
assert missing, f"NEWSLETTER 'Missing' Error - Expect status of True but got False"

0 commit comments

Comments
 (0)