diff options
Diffstat (limited to 'iv/orodja/napad')
-rwxr-xr-x | iv/orodja/napad/exploit.sh | 1 | ||||
-rwxr-xr-x[-rw-r--r--] | iv/orodja/napad/genconfig.sh (renamed from iv/orodja/napad/config) | 30 | ||||
-rw-r--r-- | iv/orodja/napad/index.html | 4 | ||||
-rwxr-xr-x | iv/orodja/napad/nadzor.py | 36 | ||||
-rwxr-xr-x | iv/orodja/napad/submission.py | 95 | ||||
-rw-r--r-- | iv/orodja/napad/templates/frontend.html | 58 |
6 files changed, 122 insertions, 102 deletions
diff --git a/iv/orodja/napad/exploit.sh b/iv/orodja/napad/exploit.sh index b482c06..9e2cafb 100755 --- a/iv/orodja/napad/exploit.sh +++ b/iv/orodja/napad/exploit.sh @@ -25,6 +25,7 @@ Set the following environment variables to alter behaviour: waiting for the next round. Only valid for loop subcommand. EXPLOIT_VERBOSE=1: print _every_ line executed by $0 (set -x) EXPLOIT_NOTPARALLEL=1: disable parallel even if parallel is available +$EXPLOIT_ADDITIONAL_HELP_TEXT EOF exit 1 fi diff --git a/iv/orodja/napad/config b/iv/orodja/napad/genconfig.sh index dc165ce..825da18 100644..100755 --- a/iv/orodja/napad/config +++ b/iv/orodja/napad/genconfig.sh @@ -1,4 +1,14 @@ -# Common config for exploit.sh and submission.py. +#!/bin/bash +set -xeuo pipefail +statusresp=`curl --fail-with-body --no-progress-meter https://ad.ecsc2024.it/api/status` +starttime=`jq --raw-output .start <<<"$statusresp"` +roundtime=`jq --raw-output .roundTime <<<"$statusresp"` +team_names=`jq --raw-output .teams.[].shortname <<<"$statusresp" | tr $'\n' ' '` +team_numbers=`jq --raw-output .teams.[].id <<<"$statusresp" | tr $'\n' ' '` +services=`jq --raw-output .services.[].shortname <<<"$statusresp" | tr $'\n' ' '` +cat <<EOF +# THIS CONFIG IS AUTOGENERATED BY genconfig.sh, edit config values there! +# Common config for exploit.sh, submission.py and nadzor.py # It is to be sourced. It only sets environment variables. # ========================== @@ -9,6 +19,9 @@ export SUBMISSION_PORT=21502 # ========================== # ======= EXPLOIT.SH ======= +# Additional help text +export EXPLOIT_ADDITIONAL_HELP_TEXT="Services: $services" + # This regex is used to grep -Eo flags from stdout of exploits before submitting them export FLAG_REGEX_SEARCH="[A-Za-z0-9]{31}=" @@ -17,15 +30,16 @@ export SUBMISSION_HOST=localhost ### export SUBMISSION_HOST=k.4a.si # Must be precise, not less than round duration. Used to calculate round id. -export ROUND_DURATION=120 +export ROUND_DURATION=$roundtime # When does the game start (in UTC). Used to calculate current round id. -export GAME_START=2024-09-01T07:00:00 +export GAME_START=$starttime # Team numbers to attack -export GAME_TEAMS="2 69" +export GAME_TEAMS="$team_numbers" ###export GAME_TEAMS={0..10} - +EOF +cat <<'EOF' # Flag IDs URL game_flag_ids_url() { @@ -48,7 +62,7 @@ export GAME_NOP_TEAM=0 # For how many non-current rounds are flags valid at a time? # It doesn't make sense for this to be less than 0. # Setting to 0 means only the current round is valid. -export GAME_VALID_ROUNDS=5 +export GAME_VALID_ROUNDS=4 # Function exploit.sh should call on errors. # Args: service team pwd usr@pc message @@ -94,3 +108,7 @@ export SUBMISSION_TEAM_TOKEN=e5152d70a4d18093cae8844f4e959cf1 # Where to bind to. Use SUBMISSION_PORT in common settings for port. export SUBMISSION_BIND=:: + +# ========================== +# ======== NADZOR.PY ======= +EOF diff --git a/iv/orodja/napad/index.html b/iv/orodja/napad/index.html deleted file mode 100644 index 6c5d495..0000000 --- a/iv/orodja/napad/index.html +++ /dev/null @@ -1,4 +0,0 @@ -<meta name=viewport content='width=device-width, initial-scale=1.0'> -<meta charset=utf-8 /> -<title>submission.py</title> -<h1>submission.py</h1> diff --git a/iv/orodja/napad/nadzor.py b/iv/orodja/napad/nadzor.py new file mode 100755 index 0000000..515aa3e --- /dev/null +++ b/iv/orodja/napad/nadzor.py @@ -0,0 +1,36 @@ +#!/usr/bin/python3 +from flask import Flask, render_template, request +import os +import sqlite3 +import sys + + +app = Flask(__name__) + +@app.route("/", methods=["GET"]) +def frontend(): + return render_template("frontend.html") + +@app.route("/sql", methods=["POST"]) +def sql(): + with sqlite3.connect(os.getenv("SUBMISSION_DB", "flags.db")) as db: + db.setconfig(sqlite3.SQLITE_DBCONFIG_DEFENSIVE, True) + rows = [] + for row in db.execute(request.data.decode()): + columns = [] + for column in row: + if type(column) == bytes: + columns.append(column.decode("utf-8", errors="surrogateescape")) + else: + columns.append(column) + rows.append(columns) + return rows + +if __name__ == "__main__": + port = 21503 + host = "::" + if len(sys.argv) > 1: + port = int(sys.argv[1]) + if len(sys.argv) > 2: + host = sys.argv[2] + app.run(port=port, debug=True, host=host) diff --git a/iv/orodja/napad/submission.py b/iv/orodja/napad/submission.py index 4a44947..c345bdb 100755 --- a/iv/orodja/napad/submission.py +++ b/iv/orodja/napad/submission.py @@ -5,9 +5,8 @@ import re import sqlite3 import aiohttp import traceback -import json 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, team INTEGER, service BLOB, round INTEGER, context BLOB, sent INTEGER NOT NULL DEFAULT 0, date TEXT DEFAULT (strftime('%FT%R:%f', 'now')) NOT NULL, status TEXT, msg TEXT) STRICT") +db.execute("CREATE TABLE IF NOT EXISTS flags (id INTEGER PRIMARY KEY, flag TEXT NOT NULL UNIQUE, team INTEGER, service BLOB, round INTEGER, context BLOB, sent INTEGER NOT NULL DEFAULT 0, date TEXT DEFAULT (strftime('%FT%R:%f', 'now')) NOT NULL, status TEXT, msg TEXT, submitted TEXT) STRICT") # submitted is date flag_regex = re.compile(os.getenv("FLAG_REGEX_MATCH", "^[A-Z0-9]{31}=$").encode(), re.ASCII | re.DOTALL | re.VERBOSE) async def submitter (): while True: @@ -40,18 +39,14 @@ async def submitter (): 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")]) + cursor.execute("UPDATE flags SET sent=?, status=?, msg=?, submitted=strftime('%FT%R:%f', 'now') WHERE flag=?", [int(obj.get("status") != "RESUBMIT"), obj.get("status"), obj.get("msg"), obj.get("flag")]) db.commit() except Exception as e: traceback.print_exc() await asyncio.sleep(int(os.getenv("SUBMISSION_DELAY", "15"))) async def handle_client (reader, writer): - linenumber = -1 - http_request = None - http_headers = dict() while True: - linenumber += 1 - try: + try: # SUBMISSION LINE FORMAT: "flag teamnumber roundnumber service any other context" incoming = await reader.readuntil(b'\n') except asyncio.exceptions.IncompleteReadError as e: if int(str(e).split(" ")[0]) == 0: @@ -60,93 +55,9 @@ async def handle_client (reader, writer): if len(incoming) == 0: break buffer = incoming.replace(b'\r', b'').replace(b'\n', b'') - if http_request: - if len(buffer) == 0: - if http_request[1] == b"/": - with open("index.html", "rb") as index: - if http_request[0] != b"GET": - writer.write(b'HTTP/1.0 405 Method Not Allowed\r\nContent-Type: text/plain\r\n\r\n405 Method Not Allowed. Try GET.\r\n') - break - writer.write(b'HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n') - writer.write(index.read()) - break - elif http_request[1] == b"/python": - if http_request[0] != b"POST": - writer.write(b'HTTP/1.0 405 Method Not Allowed\r\nContent-Type: text/plain\r\n\r\n405 Method Not Allowed. Try POST.\r\n') - break - if b'content-length' not in http_headers.keys(): - writer.write(b'HTTP/1.0 411 Length Required\r\nContent-Type: text/plain\r\n\r\n411 Length Required.\r\n') - break - post_body = None - try: - post_body = await reader.read(int(http_headers.get(b'content-length').decode())) - except Exception as e: - writer.write(b'HTTP/1.0 400 Bad Request\r\nContent-Type: text/plain\r\n\r\nBad request. ' + str(e).encode() + b"\r\n") - raise e - break - try: - writer.write(b"HTTP/1.0 200 OK\r\nContent-Type: application/json\r\n\r\n" + eval(post_body)) - break - except Exception as e: - writer.write(b"HTTP/1.0 500 Internal Server Error\r\nContent-Type: text/plain\r\n\r\n" + str(e).encode() + b"\r\n") - raise e - break - elif http_request[1] == b"/sql": - if http_request[0] != b'POST': - writer.write(b'HTTP/1.0 405 Method Not Allowed\r\nContent-Type: text/plain\r\n\r\n405 Method Not Allowed. Try POST.\r\n') - break - if b'content-length' not in http_headers.keys(): - writer.write(b'HTTP/1.0 411 Length Required\r\nContent-Type: text/plain\r\n\r\n411 Length Required.\r\n') - break - post_body = None - try: - post_body = (await reader.read(int(http_headers.get(b'content-length').decode()))).decode() - except Exception as e: - writer.write(b'HTTP/1.0 400 Bad Request\r\nContent-Type: text/plain\r\n\r\nBad request. ' + str(e).encode() + b"\r\n") - raise e - break - try: - rows = [] - for row in db.execute(post_body): - columns = [] - for column in row: - if type(column) == bytes: - columns.append(column.decode("utf-8", errors="surrogateescape")) - else: - columns.append(column) - rows.append(columns) - response = json.dumps(rows, ensure_ascii=False, indent=1) - except Exception as e: - writer.write(b'HTTP/1.0 500 Internal Server Error\r\nContent-Type: text/plain\r\n\r\n500 Internal Server Error. ' + str(e).encode() + b"\r\n") - raise e - break - else: - writer.write(b'HTTP/1.0 200 OK\r\nContent-Type: application/json\r\n\r\n' + response.encode()) - break - else: - writer.write(b'HTTP/1.0 404 Not Found\r\nContent-Type: text/plain\r\n\r\n404 Not Found') - break - splitbuf = buffer.split(b': ') - headername = splitbuf.pop(0).lower() - http_headers[headername] = b': '.join(splitbuf) - continue - if linenumber == 0 and re.match(b'[A-Z]+ [/A-Za-z0-9?&=%+~]+ HTTP/[0-9.]+', buffer): - http_request = buffer.split(b' ') - continue - 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.split(b' ')[0]) == None: writer.write(b'BAD_FLAG\n') continue - # SUBMISSION LINE FORMAT: "flag teamnumber roundnumber service any other context" flag = buffer.split(b' ')[0].decode() context = b' '.join(buffer.split(b' ')[1:]) try: diff --git a/iv/orodja/napad/templates/frontend.html b/iv/orodja/napad/templates/frontend.html new file mode 100644 index 0000000..8f0389d --- /dev/null +++ b/iv/orodja/napad/templates/frontend.html @@ -0,0 +1,58 @@ +<meta name=viewport content='width=device-width, initial-scale=1.0'> +<meta charset=utf-8 /> +<style> +table, td, tr, th { + border: 1px solid red; +} +</style> +<title>napad/nadzor.py</title> +<h1>napad/nadzor.py</h1> +<table> +<tr> +<th> +ime podatka +</th> +<th> +vrednost +</th> +</tr> +<tr><td>čas zadnje ACCEPTED zastavice</td><td id=lastaccepteddate></td></tr> +<tr><td>neposlanih zastavic</td><td id=notsentcount></td></tr> +</table> +<div id=groupbymsg></div> +<label for=customquery> +<h3>custom query</h3> +</label> +<textarea cols=80 id=customquery placeholder="select * from flags limit 10">select flag,date,msg from flags limit 10</textarea> +<div id=customqueryres></div> +<script> +function htmltablefromquery (rows) { + let table = document.createElement("table"); + for (let i = 0; i < rows.length; i++) { + let tr = document.createElement("tr"); + for (let j = 0; j < rows[i].length; j++) { + let td = document.createElement("td"); + td.innerText = rows[i][j]; + tr.appendChild(td); + } + let td = document.createElement("td"); + td.innerText = rows[i][1]; + table.appendChild(tr); + } + return table; +} +async function refreshview () { + fetch("sql", {"method": "post", "body": "select count(flag) from flags where sent=0"}).then((r) => {r.json().then((t)=>{notsentcount.innerText = t[0][0]})}); + let msgskip = 6; // 36 v dejanski igri + fetch("sql", {"method": "post", "body": "select substr(msg, " + msgskip + "),count(substr(msg, " + msgskip + ")) from flags group by substr(msg, " + msgskip + ")"}).then((r) => {r.json().then((rows)=>{ + groupbymsg.innerHTML = ""; + groupbymsg.appendChild(htmltablefromquery(rows)); + })}); + fetch("sql", {"method": "post", "body": "select submitted from flags where status='ACCEPTED' order by submitted desc limit 1"}).then((r) => {r.json().then((t)=>{lastaccepteddate.innerText = t[0][0]})}); + fetch("sql", {"method": "post", "body": customquery.value}).then((r) => {r.json().then((rows)=>{ + customqueryres.innerHTML = ""; + customqueryres.appendChild(htmltablefromquery(rows)); + })}); +} +setInterval(refreshview, 5555); +</script> |