bbot 2.1.2.5173rc0__py3-none-any.whl → 2.1.2.5180rc0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of bbot might be problematic. Click here for more details.

bbot/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # version placeholder (replaced by poetry-dynamic-versioning)
2
- __version__ = "v2.1.2.5173rc"
2
+ __version__ = "v2.1.2.5180rc"
3
3
 
4
4
  from .scanner import Scanner, Preset
bbot/db/sql/models.py ADDED
@@ -0,0 +1,147 @@
1
+ # This file contains SQLModel (Pydantic + SQLAlchemy) models for BBOT events, scans, and targets.
2
+ # Used by the SQL output modules, but portable for outside use.
3
+
4
+ import json
5
+ import logging
6
+ from datetime import datetime
7
+ from pydantic import ConfigDict
8
+ from typing import List, Optional
9
+ from typing_extensions import Annotated
10
+ from pydantic.functional_validators import AfterValidator
11
+ from sqlmodel import inspect, Column, Field, SQLModel, JSON, String, DateTime as SQLADateTime
12
+
13
+
14
+ log = logging.getLogger("bbot_server.models")
15
+
16
+
17
+ def naive_datetime_validator(d: datetime):
18
+ """
19
+ Converts all dates into UTC, then drops timezone information.
20
+
21
+ This is needed to prevent inconsistencies in sqlite, because it is timezone-naive.
22
+ """
23
+ # drop timezone info
24
+ return d.replace(tzinfo=None)
25
+
26
+
27
+ NaiveUTC = Annotated[datetime, AfterValidator(naive_datetime_validator)]
28
+
29
+
30
+ class CustomJSONEncoder(json.JSONEncoder):
31
+ def default(self, obj):
32
+ # handle datetime
33
+ if isinstance(obj, datetime):
34
+ return obj.isoformat()
35
+ return super().default(obj)
36
+
37
+
38
+ class BBOTBaseModel(SQLModel):
39
+ model_config = ConfigDict(extra="ignore")
40
+
41
+ def __init__(self, *args, **kwargs):
42
+ self._validated = None
43
+ super().__init__(*args, **kwargs)
44
+
45
+ @property
46
+ def validated(self):
47
+ try:
48
+ if self._validated is None:
49
+ self._validated = self.__class__.model_validate(self)
50
+ return self._validated
51
+ except AttributeError:
52
+ return self
53
+
54
+ def to_json(self, **kwargs):
55
+ return json.dumps(self.validated.model_dump(), sort_keys=True, cls=CustomJSONEncoder, **kwargs)
56
+
57
+ @classmethod
58
+ def _pk_column_names(cls):
59
+ return [column.name for column in inspect(cls).primary_key]
60
+
61
+ def __hash__(self):
62
+ return hash(self.to_json())
63
+
64
+ def __eq__(self, other):
65
+ return hash(self) == hash(other)
66
+
67
+
68
+ ### EVENT ###
69
+
70
+
71
+ class Event(BBOTBaseModel, table=True):
72
+
73
+ def __init__(self, *args, **kwargs):
74
+ super().__init__(*args, **kwargs)
75
+ data = self._get_data(self.data, self.type)
76
+ self.data = {self.type: data}
77
+ if self.host:
78
+ self.reverse_host = self.host[::-1]
79
+
80
+ def get_data(self):
81
+ return self._get_data(self.data, self.type)
82
+
83
+ @staticmethod
84
+ def _get_data(data, type):
85
+ # handle SIEM-friendly format
86
+ if isinstance(data, dict) and list(data) == [type]:
87
+ return data[type]
88
+ return data
89
+
90
+ uuid: str = Field(
91
+ primary_key=True,
92
+ index=True,
93
+ nullable=False,
94
+ )
95
+ id: str = Field(index=True)
96
+ type: str = Field(index=True)
97
+ scope_description: str
98
+ data: dict = Field(sa_type=JSON)
99
+ host: Optional[str]
100
+ port: Optional[int]
101
+ netloc: Optional[str]
102
+ # store the host in reversed form for efficient lookups by domain
103
+ reverse_host: Optional[str] = Field(default="", exclude=True, index=True)
104
+ resolved_hosts: List = Field(default=[], sa_type=JSON)
105
+ dns_children: dict = Field(default={}, sa_type=JSON)
106
+ web_spider_distance: int = 10
107
+ scope_distance: int = Field(default=10, index=True)
108
+ scan: str = Field(index=True)
109
+ timestamp: NaiveUTC = Field(index=True)
110
+ parent: str = Field(index=True)
111
+ tags: List = Field(default=[], sa_type=JSON)
112
+ module: str = Field(index=True)
113
+ module_sequence: str
114
+ discovery_context: str = ""
115
+ discovery_path: List[str] = Field(default=[], sa_type=JSON)
116
+ parent_chain: List[str] = Field(default=[], sa_type=JSON)
117
+
118
+
119
+ ### SCAN ###
120
+
121
+
122
+ class Scan(BBOTBaseModel, table=True):
123
+ id: str = Field(primary_key=True)
124
+ name: str
125
+ status: str
126
+ started_at: NaiveUTC = Field(index=True)
127
+ finished_at: Optional[NaiveUTC] = Field(default=None, sa_column=Column(SQLADateTime, nullable=True, index=True))
128
+ duration_seconds: Optional[float] = Field(default=None)
129
+ duration: Optional[str] = Field(default=None)
130
+ target: dict = Field(sa_type=JSON)
131
+ preset: dict = Field(sa_type=JSON)
132
+
133
+
134
+ ### TARGET ###
135
+
136
+
137
+ class Target(BBOTBaseModel, table=True):
138
+ name: str = "Default Target"
139
+ strict_scope: bool = False
140
+ seeds: List = Field(default=[], sa_type=JSON)
141
+ whitelist: List = Field(default=None, sa_type=JSON)
142
+ blacklist: List = Field(default=[], sa_type=JSON)
143
+ hash: str = Field(sa_column=Column("hash", String, unique=True, primary_key=True, index=True))
144
+ scope_hash: str = Field(sa_column=Column("scope_hash", String, index=True))
145
+ seed_hash: str = Field(sa_column=Column("seed_hashhash", String, index=True))
146
+ whitelist_hash: str = Field(sa_column=Column("whitelist_hash", String, index=True))
147
+ blacklist_hash: str = Field(sa_column=Column("blacklist_hash", String, index=True))
@@ -0,0 +1,29 @@
1
+ from pathlib import Path
2
+
3
+ from bbot.modules.templates.sql import SQLTemplate
4
+
5
+
6
+ class SQLite(SQLTemplate):
7
+ watched_events = ["*"]
8
+ meta = {"description": "Output scan data to a SQLite database"}
9
+ options = {
10
+ "database": "",
11
+ }
12
+ options_desc = {
13
+ "database": "The path to the sqlite database file",
14
+ }
15
+ deps_pip = ["sqlmodel", "sqlalchemy-utils", "aiosqlite"]
16
+
17
+ async def setup(self):
18
+ db_file = self.config.get("database", "")
19
+ if not db_file:
20
+ db_file = self.scan.home / "output.sqlite"
21
+ db_file = Path(db_file)
22
+ if not db_file.is_absolute():
23
+ db_file = self.scan.home / db_file
24
+ self.db_file = db_file
25
+ self.db_file.parent.mkdir(parents=True, exist_ok=True)
26
+ return await super().setup()
27
+
28
+ def connection_string(self, mask_password=False):
29
+ return f"sqlite+aiosqlite:///{self.db_file}"
@@ -0,0 +1,89 @@
1
+ from sqlmodel import SQLModel
2
+ from sqlalchemy.orm import sessionmaker
3
+ from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
4
+ from sqlalchemy_utils.functions import database_exists, create_database
5
+
6
+ from bbot.db.sql.models import Event, Scan, Target
7
+ from bbot.modules.output.base import BaseOutputModule
8
+
9
+
10
+ class SQLTemplate(BaseOutputModule):
11
+ meta = {"description": "SQL output module template"}
12
+ options = {
13
+ "protocol": "",
14
+ "database": "bbot",
15
+ "username": "",
16
+ "password": "",
17
+ "host": "127.0.0.1",
18
+ "port": 0,
19
+ }
20
+ options_desc = {
21
+ "protocol": "The protocol to use to connect to the database",
22
+ "database": "The database to use",
23
+ "username": "The username to use to connect to the database",
24
+ "password": "The password to use to connect to the database",
25
+ "host": "The host to use to connect to the database",
26
+ "port": "The port to use to connect to the database",
27
+ }
28
+
29
+ async def setup(self):
30
+ self.database = self.config.get("database", "bbot")
31
+ self.username = self.config.get("username", "")
32
+ self.password = self.config.get("password", "")
33
+ self.host = self.config.get("host", "127.0.0.1")
34
+ self.port = self.config.get("port", 0)
35
+
36
+ self.log.info(f"Connecting to {self.connection_string(mask_password=True)}")
37
+
38
+ self.engine = create_async_engine(self.connection_string())
39
+ # Create a session factory bound to the engine
40
+ self.async_session = sessionmaker(self.engine, expire_on_commit=False, class_=AsyncSession)
41
+ await self.init_database()
42
+ return True
43
+
44
+ async def handle_event(self, event):
45
+ event_obj = Event(**event.json()).validated
46
+
47
+ async with self.async_session() as session:
48
+ async with session.begin():
49
+ # insert event
50
+ session.add(event_obj)
51
+
52
+ # if it's a SCAN event, create/update the scan and target
53
+ if event_obj.type == "SCAN":
54
+ event_data = event_obj.get_data()
55
+ if not isinstance(event_data, dict):
56
+ raise ValueError(f"Invalid data for SCAN event: {event_data}")
57
+ scan = Scan(**event_data).validated
58
+ await session.merge(scan) # Insert or update scan
59
+
60
+ target_data = event_data.get("target", {})
61
+ if not isinstance(target_data, dict):
62
+ raise ValueError(f"Invalid target for SCAN event: {target_data}")
63
+ target = Target(**target_data).validated
64
+ await session.merge(target) # Insert or update target
65
+
66
+ await session.commit()
67
+
68
+ async def init_database(self):
69
+ async with self.engine.begin() as conn:
70
+ # Check if the database exists using the connection's engine URL
71
+ if not await conn.run_sync(lambda sync_conn: database_exists(sync_conn.engine.url)):
72
+ await conn.run_sync(lambda sync_conn: create_database(sync_conn.engine.url))
73
+ # Create all tables
74
+ await conn.run_sync(SQLModel.metadata.create_all)
75
+
76
+ def connection_string(self, mask_password=False):
77
+ connection_string = f"{self.protocol}://"
78
+ if self.username:
79
+ password = self.password
80
+ if mask_password:
81
+ password = "****"
82
+ connection_string += f"{self.username}:{password}"
83
+ if self.host:
84
+ connection_string += f"@{self.host}"
85
+ if self.port:
86
+ connection_string += f":{self.port}"
87
+ if self.database:
88
+ connection_string += f"/{self.database}"
89
+ return connection_string
bbot/scanner/scanner.py CHANGED
@@ -161,7 +161,7 @@ class Scanner:
161
161
  tries += 1
