From 12451d0915b89a85c4c96ac597745afcd284c832 Mon Sep 17 00:00:00 2001 From: liquidsec Date: Tue, 6 Feb 2024 15:53:13 -0500 Subject: [PATCH 1/8] better bypass403 error handling --- bbot/modules/bypass403.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/bbot/modules/bypass403.py b/bbot/modules/bypass403.py index 6799a5bb1..3fd40a1cf 100644 --- a/bbot/modules/bypass403.py +++ b/bbot/modules/bypass403.py @@ -63,6 +63,7 @@ "X-Host": "127.0.0.1", } +# This is planned to be replaced in the future: https://github.com/blacklanternsecurity/bbot/issues/1068 waf_strings = ["The requested URL was rejected"] for qp in query_payloads: @@ -83,8 +84,13 @@ class bypass403(BaseModule): async def do_checks(self, compare_helper, event, collapse_threshold): results = set() + error_count = 0 for sig in signatures: + if error_count > 3: + self.warning(f"Received too many errors for URL {event.data} aborting bypass403") + return None + sig = self.format_signature(sig, event) if sig[2] != None: headers = dict(sig[2]) @@ -95,6 +101,7 @@ async def do_checks(self, compare_helper, event, collapse_threshold): sig[1], headers=headers, method=sig[0], allow_redirects=True ) except HttpCompareError as e: + error_count += 1 self.debug(e) continue @@ -106,7 +113,8 @@ async def do_checks(self, compare_helper, event, collapse_threshold): return if match == False: - if str(subject_response.status_code)[0] != "4": + self.critical(subject_response.status_code) + if str(subject_response.status_code)[0] != "4" and subject_response.status_code != 503: if sig[2]: added_header_tuple = next(iter(sig[2].items())) reported_signature = f"Added Header: {added_header_tuple[0]}: {added_header_tuple[1]}" @@ -149,6 +157,7 @@ async def handle_event(self, event): source=event, ) + # When a WAF-check helper is available in the future, we will convert to HTTP_RESPONSE and check for the WAF string here. async def filter_event(self, event): if ("status-403" in event.tags) or ("status-401" in event.tags): return True From 1da63d2bfb68a6ef0c48eb3bf13c615bbe783d48 Mon Sep 17 00:00:00 2001 From: liquidsec Date: Tue, 6 Feb 2024 15:55:29 -0500 Subject: [PATCH 2/8] remove testing code --- bbot/modules/bypass403.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bbot/modules/bypass403.py b/bbot/modules/bypass403.py index 3fd40a1cf..c58463401 100644 --- a/bbot/modules/bypass403.py +++ b/bbot/modules/bypass403.py @@ -113,8 +113,7 @@ async def do_checks(self, compare_helper, event, collapse_threshold): return if match == False: - self.critical(subject_response.status_code) - if str(subject_response.status_code)[0] != "4" and subject_response.status_code != 503: + if str(subject_response.status_code)[0] != "4": if sig[2]: added_header_tuple = next(iter(sig[2].items())) reported_signature = f"Added Header: {added_header_tuple[0]}: {added_header_tuple[1]}" From 3ab53d058d0055f0c59b1fe31c06189bd636d0ae Mon Sep 17 00:00:00 2001 From: TheTechromancer Date: Tue, 6 Feb 2024 16:15:14 -0500 Subject: [PATCH 3/8] use better sqlite text factory --- bbot/modules/gowitness.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bbot/modules/gowitness.py b/bbot/modules/gowitness.py index a335bf021..c49b4d3ae 100644 --- a/bbot/modules/gowitness.py +++ b/bbot/modules/gowitness.py @@ -200,6 +200,7 @@ def new_screenshots(self): if self.db_path.is_file(): with sqlite3.connect(str(self.db_path)) as con: con.row_factory = sqlite3.Row + con.text_factory = self.helpers.smart_decode cur = con.cursor() res = self.cur_execute(cur, "SELECT * FROM urls") for row in res: From 0ec10c74c0f4272469c8b2239d5d4b049ff78bb6 Mon Sep 17 00:00:00 2001 From: TheTechromancer Date: Wed, 7 Feb 2024 17:11:41 -0500 Subject: [PATCH 4/8] make sure gowitness has a working chrome install --- bbot/modules/gowitness.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/bbot/modules/gowitness.py b/bbot/modules/gowitness.py index a335bf021..614a9051d 100644 --- a/bbot/modules/gowitness.py +++ b/bbot/modules/gowitness.py @@ -94,6 +94,20 @@ async def setup(self): custom_chrome_path = self.helpers.tools_dir / "chrome-linux" / "chrome" if custom_chrome_path.is_file(): self.chrome_path = custom_chrome_path + + # make sure we have a working chrome install + chrome_test_pass = False + for binary in ("chrome", "chromium", custom_chrome_path): + binary_path = self.helpers.which(binary) + if binary_path and Path(binary_path).is_file(): + chrome_test_proc = await self.helpers.run([binary, "--version"]) + if getattr(chrome_test_proc, "returncode", 1) == 0: + self.verbose(f"Found chrome executable at {binary_path}") + chrome_test_pass = True + break + if not chrome_test_pass: + return False, "Failed to set up Google chrome. Please install manually use try again with --force-deps." + self.db_path = self.base_path / "gowitness.sqlite3" self.screenshot_path = self.base_path / "screenshots" self.command = self.construct_command() From d9efdc60370a581895a1beefc4de55a4a4e02692 Mon Sep 17 00:00:00 2001 From: TheTechromancer Date: Wed, 7 Feb 2024 17:16:26 -0500 Subject: [PATCH 5/8] fix typo --- bbot/modules/gowitness.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bbot/modules/gowitness.py b/bbot/modules/gowitness.py index 614a9051d..4acaf30f4 100644 --- a/bbot/modules/gowitness.py +++ b/bbot/modules/gowitness.py @@ -106,7 +106,7 @@ async def setup(self): chrome_test_pass = True break if not chrome_test_pass: - return False, "Failed to set up Google chrome. Please install manually use try again with --force-deps." + return False, "Failed to set up Google chrome. Please install manually or try again with --force-deps." self.db_path = self.base_path / "gowitness.sqlite3" self.screenshot_path = self.base_path / "screenshots" From d37c0a3e2b8fc364b265d92d17ca7b90169527f8 Mon Sep 17 00:00:00 2001 From: TheTechromancer Date: Wed, 7 Feb 2024 17:19:35 -0500 Subject: [PATCH 6/8] use full binary path --- bbot/modules/gowitness.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bbot/modules/gowitness.py b/bbot/modules/gowitness.py index 4acaf30f4..0734858f1 100644 --- a/bbot/modules/gowitness.py +++ b/bbot/modules/gowitness.py @@ -100,7 +100,7 @@ async def setup(self): for binary in ("chrome", "chromium", custom_chrome_path): binary_path = self.helpers.which(binary) if binary_path and Path(binary_path).is_file(): - chrome_test_proc = await self.helpers.run([binary, "--version"]) + chrome_test_proc = await self.helpers.run([binary_path, "--version"]) if getattr(chrome_test_proc, "returncode", 1) == 0: self.verbose(f"Found chrome executable at {binary_path}") chrome_test_pass = True From f498a97484c54a7e87d28ea85fdd6675fe5cbec5 Mon Sep 17 00:00:00 2001 From: liquidsec Date: Thu, 8 Feb 2024 09:57:58 -0500 Subject: [PATCH 7/8] removing weird characters from docs --- docs/modules/nuclei.md | 22 +++++++++++----------- docs/scanning/events.md | 8 ++++---- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/modules/nuclei.md b/docs/modules/nuclei.md index 3f492b8ba..d94e6f7f0 100644 --- a/docs/modules/nuclei.md +++ b/docs/modules/nuclei.md @@ -8,7 +8,7 @@ BBOT integrates with [Nuclei](https://github.com/projectdiscovery/nuclei), an op * The BBOT Nuclei module ingests **[URL]** events and emits events of type **[VULNERABILITY]** or **[FINDING]** -* Vulnerabilities will inherit their severity from the Nuclei templates​ +* Vulnerabilities will inherit their severity from the Nuclei templates * Nuclei templates of severity INFO will be emitted as **[FINDINGS]** ## Default Behavior @@ -59,15 +59,15 @@ This is equivalent to the Nuclei '-as' scan option. It only use templates that m #### Budget -Budget mode is unique to BBOT. ​ +Budget mode is unique to BBOT. -For larger scans with thousands of targets, doing a FULL Nuclei scan (1000s of Requests) for each is not realistic. ​ -As an alternative to the other modes, you can take advantage of Nuclei's "collapsible" template feature. ​ +For larger scans with thousands of targets, doing a FULL Nuclei scan (1000s of Requests) for each is not realistic. +As an alternative to the other modes, you can take advantage of Nuclei's "collapsible" template feature. For only the cost of one (or more) "extra" request(s) per host, it can activate several hundred modules. These are modules which happen to look at a BaseUrl, and typically look for a specific string or other attribute. Nuclei is smart about reusing the request data when it can, and we can use this to our advantage. -The budget parameter is the # of extra requests per host you are willing to send to "feed" Nuclei templates​ (defaults to 1). -For those times when vulnerability scanning isn't the main focus, but you want to look for easy wins.​ +The budget parameter is the # of extra requests per host you are willing to send to "feed" Nuclei templates (defaults to 1). +For those times when vulnerability scanning isn't the main focus, but you want to look for easy wins. Of course, there is a rapidly diminishing return when you set he value to more than a handful. Eventually, this becomes 1 template per 1 budget value increase. However, in the 1-10 range there is a lot of value. This graphic should give you a rough visual idea of this concept. @@ -86,20 +86,20 @@ The **ratelimit** and **concurrency** settings default to the same defaults that ```bash # Scan a SINGLE target with a basic port scan and web modules -bbot -f web-basic -m nmap nuclei --allow-deadly -t app.evilcorp.com​ +bbot -f web-basic -m nmap nuclei --allow-deadly -t app.evilcorp.com ``` ```bash # Scanning MULTIPLE targets -bbot -f web-basic -m nmap nuclei --allow-deadly -t app1.evilcorp.com app2.evilcorp.com app3.evilcorp.com​ +bbot -f web-basic -m nmap nuclei --allow-deadly -t app1.evilcorp.com app2.evilcorp.com app3.evilcorp.com ``` ```bash # Scanning MULTIPLE targets while performing subdomain enumeration -bbot -f subdomain-enum web-basic -m nmap nuclei –allow-deadly -t app1.evilcorp.com app2.evilcorp.com app3.evilcorp.com​ +bbot -f subdomain-enum web-basic -m nmap nuclei --allow-deadly -t app1.evilcorp.com app2.evilcorp.com app3.evilcorp.com ``` ```bash -# Scanning MULTIPLE targets on a BUDGET​ -bbot -f subdomain-enum web-basic -m nmap nuclei –allow-deadly –c modules.nuclei.mode=Budget -t app1.evilcorp.com app2.evilcorp.com app3.evilcorp.com​ +# Scanning MULTIPLE targets on a BUDGET +bbot -f subdomain-enum web-basic -m nmap nuclei --allow-deadly -c modules.nuclei.mode=budget -t app1.evilcorp.com app2.evilcorp.com app3.evilcorp.com ``` diff --git a/docs/scanning/events.md b/docs/scanning/events.md index 362a0b958..8f02125e7 100644 --- a/docs/scanning/events.md +++ b/docs/scanning/events.md @@ -87,12 +87,12 @@ BBOT has a sharp distinction between Findings and Vulnerabilities: **VULNERABILITY** -* There's a higher standard for what is allowed to be a vulnerability. They should be considered **confirmed** and **actionable​** - no additional confirmation required -* They are always assigned a severity. The possible severities are: LOW, MEDIUM, HIGH, or CRITICAL​ +* There's a higher standard for what is allowed to be a vulnerability. They should be considered **confirmed** and **actionable** - no additional confirmation required +* They are always assigned a severity. The possible severities are: LOW, MEDIUM, HIGH, or CRITICAL -**FINDING​** +**FINDING** -* Findings can range anywhere from "slightly interesting behavior" to "likely, but unconfirmed vulnerability"​ +* Findings can range anywhere from "slightly interesting behavior" to "likely, but unconfirmed vulnerability" * Are often false positives By making this separation, actionable vulnerabilities can be identified quickly in the midst of a large scan From 1a0ebf29da4eae88cbf44d8dc1201de37f3da677 Mon Sep 17 00:00:00 2001 From: liquidsec Date: Thu, 8 Feb 2024 09:58:15 -0500 Subject: [PATCH 8/8] making nuclei mode case insensitive --- bbot/modules/deadly/nuclei.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bbot/modules/deadly/nuclei.py b/bbot/modules/deadly/nuclei.py index f3ef4ad67..be24599ac 100644 --- a/bbot/modules/deadly/nuclei.py +++ b/bbot/modules/deadly/nuclei.py @@ -70,7 +70,7 @@ async def setup(self): else: self.warning("Error running nuclei template update command") self.proxy = self.scan.config.get("http_proxy", "") - self.mode = self.config.get("mode", "severe") + self.mode = self.config.get("mode", "severe").lower() self.ratelimit = int(self.config.get("ratelimit", 150)) self.concurrency = int(self.config.get("concurrency", 25)) self.budget = int(self.config.get("budget", 1))