Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
53ea0b3
virtualhost overhaul initial
liquidsec Aug 27, 2025
3bf032a
tweak
liquidsec Aug 27, 2025
38d4d2e
virtualhost update major refactor
liquidsec Aug 30, 2025
454d7d1
virtual host another major refactor
liquidsec Sep 2, 2025
f04c486
changes to as_completed
liquidsec Sep 2, 2025
1d16b49
pinned curl
liquidsec Sep 2, 2025
7c32cb1
fix missing arg
liquidsec Sep 2, 2025
3447866
finish rename
liquidsec Sep 2, 2025
0d98531
small refactor
liquidsec Sep 2, 2025
eb93201
fix async handling
liquidsec Sep 3, 2025
84c0f8e
presets and other adjustments
liquidsec Sep 3, 2025
ef1674f
change as_completed to only accept coros
liquidsec Sep 3, 2025
bd51281
starting to back out of debug, more tweaks
liquidsec Sep 4, 2025
d160941
special virtual host only ignore strings
liquidsec Sep 4, 2025
d19f324
add module test
liquidsec Sep 4, 2025
a5847c5
Merge branch 'dev' into virtualhost-upgrade
liquidsec Sep 4, 2025
ba9fcae
typo
liquidsec Sep 4, 2025
951ca41
fix virtual_host event test fixtures
liquidsec Sep 4, 2025
9940355
lint
liquidsec Sep 4, 2025
525481e
yanking debug stuff, polishing
liquidsec Sep 4, 2025
b3757ec
make curl command message debug only
liquidsec Sep 4, 2025
3082880
Merge branch 'dev' into virtualhost-upgrade
liquidsec Sep 4, 2025
f160f00
finish() scan bug fix
liquidsec Sep 4, 2025
978b5cd
go mariners
liquidsec Sep 5, 2025
7def332
as_completed error handling, tests
liquidsec Sep 5, 2025
2ba8318
yet another major refactor
liquidsec Sep 5, 2025
824d35d
i let the clanker try to alphabetize
liquidsec Sep 5, 2025
9fdf579
lint
liquidsec Sep 5, 2025
53512df
fixing test
liquidsec Sep 6, 2025
fbeadc0
adjustments
liquidsec Sep 7, 2025
cbe372d
more refactoring
liquidsec Sep 8, 2025
716652e
error message adjustments
liquidsec Sep 8, 2025
bf978b9
error handling
liquidsec Sep 9, 2025
05505d8
add web.response_similarity web helper
liquidsec Sep 10, 2025
7a24d16
use new response_similarity helper
liquidsec Sep 10, 2025
6d4c88b
further generalizing comparison helper
liquidsec Sep 10, 2025
c0f9467
Merge branch 'dev' into virtualhost-upgrade
liquidsec Sep 22, 2025
49e724b
Merge branch 'dev' into virtualhost-upgrade
liquidsec Sep 29, 2025
cd0c3f9
Merge branch 'dev' into virtualhost-upgrade
liquidsec Oct 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions bbot/core/event/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1604,16 +1604,17 @@ def _pretty_string(self):
return self.data["technology"]


class VHOST(DictHostEvent):
class VIRTUAL_HOST(DictHostEvent):
class _data_validator(BaseModel):
host: str
vhost: str
virtual_host: str
description: str
url: Optional[str] = None
_validate_url = field_validator("url")(validators.validate_url)
_validate_host = field_validator("host")(validators.validate_host)

def _pretty_string(self):
return self.data["vhost"]
return self.data["virtual_host"]


class PROTOCOL(DictHostEvent):
Expand Down
2 changes: 1 addition & 1 deletion bbot/core/helpers/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ async def _spawn_proc(self, *command, **kwargs):
raise ValueError("stdin and input arguments may not both be used.")
kwargs["stdin"] = asyncio.subprocess.PIPE

log.hugeverbose(f"run: {' '.join(command)}")
log.debug(f"run: {' '.join(command)}")
try:
proc = await asyncio.create_subprocess_exec(*command, **kwargs)
return proc, _input, command
Expand Down
109 changes: 91 additions & 18 deletions bbot/core/helpers/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
import regex as re
import subprocess as sp


from pathlib import Path
from contextlib import suppress
from unidecode import unidecode # noqa F401
from typing import Iterable, Awaitable, Optional
from asyncio import create_task, gather, sleep, wait_for # noqa
from urllib.parse import urlparse, quote, unquote, urlunparse, urljoin # noqa F401

Expand Down Expand Up @@ -2590,30 +2592,101 @@ def parse_port_string(port_string):
return ports


async def as_completed(coros):
async def as_completed(
coroutines: Iterable[Awaitable],
max_concurrent: Optional[int] = None,
):
"""
Async generator that yields completed Tasks as they are completed.
Yield completed coroutines as they finish with optional concurrency limiting.
All coroutines are scheduled as tasks internally for execution.

Args:
coros (iterable): An iterable of coroutine objects or asyncio Tasks.
Guarantees cleanup:
- If the consumer breaks early or an internal cancellation is detected, all remaining
tasks are cancelled and awaited (with return_exceptions=True) to avoid
"Task exception was never retrieved" warnings.
"""
it = iter(coroutines)