162
162
  else:
163
163
  scan_name = str(self.preset.scan_name)
164
- self.name = scan_name
164
+ self.name = scan_name.replace("/", "_")
165
165
 
166
166
  # make sure the preset has a description
167
167
  if not self.preset.description:
@@ -0,0 +1,18 @@
1
+ import sqlite3
2
+ from .base import ModuleTestBase
3
+
4
+
5
+ class TestSQLite(ModuleTestBase):
6
+ targets = ["evilcorp.com"]
7
+
8
+ def check(self, module_test, events):
9
+ sqlite_output_file = module_test.scan.home / "output.sqlite"
10
+ assert sqlite_output_file.exists(), "SQLite output file not found"
11
+ with sqlite3.connect(sqlite_output_file) as db:
12
+ cursor = db.cursor()
13
+ cursor.execute("SELECT * FROM event")
14
+ assert len(cursor.fetchall()) > 0, "No events found in SQLite database"
15
+ cursor.execute("SELECT * FROM scan")
16
+ assert len(cursor.fetchall()) > 0, "No scans found in SQLite database"
17
+ cursor.execute("SELECT * FROM target")
18
+ assert len(cursor.fetchall()) > 0, "No targets found in SQLite database"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bbot
3
- Version: 2.1.2.5173rc0
3
+ Version: 2.1.2.5180rc0
4
4
  Summary: OSINT automation for hackers.
