Skip to content

Commit

Permalink
documentation for Target
Browse files Browse the repository at this point in the history
  • Loading branch information
TheTechromancer committed Sep 14, 2023
1 parent 0ce26a1 commit f6b3c75
Show file tree
Hide file tree
Showing 2 changed files with 191 additions and 17 deletions.
41 changes: 29 additions & 12 deletions bbot/modules/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ async def finish(self):
This method can be overridden to execute any necessary finalization logic. For example, if the module relies on a word cloud, you might wait for the scan to finish to ensure the word cloud is most complete before running an operation.
Returns:
None by default, but can return additional data if overridden.
None
Warnings:
This method may be called multiple times since it can raise events, which may re-trigger the "finish" phase of the scan. Optional to override.
Expand All @@ -218,7 +218,7 @@ async def report(self):
This method can be overridden to aggregate data and raise summary events at the end of the scan.
Returns:
None by default, but can return additional data if overridden.
None
Note:
This method is called only once per scan.
Expand All @@ -231,27 +231,28 @@ async def cleanup(self):
This method can be overridden to implement custom cleanup logic. It is called only once per scan and may not raise events.
Returns:
None by default, but can return additional data if overridden.
None
Note:
This method is called only once per scan and may not raise events.
"""
return

async def require_api_key(self):
"""Asynchronously checks if the module is configured with a valid API key.
This method is typically used within the setup() method to ensure that an API key is provided in the module configuration. Your module must define an 'api_key' in its config options for this method to work properly.
"""
Asynchronously checks if an API key is required and valid.
Example Usage:
def setup(self):
return await self.require_api_key()
Args:
None
Returns:
Tuple (bool, str): The first element is a boolean indicating whether the API is ready to use. The second element is a string message, either indicating that the API is ready or describing the error.
bool or tuple: Returns True if API key is valid and ready.
Returns a tuple (None, "error message") otherwise.
Raises:
Exception: Any exceptions raised by the self.ping() method will propagate.
Notes:
- Fetches the API key from the configuration.
- Calls the 'ping()' method to test API accessibility.
- Sets the API key readiness status accordingly.
"""
self.api_key = self.config.get("api_key", "")
if self.auth_secret:
Expand Down Expand Up @@ -308,6 +309,22 @@ def get_watched_events(self):
return self._watched_events

async def _handle_batch(self):
"""
Asynchronously handles a batch of events in the module.
Args:
None
Returns:
bool: True if events were submitted for processing, False otherwise.
Notes:
- The method is wrapped in a task counter to monitor asynchronous operations.
- Checks if there are any events in the incoming queue and module is not in an error state.
- Invokes '_events_waiting()' to fetch a batch of events.
- Calls the module's 'handle_batch()' method to process these events.
- If a "FINISHED" event is found, invokes 'finish()' method of the module.
"""
finish = False
async with self._task_counter.count(f"{self.name}.handle_batch()"):
submitted = False
Expand Down
167 changes: 162 additions & 5 deletions bbot/scanner/target.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
import ipaddress
from copy import deepcopy
from contextlib import suppress

from bbot.core.errors import *
Expand All @@ -10,11 +11,81 @@


class Target:
"""
A class representing a target. Can contain an unlimited number of hosts, IP or IP ranges, URLs, etc.
Attributes:
make_in_scope (bool): Specifies whether to mark contained events as in-scope.
scan (Scan): Reference to the Scan object that instantiated the Target.
_events (dict): Dictionary mapping hosts to events related to the target.
strict_scope (bool): Flag indicating whether to consider child domains in-scope.
If set to True, only the exact hosts specifieid and not their children are considered part of the target.
Examples:
Basic usage
>>> target = Target(scan, "evilcorp.com", "1.2.3.0/24")
>>> len(target)
257
>>> list(t.events)
[
DNS_NAME("evilcorp.com", module=TARGET, tags={'domain', 'distance-1', 'target'}),
IP_RANGE("1.2.3.0/24", module=TARGET, tags={'ipv4', 'distance-1', 'target'})
]
>>> "www.evilcorp.com" in target
True
>>> "1.2.3.4" in target
True
>>> "4.3.2.1" in target
False
>>> "https://admin.evilcorp.com" in target
True
>>> "[email protected]" in target
True
Event correlation
>>> target.get("www.evilcorp.com")
DNS_NAME("evilcorp.com", module=TARGET, tags={'domain', 'distance-1', 'target'})
>>> target.get("1.2.3.4")
IP_RANGE("1.2.3.0/24", module=TARGET, tags={'ipv4', 'distance-1', 'target'})
Target comparison
>>> target2 = Targets(scan, "www.evilcorp.com")
>>> target2 == target
False
>>> target2 in target
True
>>> target in target2
False
Notes:
- Targets are only precise down to the individual host. Ports and protocols are not considered in scope calculations.
- If you specify "https://evilcorp.com:8443" as a target, all of evilcorp.com (including subdomains and other ports and protocols) will be considered part of the target
- If you do not want to include child subdomains, use `strict_scope=True`
"""

