summaryrefslogtreecommitdiffstats
path: root/iv/orodja/napad
diff options
context:
space:
mode:
Diffstat (limited to 'iv/orodja/napad')
-rw-r--r--iv/orodja/napad/.gitignore1
-rw-r--r--iv/orodja/napad/config44
-rwxr-xr-xiv/orodja/napad/exploit.sh23
-rwxr-xr-xiv/orodja/napad/submission.py64
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())