5
5
  Home-page: https://github.com/blacklanternsecurity/bbot
6
6
  License: GPL-3.0
@@ -1,4 +1,4 @@
1
- bbot/__init__.py,sha256=iK8WXit-jx4ZqGMOaH9TkpxZ6WlAKQrfE2Y-x0ZMPXY,130
1
+ bbot/__init__.py,sha256=7P-sFinr0Bm0Rs1mjS4V3YlX2Bn0VY0mSdU-Wy9dDJY,130
2
2
  bbot/cli.py,sha256=7S3a4eB-Dl8yodc5WC-927Z30CNlLl9EXimGvIVypJo,10434
3
3
  bbot/core/__init__.py,sha256=l255GJE_DvUnWvrRb0J5lG-iMztJ8zVvoweDOfegGtI,46
4
4
  bbot/core/config/__init__.py,sha256=zYNw2Me6tsEr8hOOkLb4BQ97GB7Kis2k--G81S8vofU,342
@@ -46,6 +46,7 @@ bbot/core/helpers/web/web.py,sha256=K7BOts1c1bRjU5rpluD94jClwchmBMZQk8FZI1ljS94,
46
46
  bbot/core/helpers/wordcloud.py,sha256=WdQwboCNcCxcUdLuB6MMMDQBL4ZshFM_f6GW7nUZEBQ,19819
