Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions bbot_server/applets/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,11 @@ def make_activity(self, *args, **kwargs):
return Activity(*args, **kwargs)

async def emit_activity(self, *args, **kwargs):
"""
Emits an activity to the message queue.

Accepts either an Activity object, or arguments to create a new Activity object.
"""
if not kwargs and len(args) == 1 and isinstance(args[0], Activity):
activity = args[0]
else:
Expand Down
59 changes: 29 additions & 30 deletions bbot_server/models/asset_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,14 @@
host_split_regex = re.compile(r"[^a-z0-9]")


class BaseAssetFacet(BaseBBOTServerModel):
class BaseHostModel(BaseBBOTServerModel):
"""
An "asset facet" is a database object that contains data about an asset.
A base model for all BBOT Server models that have a host, port, netloc, and url

Unlike the main asset model which contains a summary of all the data,
a facet contains a certain detail which is too big to be stored in the main asset model.

For example, the main asset might contain a summary of all the technologies found on the asset,
but a facet might contain the specific technologies and details about their discovery.

A facet typically corresponds to an applet.
Inherited by Asset and Activity models.
"""

# unless overridden, all asset facets are stored in the asset store
__store_type__ = "asset"
__table_name__ = "assets"

# TODO: why is id commented out?
# id: Annotated[str, "indexed", "unique"] = Field(default_factory=lambda: str(uuid.uuid4()))
type: Annotated[Optional[str], "indexed"] = None
host: Annotated[str, "indexed"]
Expand All @@ -37,10 +28,8 @@ class BaseAssetFacet(BaseBBOTServerModel):
modified: Annotated[float, "indexed"] = Field(default_factory=utc_now)
ignored: bool = False
archived: bool = False
scope: Annotated[list[UUID], "indexed"] = []

def __init__(self, *args, **kwargs):
kwargs["type"] = self.__class__.__name__
event = kwargs.pop("event", None)
super().__init__(*args, **kwargs)
if self.host and self.port:
Expand Down Expand Up @@ -68,28 +57,38 @@ def set_event(self, event):
@computed_field
@property
def reverse_host(self) -> Annotated[str, "indexed"]:
if not self.host:
return ""
return self.host[::-1]

@computed_field
@property
def host_parts(self) -> Annotated[list[str], "indexed"]:
if not self.host:
return []
return host_split_regex.split(self.host)

# def _ingest_event(self, event) -> list[Activity]:
# self_before = self.__class__.model_validate(self)
# self.ingest_event(event)
# return self.diff(self_before)

# def ingest_event(self, event):
# """
# Given a BBOT event, update the asset facet.
class BaseAssetFacet(BaseHostModel):
"""
An "asset facet" is a database object that contains data about an asset.

# E.g., given an OPEN_TCP_PORT event, update the open_ports field to include the new port.
# """
# raise NotImplementedError(f"Must define ingest_event() in {self.__class__.__name__}")
Unlike the main asset model which contains a summary of all the data,
a facet contains a certain detail which is too big to be stored in the main asset model.

For example, the main asset might contain a summary of all the technologies found on the asset,
but a facet might contain the specific technologies and details about their discovery.

A facet typically corresponds to an applet.
"""

# scope is an array of target IDs, which are dynamically maintained as new scan data arrives, or as targets are created/updated.
scope: Annotated[list[UUID], "indexed"] = []

# unless overridden, all asset facets are stored in the asset store
__store_type__ = "asset"
__table_name__ = "assets"

# def diff(self, other) -> list[Activity]:
# """
# Given another facet (typically an older version of the same host), return a list of AssetActivities which describe the new changes.
# """
# raise NotImplementedError(f"Must define diff() in {self.__class__.__name__}")
def __init__(self, *args, **kwargs):
kwargs["type"] = self.__class__.__name__
super().__init__(*args, **kwargs)
12 changes: 4 additions & 8 deletions bbot_server/modules/activity/activity_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@

from bbot_server.utils.misc import utc_now
from bbot_server.cli.themes import COLOR, DARK_COLOR
from bbot_server.models.base import BaseBBOTServerModel
from bbot_server.models.asset_models import BaseHostModel

