This commit is contained in:
frogolonso
2026-08-26 12:53:27 +03:00
parent 12b1d02f39
commit 0d1b49ac1b
7 changed files with 133 additions and 34 deletions
+27 -7
View File
@@ -1,5 +1,5 @@
#!/usr/bin/python3
from flask import Flask, request, redirect
from flask import Flask, request, redirect, abort
import requests as r
app = Flask(__name__)
proxy_node_port = 5000
@@ -10,29 +10,49 @@ static_node = "static:" + str(static_node_port)
logic_node = "logic:" + str(logic_node_port)
content_node = "content:" + str(content_node_port)
# Только реальные страницы. /db/users/pwn.html больше не уходит в content (V1).
ALLOWED_HTML = {"index.html", "login.html", "register.html", "specific.html"}
ALLOWED_API = {"login", "register", "update_personal", "send_msg", "post_comment"}
STATIC_EXT = {"css", "png", "jpg", "ico", "js", "otf", "eot", "svg", "ttf", "woff", "woff2"}
def safe_path(path: str):
if not path or ".." in path or "\\" in path or "\x00" in path:
return False
if path.startswith("db/") or "/db/" in path:
return False
return True
@app.route('/', defaults={'path': 'index.html'})
@app.route('/<path:path>', methods=["GET", "POST"])
def catch_all(path: str):
print(path)
if not safe_path(path):
abort(404)
ext = path.split('.')[-1]
if ext == "html":
resp = r.get(f"http://{content_node}/{path}?{request.full_path.split('?')[1]}", headers=request.headers)
if path not in ALLOWED_HTML:
abort(404)
qs = request.full_path.split('?')[1] if '?' in request.full_path else ''
resp = r.get(f"http://{content_node}/{path}?{qs}", headers=request.headers,
timeout=10)
return resp.content, resp.status_code, resp.headers.items()
if ext in ["css", "png", "jpg", "ico", "js", "otf", "eot", "svg", "ttf", "woff", "woff2"]:
resp = r.get(f"http://{static_node}/{path}", stream=True)
if ext in STATIC_EXT:
resp = r.get(f"http://{static_node}/{path}", stream=True, timeout=5)
headers = [("Cache-Control", "max-age=36000;"), ("Content-Type", resp.headers["Content-Type"])]
return resp.content, resp.status_code, headers
if path.startswith("api/"):
action = path[4:].split("/", 1)[0]
if action not in ALLOWED_API:
abort(404)
if request.method.lower() not in ("get", "post"):
return redirect("/", code=302)
passcall = getattr(r, request.method.lower())
# PATCH V3: allow_redirects=False. requests по умолчанию ХОДИТ по Location,
# который отдаёт logic, а Location раньше строился из заголовка Origin =>
# proxy делал запрос во внутреннюю сеть за атакующего (SSRF).
# Редирект теперь отдаём клиенту, а не отрабатываем сами.
resp = passcall(f"http://{logic_node}/{path[4:]}", json=request.get_json(silent=True),
headers=request.headers, allow_redirects=False)
headers=request.headers, allow_redirects=False, timeout=8)
return resp.content, resp.status_code, resp.headers.items()
return redirect("/", code=302)