47
47
  bbot/core/modules.py,sha256=OOUSncr-EM6bJBrI3iH5wvfnpTXKQ-A8OL8UMvkL0CU,31432
48
48
  bbot/core/shared_deps.py,sha256=IZgYbeJy20ToUNa8TnNAgzaKRK_c09W6rl-uxEhudd0,5187
49
+ bbot/db/sql/models.py,sha256=AXefz4nEtpV2p19d6rLaEcCQNnqXODpldA-P5uSUntg,4729
49
50
  bbot/defaults.yml,sha256=_3sNH-2TWPaQHZ6ozBA1UKWLB7HuHK8vjZ534mb8cO4,6042
50
51
  bbot/errors.py,sha256=xwQcD26nU9oc7-o0kv5jmEDTInmi8_W8eKAgQZZxdVM,953
51
52
  bbot/logger.py,sha256=rLcLzNDvfR8rFj7_tZ-f5QB3Z8T0RVroact3W0ogjpA,1408
@@ -143,6 +144,7 @@ bbot/modules/output/neo4j.py,sha256=u950eUwu8YMql_WaBA38TN2bUhx7xnZdIIvYfR3xVcY,
143
144
  bbot/modules/output/python.py,sha256=RvK2KN-Zp0Vy_1zGSNioE5eeL5hIh6Z_riFtaTymyIM,270