Yields:
asyncio.Task: A Task object that has completed its execution.
running: set[asyncio.Task] = set()
limit = max_concurrent or float("inf")

Examples:
>>> async def main():
... async for task in as_completed([coro1(), coro2(), coro3()]):
... result = task.result()
... print(f'Task completed with result: {result}')
async def _cancel_and_drain_remaining():
if not running:
return
for t in running:
t.cancel()
try:
await asyncio.gather(*running, return_exceptions=True)
finally:
running.clear()

>>> asyncio.run(main())
# Prime the running set up to the concurrency limit (or all, if unlimited)
try:
while len(running) < limit:
coro = next(it)
running.add(asyncio.create_task(coro))
except StopIteration:
pass

# Dedup state for repeated error messages
_last_err = {"msg": None, "count": 0}

try:
# Drain: yield completed tasks, backfill from the iterator as slots free up
while running:
done, running = await asyncio.wait(running, return_when=asyncio.FIRST_COMPLETED)
for task in done:
# Immediately backfill one slot per completed task, if more work remains
try:
coro = next(it)
running.add(asyncio.create_task(coro))
except StopIteration:
pass

# If task raised, handle cancellation gracefully and dedupe noisy repeats
if task.exception() is not None:
e = task.exception()
if in_exception_chain(e, (KeyboardInterrupt, asyncio.CancelledError)):
# Quietly stop if we're being cancelled
log.info("as_completed: cancellation detected; exiting early")
await _cancel_and_drain_remaining()
return
# Build a concise message
msg = f"as_completed yielded exception: {e}"
if msg == _last_err["msg"]:
_last_err["count"] += 1
if _last_err["count"] <= 3:
log.warning(msg)
elif _last_err["count"] % 10 == 0:
log.warning(f"{msg} (repeated {_last_err['count']}x)")
else:
log.debug(msg)
else:
_last_err["msg"] = msg
_last_err["count"] = 1
log.warning(msg)
yield task
finally:
# If the consumer breaks early or an error bubbles, ensure we don't leak tasks
await _cancel_and_drain_remaining()


def get_waf_strings():
"""
Returns a list of common WAF (Web Application Firewall) detection strings.

Returns:
list: List of WAF detection strings

Examples:
>>> waf_strings = get_waf_strings()
>>> "The requested URL was rejected" in waf_strings
True
"""
tasks = {coro if isinstance(coro, asyncio.Task) else asyncio.create_task(coro): coro for coro in coros}
while tasks:
done, _ = await asyncio.wait(tasks.keys(), return_when=asyncio.FIRST_COMPLETED)
for task in done:
tasks.pop(task)
yield task
return [
"The requested URL was rejected",
"This content has been blocked",
]


def clean_dns_record(record):
Expand Down
13 changes: 13 additions & 0 deletions bbot/core/helpers/names_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"crumbly",
"cryptic",
"cuddly",
"cursed",
"cute",
"dark",
"dastardly",
Expand Down Expand Up @@ -158,6 +159,7 @@
"mysterious",
"nascent",
"naughty",
"nautical",
"nefarious",
"negligent",
"neurotic",
Expand All @@ -169,6 +171,7 @@
"overmedicated",
"overwhelming",
"overzealous",
"pacific",
"paranoid",
"pasty",
"peckish",
Expand Down Expand Up @@ -347,7 +350,9 @@
"brittany",
"bruce",
"bryan",
"buhner",
"caitlyn",
"cal",
"caleb",
"cameron",
"carl",
Expand Down Expand Up @@ -432,6 +437,7 @@
"evan",
"evelyn",
"faramir",
"felix",
"florence",
"fox",
"frances",
Expand All @@ -458,6 +464,7 @@
"gollum",
"grace",
"gregory",
"griffey",
"gus",
"hagrid",
"hank",
Expand All @@ -472,6 +479,7 @@
"homer",
"howard",
"hunter",
"ichiro",
"irene",
"isaac",
"isabella",
Expand Down Expand Up @@ -515,6 +523,7 @@
"judy",
"julia",
"julie",
"julio",
"justin",
"karen",
"katherine",
Expand Down Expand Up @@ -547,6 +556,7 @@
"logan",
"lois",
"lori",
"lou",
"louis",
"louise",
"lucius",
Expand Down Expand Up @@ -578,6 +588,7 @@
"mildred",
"milhouse",
"monica",
"moose",
"nancy",
"natalie",
"nathan",
Expand Down Expand Up @@ -694,6 +705,8 @@
"wendy",
"william",
"willie",
"wilson",
"woo",
"worf",
"wormtongue",
"xavier",
Expand Down
Loading