remove_rich_color_pattern = re.compile(r"\[([\w ]+)\](.*?)\[/\1\]")

log = logging.getLogger(__name__)


class Activity(BaseBBOTServerModel):
class Activity(BaseHostModel):
"""
An Activity is BBOT server's equivalent of an event.

Expand All @@ -25,23 +25,19 @@ class Activity(BaseBBOTServerModel):
They are usually associated with an asset, and can be traced back to a specific BBOT event.
"""

__table_name__ = "history"
__store_type__ = "asset"
__table_name__ = "history"
# id is a UUID
id: Annotated[str, "indexed", "unique"] = Field(default_factory=lambda: str(uuid.uuid4()))
type: Annotated[str, "indexed"]
timestamp: Annotated[float, "indexed"]
created: Annotated[float, "indexed"] = Field(default_factory=utc_now)
archived: Annotated[bool, "indexed"] = False
description: Annotated[str, "indexed"]
description_colored: str = Field(default="")
detail: dict[str, Any] = {}
host: Annotated[Optional[str], "indexed"] = None
port: Annotated[Optional[int], "indexed"] = None
netloc: Annotated[Optional[str], "indexed"] = None
url: Annotated[Optional[str], "indexed"] = None
module: Annotated[Optional[str], "indexed"] = None
scan: Annotated[Optional[str], "indexed"] = None
host: Annotated[Optional[str], "indexed"] = None
parent_event_uuid: Annotated[Optional[str], "indexed"] = None
parent_event_id: Annotated[Optional[str], "indexed"] = None
parent_scan_run_id: Annotated[Optional[str], "indexed"] = None
Expand Down
2 changes: 1 addition & 1 deletion bbot_server/modules/assets/assets_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ async def refresh_assets(self):
for child_applet in self.all_child_applets(include_self=True):
activities = await child_applet.refresh(asset, events_by_type)
for activity in activities:
await self._emit_activity(activity)
await self.emit_activity(activity)

# update the asset with any changes made by the child applets
await self.update_asset(asset)
Expand Down
4 changes: 3 additions & 1 deletion bbot_server/modules/findings/findings_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,8 +306,10 @@ async def _insert_or_update_finding(self, finding: Finding, asset, event=None):
{
"$set": {
"modified": self.helpers.utc_now(),
"confidence": finding.confidence_score,
"severity": finding.severity,
"severity_score": finding.severity_score,
"confidence": finding.confidence,
"confidence_score": finding.confidence_score,
}
},
)
Expand Down
2 changes: 1 addition & 1 deletion bbot_server/modules/presets/presets_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class Preset(BaseBBOTServerModel):
@classmethod
def sanitize_preset(cls, v: dict[str, Any]) -> dict[str, Any]:
# remote target information
for value in ("target", "targets", "whitelist", "blacklist"):
for value in ("target", "targets", "seeds", "blacklist"):
v.pop(value, None)
# remove strict scope setting (this is stored in the target)
config = v.pop("config", {})
Expand Down
4 changes: 2 additions & 2 deletions bbot_server/modules/scans/scans_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,8 @@ async def start_scans_loop(self):
# merge target and preset
scan_preset = dict(scan.preset.preset)
scan_preset["scan_name"] = scan.name
scan_preset["target"] = scan.target.seeds
scan_preset["whitelist"] = scan.target.whitelist
scan_preset["target"] = scan.target.target
scan_preset["seeds"] = scan.target.seeds
scan_preset["blacklist"] = scan.target.blacklist
config = scan_preset.get("config", {})
scope_config = config.get("scope", {})
Expand Down
2 changes: 1 addition & 1 deletion bbot_server/modules/scans/scans_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ def iter_scans():
finished = "" if scan.finished_at is None else self.timestamp_to_human(scan.finished_at)
target_name = f"[{self.COLOR}]{scan.target.name}[/{self.COLOR}]"
for attr, friendly_attr in (
("target_size", "Target"),
("seed_size", "Seeds"),
("whitelist_size", "Whitelist"),
("blacklist_size", "Blacklist"),
):
if getattr(scan.target, attr):
Expand Down
Loading