144
145
  bbot/modules/output/slack.py,sha256=Ir_z11VYBdXDx8DwntWCv33Ic43vO1UIbxcp9gj0vvk,1181
145
146
  bbot/modules/output/splunk.py,sha256=TjTCUmDwRwKOFKBJw-Xbjku64U77OauHjtR56gyaAPs,1952
147
+ bbot/modules/output/sqlite.py,sha256=kR5JSYbenCVrFfwMyHtOmuRyGdBK-xTEfVZMUeedYs8,908
146
148
  bbot/modules/output/stdout.py,sha256=kRVlliUcQ6aZeweZvTsl12IqVbQdnPIhTO0Bid-ILaI,3024
147
149
  bbot/modules/output/subdomains.py,sha256=3KZz4vD0itmqpo56uCyk43Z_zN1Q0Q_nyXjdnEublPA,1515
148
150
  bbot/modules/output/teams.py,sha256=I2d52LqDC7e_oboLHoYBaRK6UpN0nkJ0uRnBXrhZf9E,4628
@@ -178,6 +180,7 @@ bbot/modules/templates/bucket.py,sha256=x-c_iAeMILux6wRm0xkUUJkc2P69hYWS6DxqD7g5
178
180
  bbot/modules/templates/github.py,sha256=ENnDWpzzmZBsTisDx6Cg9V_NwJKyVyPIOpGAPktigdI,1455
179
181
  bbot/modules/templates/postman.py,sha256=oxwVusW2EdNotVX7xnnxCTnWtj3xNPbfs8aff9s4phs,614
180
182
  bbot/modules/templates/shodan.py,sha256=BfI0mNPbqkykGmjMtARhmCGKmk1uq7yTlZoPgzzJ040,1175
183
+ bbot/modules/templates/sql.py,sha256=7HRp4gBA7NipZeUL5OACiWkHf4jYJTX1j30Yv50d4eQ,3748
181
184
  bbot/modules/templates/subdomain_enum.py,sha256=lT5MZF66OuzsyFFrj20wKlsZflzL9MOkPjDIbN3o65o,8375
182
185
  bbot/modules/templates/webhook.py,sha256=MYhKWrNYrsfM0a4PR6yVotudLyyCwgmy2eI-l9LvpBs,3706
183
186
  bbot/modules/trickest.py,sha256=HfAzjnawxXd9ypi3gumDHqImE5-C7uwNugo8d_b9HT0,1544
@@ -215,7 +218,7 @@ bbot/scanner/preset/conditions.py,sha256=hFL9cSIWGEsv2TfM5UGurf0c91cyaM8egb5IngB
215
218
  bbot/scanner/preset/environ.py,sha256=-wbFk1YHpU8IJLKVw23Q3btQTICeX0iulURo7D673L0,4732
216
219
  bbot/scanner/preset/path.py,sha256=p9tZC7XcgZv2jXpbEJAg1lU2b4ZLX5COFnCxEUOXz2g,2234
217
220
  bbot/scanner/preset/preset.py,sha256=-HH_nlr4VaXmKCooXMG5av39gOUdCVOO_y9Bhgbt_u4,40180
218
- bbot/scanner/scanner.py,sha256=62DKCjgV1uLxNAwpxjvE5h1uzQCxG-nzBxp1PBCSVKc,53674
221
+ bbot/scanner/scanner.py,sha256=n0jpHQ9tXqrPJTwg8DndMgNPas0NbNzV0FeoQDbQgJE,53692
219
222
  bbot/scanner/stats.py,sha256=re93sArKXZSiD0Owgqk2J3Kdvfm3RL4Y9Qy_VOcaVk8,3623
220
223
  bbot/scanner/target.py,sha256=X25gpgRv5HmqQjGADiSe6b8744yOkRhAGAvKKYbXnSI,19886
221
224
  bbot/scripts/docs.py,sha256=kg2CzovmUVGJx9hBZjAjUdE1hXeIwC7Ry3CyrnE8GL8,10782