make_in_scope = False

def __init__(self, scan, *targets, strict_scope=False):
"""
Initialize a Target object.
Args:
scan (Scan): Reference to the Scan object that instantiated the Target.
*targets: One or more targets (e.g., domain names, IP ranges) to be included in this Target.
strict_scope (bool, optional): Flag to control whether only the exact hosts are considered in-scope.
Defaults to False.
Attributes:
scan (Scan): Reference to the Scan object.
strict_scope (bool): Flag to control in-scope conditions. If True, only exact hosts are considered.
Notes:
- If you are instantiating a target from within a BBOT module, use `self.helpers.make_target()` instead. (this removes the need to pass in a scan object.)
- The strict_scope flag can be set to restrict scope calculation to only exactly-matching hosts and not their child subdomains.
- Each target is processed and stored as an `Event` in the '_events' dictionary.
"""
self.scan = scan
self.dummy_module = ScanTargetDummyModule(scan)
self._dummy_module = ScanTargetDummyModule(scan)
self._events = dict()
if len(targets) > 0:
log.verbose(f"Creating events from {len(targets):,} targets")
Expand All @@ -25,6 +96,23 @@ def __init__(self, scan, *targets, strict_scope=False):
self._hash = None

def add_target(self, t):
"""
Add a target or merge events from another Target object into this Target.
Args:
t: The target to be added. It can be either a string, an event object, or another Target object.
Attributes Modified:
_events (dict): The dictionary is updated to include the new target's events.
Examples:
>>> target.add_target('example.com')
Notes:
- If `t` is of the same class as this Target, all its events are merged.
- If `t` is an event, it is directly added to `_events`.
- If `make_in_scope` is True, the scope distance of the event is set to 0.
"""
if type(t) == self.__class__:
for k, v in t._events.items():
try:
Expand All @@ -35,7 +123,9 @@ def add_target(self, t):
if is_event(t):
event = t
else:
event = self.scan.make_event(t, source=self.scan.root_event, module=self.dummy_module, tags=["target"])
event = self.scan.make_event(
t, source=self.scan.root_event, module=self._dummy_module, tags=["target"]
)
if self.make_in_scope:
event.set_scope_distance(0)
try:
Expand All @@ -47,18 +137,73 @@ def add_target(self, t):

@property
def events(self):
"""
A generator property that yields all events in the target.
Yields:
Event object: One of the Event objects stored in the `_events` dictionary.
Examples:
>>> target = Target(scan, "example.com")
>>> for event in target.events:
... print(event)
Notes:
- This property is read-only.
- Iterating over this property gives you one event at a time from the `_events` dictionary.
"""
for _events in self._events.values():
yield from _events

def copy(self):
"""
Creates and returns a copy of the Target object, including a deep copy of the `_events` attribute.
Returns:
Target: A new Target object with the same `scan` and `strict_scope` attributes as the original.
A deep copy of the `_events` dictionary is made.
Examples:
>>> original_target = Target(scan, "example.com")
>>> copied_target = original_target.copy()
>>> copied_target is original_target
False
>>> copied_target == original_target
True
>>> copied_target in original_target
True
>>> original_target in copied_target
True
Notes:
- The `scan` object reference is kept intact in the copied Target object.
"""
self_copy = self.__class__(self.scan, strict_scope=self.strict_scope)
self_copy._events = dict(self._events)
self_copy._events = deepcopy(self._events)
return self_copy

def get(self, host):
"""
Get the matching target for a specified host. If not found, return None
Gets the event associated with the specified host from the target's `_events` dictionary.
Args:
host (Event, Target, or str): The hostname, IP, URL, or event to look for.
Returns:
Event or None: Returns the Event object associated with the given host if it exists, otherwise returns None.
Examples:
>>> target = Target(scan, "evilcorp.com", "1.2.3.0/24")
>>> target.get("www.evilcorp.com")
DNS_NAME("evilcorp.com", module=TARGET, tags={'domain', 'distance-1', 'target'})
>>> target.get("1.2.3.4")
IP_RANGE("1.2.3.0/24", module=TARGET, tags={'ipv4', 'distance-1', 'target'})
Notes:
- The method returns the first event that matches the given host.
- If `strict_scope` is False, it will also consider parent domains and IP ranges.
"""

try:
other = make_event(host, dummy=True)
except ValidationError:
Expand Down Expand Up @@ -108,7 +253,19 @@ def __hash__(self):

def __len__(self):
"""
Returns the total number of HOSTS (not events) in the target
Calculates and returns the total number of hosts within this target, not counting duplicate events.
Returns:
int: The total number of unique hosts present within the target's `_events`.
Examples:
>>> target = Target(scan, "evilcorp.com", "1.2.3.0/24")
>>> len(target)
257
Notes:
- If a host is represented as an IP network, all individual IP addresses in that network are counted.
- For other types of hosts, each unique event is counted as one.
"""
num_hosts = 0
for host, _events in self._events.items():
Expand Down

0 comments on commit f6b3c75

Please sign in to comment.