Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
| 1 |
from flask import Flask, request, jsonify, redirect, Response, abort, send_from_directory
|
|
|
|
|
|
|
| 2 |
import os
|
| 3 |
import requests
|
| 4 |
import subprocess
|
|
@@ -846,6 +848,193 @@ def proxy_chrome_gw(path):
|
|
| 846 |
}
|
| 847 |
)
|
| 848 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 849 |
if __name__ == "__main__":
|
| 850 |
if os.environ.get("WERKZEUG_RUN_MAIN") != "true":
|
| 851 |
start_processes()
|
|
|
|
| 1 |
from flask import Flask, request, jsonify, redirect, Response, abort, send_from_directory
|
| 2 |
+
from flask import make_response, stream_with_context
|
| 3 |
+
|
| 4 |
import os
|
| 5 |
import requests
|
| 6 |
import subprocess
|
|
|
|
| 848 |
}
|
| 849 |
)
|
| 850 |
|
| 851 |
+
# ------------------------------------------------------------------
|
| 852 |
+
# 設定
|
| 853 |
+
# ------------------------------------------------------------------
|
| 854 |
+
TARPIT_SECONDS = 8 # 1リクエストあたりの遅延
|
| 855 |
+
MAX_TARPIT = 60 # 最大遅延
|
| 856 |
+
CHUNK_DELAY = 0.4 # チャンク間の遅延
|
| 857 |
+
FAKE_HTML_ON_JSON = True # JSONを期待する相手にHTMLを返す
|
| 858 |
+
|
| 859 |
+
|
| 860 |
+
# ------------------------------------------------------------------
|
| 861 |
+
# ユーティリティ
|
| 862 |
+
# ------------------------------------------------------------------
|
| 863 |
+
def weird_json():
|
| 864 |
+
"""
|
| 865 |
+
構文的にほぼ正しいがパーサが嫌がるJSONを返す。
|
| 866 |
+
- 制御文字
|
| 867 |
+
- 深いネスト
|
| 868 |
+
- サロゲートペアの片割れ
|
| 869 |
+
- NaN / Infinity
|
| 870 |
+
- BOM
|
| 871 |
+
- 途中で切れた文字列風
|
| 872 |
+
"""
|
| 873 |
+
deep = {"a": None}
|
| 874 |
+
cur = deep
|
| 875 |
+
for _ in range(200): # 深いネスト
|
| 876 |
+
cur["a"] = {"a": None}
|
| 877 |
+
cur = cur["a"]
|
| 878 |
+
payload = {
|
| 879 |
+
"ok": True,
|
| 880 |
+
"config": deep,
|
| 881 |
+
"note": "\u0000\u0001\u0002 null byte \u0007",
|
| 882 |
+
"lone_surrogate": "\ud800", # 単独サロゲート
|
| 883 |
+
"nan": float("nan"),
|
| 884 |
+
"inf": float("inf"),
|
| 885 |
+
"invalid_escape": "\\x",
|
| 886 |
+
}
|
| 887 |
+
# わざと json.dumps を通さず、細工した文字列を返す
|
| 888 |
+
raw = (
|
| 889 |
+
"\ufeff" # BOM
|
| 890 |
+
+ json.dumps(payload, ensure_ascii=False, allow_nan=True)
|
| 891 |
+
+ "\x00" # 末尾にNUL
|
| 892 |
+
)
|
| 893 |
+
return raw
|
| 894 |
+
|
| 895 |
+
|
| 896 |
+
def fake_env():
|
| 897 |
+
"""
|
| 898 |
+
.env っぽく見えるが、パーサが混乱する内容。
|
| 899 |
+
引用符が閉じていない、複数行、NUL、制御文字などを混入。
|
| 900 |
+
"""
|
| 901 |
+
lines = [
|
| 902 |
+
"# generated",
|
| 903 |
+
"DB_HOST=localhost",
|
| 904 |
+
'SECRET_KEY="unterminated',
|
| 905 |
+
"API_TOKEN=abc\x00def",
|
| 906 |
+
"MULTILINE=<<EOF",
|
| 907 |
+
"still inside value",
|
| 908 |
+
"EOF",
|
| 909 |
+
"=\x07\x08",
|
| 910 |
+
"PATH=/usr/bin:/bin",
|
| 911 |
+
"\x00",
|
| 912 |
+
"PORT=999999999999999999999999999999",
|
| 913 |
+
]
|
| 914 |
+
return "\n".join(lines) + "\n"
|
| 915 |
+
|
| 916 |
+
|
| 917 |
+
def fake_secrets_toml():
|
| 918 |
+
"""
|
| 919 |
+
Streamlit secrets.toml 風だがTOMLとして壊れている。
|
| 920 |
+
"""
|
| 921 |
+
return (
|
| 922 |
+
"[server]\n"
|
| 923 |
+
'port = "not a number"\n'
|
| 924 |
+
"[[[broken\n"
|
| 925 |
+
'key = "unterminated\n'
|
| 926 |
+
"\x00\n"
|
| 927 |
+
"[aws]\n"
|
| 928 |
+
'secret = """multi\n'
|
| 929 |
+
"line but never closed\n"
|
| 930 |
+
)
|
| 931 |
+
|
| 932 |
+
|
| 933 |
+
# ------------------------------------------------------------------
|
| 934 |
+
# タープット(遅延)ヘルパ
|
| 935 |
+
# ------------------------------------------------------------------
|
| 936 |
+
def tarpit(seconds=None):
|
| 937 |
+
s = min(seconds or random.uniform(1, TARPIT_SECONDS), MAX_TARPIT)
|
| 938 |
+
time.sleep(s)
|
| 939 |
+
|
| 940 |
+
|
| 941 |
+
# ------------------------------------------------------------------
|
| 942 |
+
# ルーティング
|
| 943 |
+
# ------------------------------------------------------------------
|
| 944 |
+
|
| 945 |
+
@app.route("/api/config", methods=["GET", "POST", "HEAD"])
|
| 946 |
+
def api_config():
|
| 947 |
+
tarpit()
|
| 948 |
+
# JSONを期待している相手にHTMLを返す(Content-Typeも嘘をつく)
|
| 949 |
+
if FAKE_HTML_ON_JSON:
|
| 950 |
+
body = "<html><body>not json</body></html>\x00"
|
| 951 |
+
resp = make_response(body, 200)
|
| 952 |
+
resp.headers["Content-Type"] = "application/json" # わざと嘘
|
| 953 |
+
resp.headers["Content-Length"] = str(len(body) + 100) # わざと不一致
|
| 954 |
+
return resp
|
| 955 |
+
|
| 956 |
+
body = weird_json()
|
| 957 |
+
resp = make_response(body, 200)
|
| 958 |
+
resp.headers["Content-Type"] = "application/json; charset=invalid-charset"
|
| 959 |
+
return resp
|
| 960 |
+
|
| 961 |
+
|
| 962 |
+
@app.route("/.env", methods=["GET", "POST", "HEAD"])
|
| 963 |
+
@app.route("/.env.local", methods=["GET", "POST", "HEAD"])
|
| 964 |
+
@app.route("/.env.production", methods=["GET", "POST", "HEAD"])
|
| 965 |
+
@app.route("/.env.development", methods=["GET", "POST", "HEAD"])
|
| 966 |
+
@app.route("/.env.backup", methods=["GET", "POST", "HEAD"])
|
| 967 |
+
def env_files():
|
| 968 |
+
tarpit()
|
| 969 |
+
body = fake_env()
|
| 970 |
+
resp = make_response(body, 200)
|
| 971 |
+
# 本来 env は text/plain だが、わざと octet-stream で返す
|
| 972 |
+
resp.headers["Content-Type"] = "application/octet-stream"
|
| 973 |
+
resp.headers["Content-Disposition"] = 'attachment; filename=".env"'
|
| 974 |
+
return resp
|
| 975 |
+
|
| 976 |
+
|
| 977 |
+
@app.route("/.streamlit/secrets.toml", methods=["GET", "POST", "HEAD"])
|
| 978 |
+
def streamlit_secrets():
|
| 979 |
+
tarpit()
|
| 980 |
+
body = fake_secrets_toml()
|
| 981 |
+
resp = make_response(body, 200)
|
| 982 |
+
resp.headers["Content-Type"] = "application/toml; charset=utf-16"
|
| 983 |
+
return resp
|
| 984 |
+
|
| 985 |
+
|
| 986 |
+
# ------------------------------------------------------------------
|
| 987 |
+
# 汎用フォールバック:遅延ストリームで相手のパーサを待たせる
|
| 988 |
+
# ------------------------------------------------------------------
|
| 989 |
+
@app.route("/<path:anypath>", methods=["GET", "POST", "HEAD", "OPTIONS"])
|
| 990 |
+
def catch_all(anypath):
|
| 991 |
+
"""
|
| 992 |
+
存在しないパスへのアクセスをタープット。
|
| 993 |
+
チャンクを少しずつ、ランダムな長さで送る。
|
| 994 |
+
"""
|
| 995 |
+
def gen():
|
| 996 |
+
# HTTP的には chunked になる
|
| 997 |
+
fake_lines = [
|
| 998 |
+
b"HTTP/1.1 200 OK\r\n",
|
| 999 |
+
b"X-Powered-By: ???\r\n",
|
| 1000 |
+
b'{"status":"ok","data":[',
|
| 1001 |
+
b'{"id":' + str(random.randint(0, 10**9)).encode() + b',"v":"x"},',
|
| 1002 |
+
b"\x00\x01\x02",
|
| 1003 |
+
b"\xff\xfe\xfd", # 不正UTF-8
|
| 1004 |
+
b"]}",
|
| 1005 |
+
b"\r\n\r\n",
|
| 1006 |
+
]
|
| 1007 |
+
for chunk in fake_lines:
|
| 1008 |
+
yield chunk
|
| 1009 |
+
time.sleep(CHUNK_DELAY)
|
| 1010 |
+
# 最後に長い無意味データ
|
| 1011 |
+
for _ in range(20):
|
| 1012 |
+
yield b"A" * 512
|
| 1013 |
+
time.sleep(CHUNK_DELAY)
|
| 1014 |
+
|
| 1015 |
+
return Response(stream_with_context(gen()), status=200,
|
| 1016 |
+
mimetype="application/json")
|
| 1017 |
+
|
| 1018 |
+
|
| 1019 |
+
# ------------------------------------------------------------------
|
| 1020 |
+
# エラーハンドラ:404も500も返さず、タープットに流す
|
| 1021 |
+
# ------------------------------------------------------------------
|
| 1022 |
+
@app.errorhandler(404)
|
| 1023 |
+
def handle_404(e):
|
| 1024 |
+
tarpit(2)
|
| 1025 |
+
body = "not found, but here is some garbage: " + ("X" * 1024) + "\x00"
|
| 1026 |
+
resp = make_response(body, 404)
|
| 1027 |
+
resp.headers["Content-Type"] = "text/html; charset=utf-7"
|
| 1028 |
+
return resp
|
| 1029 |
+
|
| 1030 |
+
|
| 1031 |
+
@app.errorhandler(500)
|
| 1032 |
+
def handle_500(e):
|
| 1033 |
+
tarpit(3)
|
| 1034 |
+
resp = make_response("server error, retry with backoff\x00", 500)
|
| 1035 |
+
resp.headers["Retry-After"] = "999999"
|
| 1036 |
+
return resp
|
| 1037 |
+
|
| 1038 |
if __name__ == "__main__":
|
| 1039 |
if os.environ.get("WERKZEUG_RUN_MAIN") != "true":
|
| 1040 |
start_processes()
|