@@ -360,6 +363,7 @@ bbot/test/test_step_2/module_tests/test_module_smuggler.py,sha256=J23iNHxJFbZ0vd
360
363
  bbot/test/test_step_2/module_tests/test_module_social.py,sha256=McjY8g97HbZm5xnarW924pGytwwGGnzA6MfD0HmBPMU,2050
361
364
  bbot/test/test_step_2/module_tests/test_module_speculate.py,sha256=UvlSWOAtxl6jS63m4JXFRUrBHuCZZzRCsTRgdbljSeQ,3131
362
365
  bbot/test/test_step_2/module_tests/test_module_splunk.py,sha256=zCHVlgTVlFiXwhcYBeGb9Fy2RlmvVodpl4pq3j-8jJw,1859
366
+ bbot/test/test_step_2/module_tests/test_module_sqlite.py,sha256=9Q-V4wwezgJN0vdBKhkWYePuHO5zOdw98schSFhfj1c,793
363
367
  bbot/test/test_step_2/module_tests/test_module_sslcert.py,sha256=XeiV9eQZNnA5oALCVnP7bEs3m9kMaVwEtg-hvYfgi3Y,711
364
368
  bbot/test/test_step_2/module_tests/test_module_stdout.py,sha256=JbvSUCygrG3Tq225i9dabqeUFxBHUq44TeuxH-tEWpc,3250
365
369
  bbot/test/test_step_2/module_tests/test_module_subdomaincenter.py,sha256=sNWlk4_dDtFhMtGGr7VLnMukJkVwcEGpCYViEcqrVEw,610
@@ -396,8 +400,8 @@ bbot/wordlists/raft-small-extensions-lowercase_CLEANED.txt,sha256=ruUQwVfia1_m2u
396
400
  bbot/wordlists/top_open_ports_nmap.txt,sha256=LmdFYkfapSxn1pVuQC2LkOIY2hMLgG-Xts7DVtYzweM,42727
397
401
  bbot/wordlists/valid_url_schemes.txt,sha256=VciB-ww0y-O8Ii1wpTR6rJzGDiC2r-dhVsIJApS1ZYU,3309
398
402
  bbot/wordlists/wordninja_dns.txt.gz,sha256=DYHvvfW0TvzrVwyprqODAk4tGOxv5ezNmCPSdPuDUnQ,570241
399
- bbot-2.1.2.5173rc0.dist-info/LICENSE,sha256=GzeCzK17hhQQDNow0_r0L8OfLpeTKQjFQwBQU7ZUymg,32473
400
- bbot-2.1.2.5173rc0.dist-info/METADATA,sha256=d1BvjvRLvQF4l-rmOfTs8unWVCHLSe0iVIP4j92pzSU,17003
401
- bbot-2.1.2.5173rc0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
402
- bbot-2.1.2.5173rc0.dist-info/entry_points.txt,sha256=cWjvcU_lLrzzJgjcjF7yeGuRA_eDS8pQ-kmPUAyOBfo,38
403
- bbot-2.1.2.5173rc0.dist-info/RECORD,,
403
+ bbot-2.1.2.5180rc0.dist-info/LICENSE,sha256=GzeCzK17hhQQDNow0_r0L8OfLpeTKQjFQwBQU7ZUymg,32473
404
+ bbot-2.1.2.5180rc0.dist-info/METADATA,sha256=NsqTqHYM8LWSlsyChMjEOjd5DjtY3F6DjeEULRur55M,17003
405
+ bbot-2.1.2.5180rc0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
406
+ bbot-2.1.2.5180rc0.dist-info/entry_points.txt,sha256=cWjvcU_lLrzzJgjcjF7yeGuRA_eDS8pQ-kmPUAyOBfo,38
407
+ bbot-2.1.2.5180rc0.dist-info/RECORD,,