From fda4eb4f383ffeaf038a5734d6a10a5a2e310517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20Luka=20=C5=A0ijanec?= Date: Tue, 3 Sep 2024 01:51:44 +0200 Subject: napad now works --- iv/orodja/napad/config | 45 ++++++++++----- iv/orodja/napad/exploit.sh | 130 +++++++++++++++++++++++++++++++----------- iv/orodja/napad/submission.py | 47 +++++++++------ iv/orodja/napad/template.py | 10 ++++ 4 files changed, 170 insertions(+), 62 deletions(-) create mode 100755 iv/orodja/napad/template.py (limited to 'iv/orodja/napad') diff --git a/iv/orodja/napad/config b/iv/orodja/napad/config index c71c9bd..dc165ce 100644 --- a/iv/orodja/napad/config +++ b/iv/orodja/napad/config @@ -4,14 +4,17 @@ # ========================== # ========= COMMON ========= -export FLAG_REGEX="^[A-Z0-9]{31}=$" export SUBMISSION_PORT=21502 # ========================== # ======= EXPLOIT.SH ======= +# This regex is used to grep -Eo flags from stdout of exploits before submitting them +export FLAG_REGEX_SEARCH="[A-Za-z0-9]{31}=" + # Where can exploit.sh find submission.py. Port is a common setting. -export SUBMISSION_HOST=k.4a.si +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 @@ -20,41 +23,56 @@ export ROUND_DURATION=120 export GAME_START=2024-09-01T07:00:00 # Team numbers to attack -export GAME_TEAMS={0..42} +export GAME_TEAMS="2 69" +###export GAME_TEAMS={0..10} # Flag IDs URL -export game_flag_ids_url() +game_flag_ids_url() { - echo http://10.10.0.1:8081/flagIds?service=$1&team=$2&round=$3 + echo http://splet.4a.si/dir/flagids.txt + ### echo "http://10.10.0.1:8081/flagIds?service=$1&team=$2&round=$3" } export -f game_flag_ids_url # Target IP from ID -export game_target_ip() +game_target_ip() { - echo 10.60.$1.1 + echo 10.69.69.$1 + ### echo 10.60.$1.1 } export -f game_target_ip # NOP TEAM ID export GAME_NOP_TEAM=0 -# For how many rounds are flags valid at a time? -# It doesn't make sense for this to be less than 1. +# 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 # Function exploit.sh should call on errors. # Args: service team pwd usr@pc message # 1 2 3 4 5 -export exploit_error_handler() +exploit_error_handler() { - echo "[exploit.sh] ERROR $1" - notify-send "exploit.sh ERROR" "$5" --urgency critical + notify-send --version > /dev/null && notify-send "exploit.sh ERROR" "$5" --urgency critical } +export -f exploit_error_handler + +# Max exploit execution time +export EXPLOIT_TIMEOUT=5 # ========================== # ====== SUBMISSION.PY ===== +# This regex is used to verify flags before storing them +# It can be .*, no problem, just make sure you're then not sending invalid flags +# to submission TCP -- you shouldn't anyways, as submission expects flags neatly +# line by line, it will not clean up random bullshit. +# Don't just send exploit stdout to submission, use exploit.sh! +export FLAG_REGEX_MATCH="^[A-Z0-9]{31}=$" + +# Where to store flags -- sqlite3 db export SUBMISSION_DB=flags.db # How much flags to send in one request. @@ -64,7 +82,8 @@ export SUBMISSION_DB=flags.db export SUBMISSION_MAX_FLAGS=2560 # PUT request, ECSC 2024 AD style -export SUBMISSION_URL=http://10.10.0.1:8080/flags +export SUBMISSION_URL=http://z.4a.si/dir/submit.php +### 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. diff --git a/iv/orodja/napad/exploit.sh b/iv/orodja/napad/exploit.sh index 729bb68..b482c06 100755 --- a/iv/orodja/napad/exploit.sh +++ b/iv/orodja/napad/exploit.sh @@ -1,84 +1,150 @@ #!/bin/sh if [ x$1 = x ] then -echo >&2 < [team=$GAME_NOP_TEAM] # runs exploit once - $0 loop # runs an exploit in a loop once per round +cat >&2 < [args ...] Subcommands: + once [team=$GAME_NOP_TEAM] # runs exploit once + loop # once per team per round, waits for next round 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: 10.1.2.3 '{"user": "root", "pass": "hunter2"}' -Flag IDs are also available in the environment as variables FLAG_ID_: - {"user": "root", "pass": "hunter2"} will be in environment as vars - FLAG_ID_user=root and FLAG_ID_pass=hunter2 +It is called for every target with the following environment variables: + TARGET_IP: target IP address (uses game_target_ip from config) + TARGET_EXTRA: Flag IDs JSON object (uses game_flag_ids_url in config) + FLAG_ID_: Every JSON value from flag IDs individually +Example environment is therefore: + TARGET_IP=1.1.1.1 TARGET_EXTRA='{"a": "1", "b": "2"}' FLAG_ID_a=1 FLAG_ID_b=2 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. +Exploits are not run 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`. + to get the current round id. Check this on http://time.is Configuration values are also available in environment of exploits. is the name of the service (used for getting flag IDs) -If the env. var EXPLOIT_STDOUT is set, stdout of exploit will be printed to stderr: - "EXPLOIT_STDOUT=1 $0 loop " will also print stdout to term -If the env. var EXPLOIT_LOOP_ONCE is set (only valid for loop mode), looping will - stop after looping through all valid rounds and teams: - "EXPLOIT_LOOP_ONCE=1 $0 loop " won't wait for next round +Set the following environment variables to alter behaviour: + EXPLOIT_STDOUT=1: stdout of exploit will be printed to stderr/terminal + EXPLOIT_LOOP_ONCE=1: exit after executing for all valid rounds instead of + 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 EOF exit 1 fi -set -xeuo pipefail -startunix=`date +%s --utc --date $GAME_START` -current=`date +%s --utc` +[ ${EXPLOIT_VERBOSE:-false} = false ] || set -x +set -euo pipefail +current_round_id() +{ # BREAKS WHEN THERE ARE LEAP SECONDS DURING GAME + startunix=`date +%s --utc --date $GAME_START` + current=`date +%s --utc` + echo $((($current-$startunix)/$ROUND_DURATION)) +} if [ ${ROUND_ID:-x} = x ] then - export ROUND_ID=`$((($current-$startunix)/$ROUND_DURATION))` # BREAKS WHEN THERE ARE LEAP SECONDS DURING GAME + export ROUND_ID=`current_round_id` fi subcommand=$1 service=$2 exploit=$3 # tees stdout, collects flags, puts stdout to stderr, prints counts +# args: team exploit_pipe() { stdoutwhere=/dev/null - [ ${EXPLOIT_STDOUT:-x} = x ] && stdoutwhere=/dev/stderr - tee $stdoutwhere | grep -Eo "$FLAG_REGEX" | while read line + [ ! ${EXPLOIT_STDOUT:-false} = false ] && stdoutwhere=/dev/stderr + tee $stdoutwhere | { grep -Eo "$FLAG_REGEX_SEARCH" || :; } | while read -r line do - echo $line `whoami`@`hostname``pwd` $exploit $service - done | nc -v $SUBMISSION_HOST $SUBMISSION_PORT | cut -d\ -f1 | sort | uniq -c | tr $'\n' ' ' | cat /dev/stdin <(echo $'\t<= izkupiček poslanih zastavic') + echo $line $1 $ROUND_ID $service $exploit `whoami`@`hostname``pwd` + done | { nc -N $SUBMISSION_HOST $SUBMISSION_PORT || return $((200+$?)); } | cut -d\ -f1,2 | sort | uniq -c | tr $'\n' ' ' | cat <(printf "team=%-2d round=%d: " $1 $ROUND_ID) /dev/stdin <(echo) >&2 } # args: team round get_flag_ids() { - output_flagids=$(curl --fail-with-body --no-progress-bar `game_flag_ids_url $service $1 $2`) - echo [$0] ERROR: failed to get flag ids: $output_flagids >&2 + set +e + output_flagids=$(curl --fail-with-body --no-progress-meter "`game_flag_ids_url "$service" $1 $2`") + curl_exit_code=$? + set -e + echo $output_flagids + if [ ! $curl_exit_code -eq 0 ] + then + send_error $1 "round=$round failed to get flag ids: $output_flagids" >&2 + return 99 + fi } # args: team message send_error() { echo [$0] ERROR: team=$1: $2 - exploit_error_handler $service $1 `pwd` `whoami`@`hostname` $2 + exploit_error_handler "$service" $1 `pwd` `whoami`@`hostname` $2 } case $subcommand in once) target_team=$GAME_NOP_TEAM - if [ $# -ge 3 ] + if [ $# -ge 4 ] then - target_team=$3 + target_team=$4 fi - timeout $EXPLOIT_TIMEOUT $exploit `game_target_ip $target_team` `get_flag_ids $target_team $ROUND_ID` | expoit_pipe $subcommand + export TARGET_IP=`game_target_ip $target_team` + export TARGET_EXTRA="`get_flag_ids $target_team $ROUND_ID`" + source <(echo "$TARGET_EXTRA" | jq -r 'to_entries|map("export FLAG_ID_\(.key|sub("'"'"'";""))='"'"'\(.value|tostring|sub("'"'"'";"'"'\\\"'\\\"'"'"))'"'"'")|.[]') + set +e + timeout $EXPLOIT_TIMEOUT $exploit | exploit_pipe $target_team exit_code=$? + set -e + if [ $exit_code -gt 200 ] + then + send_error $target_team "submission netcat failed with $(($exit_code-200))" + exit $exit_code + fi if [ ! $exit_code -eq 0 ] && [ ! $exit_code -eq 124 ] then send_error $target_team "$exploit exited with $exit_code" fi + if [ $exit_code -eq 124 ] + then + echo [$0] team=$target_team $exploit timed out >&2 + fi + exit $exit_code ;; loop) - for round in {$ROUND_ID..} + if parallel --version > /dev/null && [ ${EXPLOIT_NOTPARALLEL:-false} = false ] + then + commands_evaluator="parallel --color-failed" + commands_output="/dev/stdout" + have_parallel=true + echo "[$0] using parallel executions (:" >&2 + else + commands_evaluator="cat /dev/stdin" + commands_output="/dev/null" + have_parallel=false + echo "[$0] parallel not found or disabled! zaporedno izvajanje ):" >&2 + fi + round=$(($ROUND_ID-$GAME_VALID_ROUNDS)) + while : do for target_team in $GAME_TEAMS do - ROUND_ID=$round $0 once $target_team + cmd2exec="ROUND_ID=$round $0 once '$service' $exploit $target_team" + if $have_parallel + then + echo $cmd2exec + else + eval $cmd2exec + fi + done | $commands_evaluator > $commands_output + round=$(($round+1)) + we_slept=false + while [ `current_round_id` -lt $round ] + do # oh no we pollin thats ugly af, who cares we have + if [ ! ${EXPLOIT_LOOP_ONCE:-false} = false ] + then + echo [$0] breaking due to EXPLOIT_LOOP_ONCE + break + fi + we_slept=true + sleep 1 # INFINITE CPU POWAH! done + if $we_slept + then # execute exploit at random time instead of at start + sleep $(($RANDOM%$ROUND_DURATION/2)) + fi done ;; esac diff --git a/iv/orodja/napad/submission.py b/iv/orodja/napad/submission.py index 30e8257..fce0a27 100755 --- a/iv/orodja/napad/submission.py +++ b/iv/orodja/napad/submission.py @@ -4,12 +4,12 @@ import asyncio import re import sqlite3 import aiohttp +import traceback 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") -flag_regex = re.compile(os.getenv("FLAG_REGEX", "^[A-Z0-9]{31}=$").encode(), re.ASCII | re.DOTALL | re.VERBOSE) +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: - print("submitter loop") flags_balance = dict() unsent_flags = 0 for flag, team, service in db.execute("SELECT flag, team, service FROM flags WHERE sent == 0 ORDER BY date DESC"): @@ -19,29 +19,39 @@ async def submitter (): unsent_flags += 1 flags = [] while len(flags) < int(os.getenv("SUBMISSION_MAX_FLAGS", "2560")) and unsent_flags > 0: # to zna biti počasno, najdi lepši način - for key in flags_balance.keys(): + for key in [x for x in flags_balance.keys()]: try: zastava = flags_balance[key].pop(0) except IndexError: flags_balance.pop(key) else: flags.append(zastava) + unsent_flags -= 1 if len(flags) == 0: await asyncio.sleep(1) + continue 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() + try: + 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() + except Exception as e: + traceback.print_exc() await asyncio.sleep(int(os.getenv("SUBMISSION_DELAY", "15"))) async def handle_client (reader, writer): while True: - incoming = await reader.readuntil(b'\n') + try: + incoming = await reader.readuntil(b'\n') + except asyncio.exceptions.IncompleteReadError as e: + if int(str(e).split(" ")[0]) == 0: + break + raise e if len(incoming) == 0: break buffer = incoming.replace(b'\r', b'').replace(b'\n', b'') @@ -63,18 +73,21 @@ async def handle_client (reader, writer): context = b' '.join(buffer.split(b' ')[1:]) try: team = int(buffer.split(b' ')[1].decode()) - except (ValueError, UnicodeDecodeError): + except (ValueError, UnicodeDecodeError, IndexError): team = -1 try: runda = int(buffer.split(b' ')[2].decode()) - except (ValueError, UnicodeDecodeError): + except (ValueError, UnicodeDecodeError, IndexError): runda = -1 - service = buffer.split(b' ')[3] try: - db.execute("INSERT INTO flags (flag, team, service, round, context) VALUES (?, ?)", [flag, team, service, runda, context]) + service = buffer.split(b' ')[3] + except IndexError: + service = None + try: + db.execute("INSERT INTO flags (flag, team, service, round, context) VALUES (?, ?, ?, ?, ?)", [flag, team, service, runda, context]) except sqlite3.IntegrityError: status, msg, date, context = [x for x in db.execute("SELECT status, msg, date, context FROM flags WHERE flag=?", [flag])][0] - writer.write(b"OLD_FLAG " + date.encode() + b"\t" + str(status).encode() + b"\t" + str(context).encode() + b"\t" + str(msg).encode() + b"\n") + writer.write(b"OLD_FLAG " + str(status).encode() + b" " + date.encode() + b" " + context + b"\t" + str(msg).encode() + b"\n") else: writer.write(b'NEW_FLAG\n') writer.close() diff --git a/iv/orodja/napad/template.py b/iv/orodja/napad/template.py new file mode 100755 index 0000000..2629fe9 --- /dev/null +++ b/iv/orodja/napad/template.py @@ -0,0 +1,10 @@ +#!/usr/bin/python3 +import os +import requests +import json +target = os.getenv("TARGET_IP") +extra = json.loads(os.getenv("TARGET_EXTRA", "{}")) +with requests.Session() as s: + userid = extra["userid"] + response = s.get(f"http://{target}:80/dir/flags.txt?user={userid}") + print(response.text) -- cgit v1.2.3