-
Notifications
You must be signed in to change notification settings - Fork 571
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1944 from blacklanternsecurity/mysql
New Module: MySQL Output
- Loading branch information
Showing
6 changed files
with
175 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
from bbot.modules.templates.sql import SQLTemplate | ||
|
||
|
||
class MySQL(SQLTemplate): | ||
watched_events = ["*"] | ||
meta = {"description": "Output scan data to a MySQL database"} | ||
options = { | ||
"username": "root", | ||
"password": "bbotislife", | ||
"host": "localhost", | ||
"port": 3306, | ||
"database": "bbot", | ||
} | ||
options_desc = { | ||
"username": "The username to connect to MySQL", | ||
"password": "The password to connect to MySQL", | ||
"host": "The server running MySQL", | ||
"port": "The port to connect to MySQL", | ||
"database": "The database name to connect to", | ||
} | ||
deps_pip = ["sqlmodel", "aiomysql"] | ||
protocol = "mysql+aiomysql" | ||
|
||
async def create_database(self): | ||
from sqlalchemy import text | ||
from sqlalchemy.ext.asyncio import create_async_engine | ||
|
||
# Create the engine for the initial connection to the server | ||
initial_engine = create_async_engine(self.connection_string().rsplit("/", 1)[0]) | ||
|
||
async with initial_engine.connect() as conn: | ||
# Check if the database exists | ||
result = await conn.execute(text(f"SHOW DATABASES LIKE '{self.database}'")) | ||
database_exists = result.scalar() is not None | ||
|
||
# Create the database if it does not exist | ||
if not database_exists: | ||
# Use aiomysql directly to create the database | ||
import aiomysql | ||
|
||
raw_conn = await aiomysql.connect( | ||
user=self.username, | ||
password=self.password, | ||
host=self.host, | ||
port=self.port, | ||
) | ||
try: | ||
async with raw_conn.cursor() as cursor: | ||
await cursor.execute(f"CREATE DATABASE {self.database}") | ||
finally: | ||
await raw_conn.ensure_closed() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import asyncio | ||
import time | ||
|
||
from .base import ModuleTestBase | ||
|
||
|
||
class TestMySQL(ModuleTestBase): | ||
targets = ["evilcorp.com"] | ||
skip_distro_tests = True | ||
|
||
async def setup_before_prep(self, module_test): | ||
process = await asyncio.create_subprocess_exec( | ||
"docker", | ||
"run", | ||
"--name", | ||
"bbot-test-mysql", | ||
"--rm", | ||
"-e", | ||
"MYSQL_ROOT_PASSWORD=bbotislife", | ||
"-e", | ||
"MYSQL_DATABASE=bbot", | ||
"-p", | ||
"3306:3306", | ||
"-d", | ||
"mysql", | ||
stdout=asyncio.subprocess.PIPE, | ||
stderr=asyncio.subprocess.PIPE, | ||
) | ||
stdout, stderr = await process.communicate() | ||
|
||
import aiomysql | ||
|
||
# wait for the container to start | ||
start_time = time.time() | ||
while True: | ||
try: | ||
conn = await aiomysql.connect(user="root", password="bbotislife", db="bbot", host="localhost") | ||
conn.close() | ||
break | ||
except Exception as e: | ||
if time.time() - start_time > 60: # timeout after 60 seconds | ||
self.log.error("MySQL server did not start in time.") | ||
raise e | ||
await asyncio.sleep(1) | ||
|
||
if process.returncode != 0: | ||
self.log.error(f"Failed to start MySQL server: {stderr.decode()}") | ||
|
||
async def check(self, module_test, events): | ||
import aiomysql | ||
|
||
# Connect to the MySQL database | ||
conn = await aiomysql.connect(user="root", password="bbotislife", db="bbot", host="localhost") | ||
|
||
try: | ||
async with conn.cursor() as cur: | ||
await cur.execute("SELECT * FROM event") | ||
events = await cur.fetchall() | ||
assert len(events) == 3, "No events found in MySQL database" | ||
|
||
await cur.execute("SELECT * FROM scan") | ||
scans = await cur.fetchall() | ||
assert len(scans) == 1, "No scans found in MySQL database" | ||
|
||
await cur.execute("SELECT * FROM target") | ||
targets = await cur.fetchall() | ||
assert len(targets) == 1, "No targets found in MySQL database" | ||
finally: | ||
conn.close() | ||
process = await asyncio.create_subprocess_exec( | ||
"docker", "stop", "bbot-test-mysql", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE | ||
) | ||
stdout, stderr = await process.communicate() | ||
|
||
if process.returncode != 0: | ||
raise Exception(f"Failed to stop MySQL server: {stderr.decode()}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters