izuemon commited on
Commit
4292d53
·
verified ·
1 Parent(s): 7530cd8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +189 -0
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()