diff options
Diffstat (limited to 'iv/orodja/napad')
-rw-r--r-- | iv/orodja/napad/.gitignore | 1 | ||||
-rw-r--r-- | iv/orodja/napad/config | 44 | ||||
-rwxr-xr-x | iv/orodja/napad/exploit.sh | 23 | ||||
-rwxr-xr-x | iv/orodja/napad/submission.py | 64 |
4 files changed, 132 insertions, 0 deletions
diff --git a/iv/orodja/napad/.gitignore b/iv/orodja/napad/.gitignore new file mode 100644 index 0000000..e417f8f --- /dev/null +++ b/iv/orodja/napad/.gitignore @@ -0,0 +1 @@ +flags.db diff --git a/iv/orodja/napad/config b/iv/orodja/napad/config new file mode 100644 index 0000000..371faed --- /dev/null +++ b/iv/orodja/napad/config @@ -0,0 +1,44 @@ +# Common config for exploit.sh and submission.py. +# It is to be sourced. It only sets environment variables. + +# ========================== +# ========= COMMON ========= + +export FLAG_REGEX="^[A-Z0-9]{31}=$" +export SUBMISSION_PORT=21502 + +# ========================== +# ======= EXPLOIT.SH ======= + +# Where can exploit.sh find submission.py. Port is a common setting. +export SUBMISSION_HOST=k.4a.si + +# Must be precise, not less than round duration. Used to calculate round id. +export ROUND_DURATION=120 + +# When does the game start (in UTC). Used to calculate current round id. +export GAME_START=2024-09-01T07:00:00 + +# ========================== +# ====== SUBMISSION.PY ===== + +export SUBMISSION_DB=flags.db + +# How much flags to send in one request. +# With 2560, if it takes 37 bytes per flag, 2560*37=94720 +# Ostane nam torej še dobrih 5280 za headerje, +# če je request limited na 100 kB +export SUBMISSION_MAX_FLAGS=2560 + +# PUT request, ECSC 2024 AD style +export SUBMISSION_URL=http://10.10.0.1:8080/flags + +# How many seconds to delay after a successful submission. +# With 15, we send at most 4 requests per minute out of 15 allowed. +export SUBMISSION_DELAY=15 + +# This is sent in X-Team-Token in requests to SUBMISSION_URL +export SUBMISSION_TEAM_TOKEN=e5152d70a4d18093cae8844f4e959cf1 + +# Where to bind to. Use SUBMISSION_PORT in common settings for port. +export SUBMISSION_BIND=:: diff --git a/iv/orodja/napad/exploit.sh b/iv/orodja/napad/exploit.sh new file mode 100755 index 0000000..1111b00 --- /dev/null +++ b/iv/orodja/napad/exploit.sh @@ -0,0 +1,23 @@ +#!/bin/sh +if [ x$1 = x ] +then +echo >&2 <<EOF +No command. Subcommands: + $0 once <exploit> # runs an exploit once, print captured flags + $1 loop <exploit> # runs an exploit in a loop once per round +<exploit> is an executable file. Flags, grepped from stdout, are submitted. +It is called for every target. Args are target IP and flag IDs JSON object. + Example: <exploit> 10.1.2.3 '{"user": "root", "pass": "hunter2"}' +Flag IDs are also available in the environment as variables FLAG_ID_<key>: + {"user": "root", "pass": "hunter2"} will be in environment as vars + FLAG_ID_user=root and FLAG_ID_pass=hunter2 +In loop mode, exploit is first exec'd rapidly for still valid old rounds. +Max execution time is $EXPLOIT_TIMEOUT seconds (EXPLOIT_TIMEOUT in config) +Exploits are NOT executed in parallel. +Make sure that your system time is set CORRECTLY TO THE SECOND, it's used + to get the current round id. Current time: `date`. +Configuration values are also available in environment of exploits. +EOF + exit 1 +fi +set -xeuo pipefail diff --git a/iv/orodja/napad/submission.py b/iv/orodja/napad/submission.py new file mode 100755 index 0000000..dcf75ca --- /dev/null +++ b/iv/orodja/napad/submission.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +import os +import asyncio +import re +import sqlite3 +import aiohttp +db = sqlite3.connect(os.getenv("SUBMISSION_DB", "flags.db")) +db.execute("CREATE TABLE IF NOT EXISTS flags (id INTEGER PRIMARY KEY, flag TEXT NOT NULL UNIQUE, sent INTEGER NOT NULL DEFAULT 0, date TEXT DEFAULT (strftime('%FT%R:%f', 'now')) NOT NULL, status TEXT, msg TEXT) STRICT") +flag_regex = re.compile(os.getenv("FLAG_REGEX", "^[A-Z0-9]{31}=$").encode(), re.ASCII | re.DOTALL | re.VERBOSE) +async def submitter (): + while True: + print("submitter loop") + flags = [] + for row in db.execute("SELECT flag FROM flags WHERE sent == 0 ORDER BY date DESC"): + if len(flags) < int(os.getenv("SUBMISSION_MAX_FLAGS", "2560")): + flags.append(row[0]) + if len(flags) == 0: + await asyncio.sleep(1) + for i in [1]: + async with aiohttp.ClientSession(headers={"X-Team-Token": os.getenv("SUBMISSION_TEAM_TOKEN")}) as session: + async with session.put(os.getenv("SUBMISSION_URL", 'http://10.10.0.1:8080/flags'), json=flags) as response: + if response.status // 100 != 2: + print("submitter error: " + await response.text()) + break + cursor = db.cursor() + for obj in await response.json(): + cursor.execute("UPDATE flags SET sent=?, status=?, msg=? WHERE flag=?", [int(obj.get("status") != "RESUBMIT"), obj.get("status"), obj.get("msg"), obj.get("flag")]) + db.commit() + await asyncio.sleep(int(os.getenv("SUBMISSION_DELAY", "15"))) +async def handle_client (reader, writer): + while True: + incoming = await reader.readuntil(b'\n') + if len(incoming) == 0: + break + buffer = incoming.replace(b'\r', b'').replace(b'\n', b'') + if buffer.startswith(b' '): + for row in db.execute(buffer[1:].decode()): + writer.write(str(row).encode() + b'\n') + continue + if buffer.startswith(b'@'): + writer.write(str(db.execute(buffer[1:].decode()).fetchall()).encode() + b'\n') + continue + if buffer.startswith(b'#'): + writer.write(str(len(db.execute(buffer[1:].decode()).fetchall())).encode() + b'\n') + continue + if re.match(flag_regex, buffer) == None: + writer.write(b'BAD_FLAG\n') + continue + flag = buffer.decode() + try: + db.execute("INSERT INTO flags (flag) VALUES (?)", [flag]) + except sqlite3.IntegrityError: + status, msg, date = [x for x in db.execute("SELECT status, msg, date FROM flags WHERE flag=?", [flag])][0] + writer.write(b"OLD_FLAG " + date.encode() + b" " + str(status).encode() + b" " + str(msg).encode() + b"\n") + else: + writer.write(b'NEW_FLAG\n') + writer.close() +async def run_server (): + server = await asyncio.start_server(handle_client, os.getenv("SUBMISSION_BIND", "::"), os.getenv("SUBMISSION_PORT", "21502")) + event_loop = asyncio.get_event_loop() + event_loop.create_task(submitter()) + async with server: + await server.serve_forever() +asyncio.run(run_server()) |