-
Notifications
You must be signed in to change notification settings - Fork 0
๐ก๏ธ Sentinel: [MEDIUM] hmac.compare_digest์์์ ์ฒ๋ฆฌ๋์ง ์์ ์์ธ๋ก ์ธํ DoS ์ทจ์ฝ์ ์์
#386
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| from fastapi import Request | ||
| from starlette.testclient import TestClient | ||
| from saas_web import app | ||
| import os | ||
| from unittest.mock import patch | ||
|
|
||
| with patch.dict(os.environ, {"CODEC_CARVER_API_KEYS": "secret-key"}): | ||
| client = TestClient(app) | ||
| req = client.build_request("POST", "/shrink", headers=[(b"x-api-key", b"\xff")]) | ||
| response = client.send(req) | ||
| print(response.status_code) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import hmac | ||
|
|
||
| provided_key = "\xff" | ||
| configured_keys = ["secret-key"] | ||
|
|
||
| try: | ||
| any(hmac.compare_digest(provided_key.encode("utf-8"), key.encode("utf-8")) for key in configured_keys) | ||
| print("UTF-8 works") | ||
| except Exception as e: | ||
| print(repr(e)) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| from fastapi import FastAPI, Request | ||
| from fastapi.testclient import TestClient | ||
| from fastapi.responses import JSONResponse | ||
| import hmac | ||
| import os | ||
|
|
||
| app = FastAPI() | ||
|
|
||
| @app.middleware("http") | ||
| async def require_api_key(request: Request, call_next): | ||
| configured_keys = ["secret-key"] | ||
| provided_key = request.headers.get("x-api-key", "") | ||
| try: | ||
| if not any( | ||
| hmac.compare_digest(provided_key, key) for key in configured_keys | ||
| ): | ||
| return JSONResponse(status_code=401, content={"error": "Invalid"}) | ||
| except Exception as e: | ||
| return JSONResponse(status_code=500, content={"error": repr(e)}) | ||
| return await call_next(request) | ||
|
|
||
| @app.get("/") | ||
| def read_root(): | ||
| return {"Hello": "World"} | ||
|
|
||
| client = TestClient(app, raise_server_exceptions=False) | ||
| import requests | ||
| # Using requests to bypass httpx's strict ASCII header checks to see if the server handles it | ||
| try: | ||
| response = requests.get("http://localhost:8000/", headers={"x-api-key": "์๋ "}) | ||
| print(f"Status Code: {response.status_code}") | ||
| except Exception as e: | ||
| print(e) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| from fastapi import FastAPI, Request | ||
| from fastapi.responses import JSONResponse | ||
| import hmac | ||
| import os | ||
| import uvicorn | ||
| import threading | ||
| import time | ||
| import socket | ||
|
|
||
| app = FastAPI() | ||
|
|
||
| @app.middleware("http") | ||
| async def require_api_key(request: Request, call_next): | ||
| configured_keys = ["secret-key"] | ||
| provided_key = request.headers.get("x-api-key", "") | ||
| try: | ||
| if not any( | ||
| hmac.compare_digest(provided_key, key) for key in configured_keys | ||
| ): | ||
| return JSONResponse(status_code=401, content={"error": "Invalid"}) | ||
| except Exception as e: | ||
| print(f"Server caught exception: {repr(e)}") | ||
| return JSONResponse(status_code=500, content={"error": repr(e)}) | ||
| return await call_next(request) | ||
|
|
||
| @app.get("/") | ||
| def read_root(): | ||
| return {"Hello": "World"} | ||
|
|
||
| def run_server(): | ||
| uvicorn.run(app, host="127.0.0.1", port=8000, log_level="error") | ||
|
|
||
| t = threading.Thread(target=run_server, daemon=True) | ||
| t.start() | ||
| time.sleep(1) | ||
|
|
||
| # Manually send a raw HTTP request with non-ASCII header | ||
| req = b"GET / HTTP/1.1\r\nHost: localhost:8000\r\nx-api-key: \xff\r\n\r\n" | ||
| s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||
| s.connect(("127.0.0.1", 8000)) | ||
| s.sendall(req) | ||
| resp = s.recv(4096) | ||
| print(resp.decode("latin-1")) | ||
| s.close() | ||
|
Comment on lines
+1
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ฉบ Stability & Availability | ๐ Major | โก Quick win ์คํ ์ฌํ ํ์ผ์ ํ ์คํธ ์์ง ๊ฒฝ๋ก์์ ์ ๊ฑฐํ์ธ์. ์ด ํ์ผ๋ค์
๐งฐ Tools๐ช Ruff (0.16.1)[warning] 21-21: Do not catch blind exception: (BLE001) [warning] 22-22: Use explicit conversion flag Replace with conversion flag (RUF010) ๐ Affects 8 files
๐ค Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import hmac | ||
| try: | ||
| hmac.compare_digest("hello".encode('utf-8'), "์๋ ".encode('utf-8')) | ||
| print("Bytes work") | ||
| except Exception as e: | ||
| print(f"Exception: {repr(e)}") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| import hmac | ||
| try: | ||
| hmac.compare_digest("hello", "world") | ||
| print("ASCII works") | ||
| hmac.compare_digest("hello", "์๋ ") | ||
| except Exception as e: | ||
| print(f"Exception: {repr(e)}") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| from fastapi import FastAPI, Request | ||
| from fastapi.testclient import TestClient | ||
| from fastapi.responses import JSONResponse | ||
| import hmac | ||
| import os | ||
|
|
||
| app = FastAPI() | ||
|
|
||
| @app.middleware("http") | ||
| async def require_api_key(request: Request, call_next): | ||
| configured_keys = ["secret-key"] | ||
| provided_key = request.headers.get("x-api-key", "") | ||
| if not any( | ||
| hmac.compare_digest(provided_key, key) for key in configured_keys | ||
| ): | ||
| return JSONResponse(status_code=401, content={"error": "Invalid"}) | ||
| return await call_next(request) | ||
|
|
||
| @app.get("/") | ||
| def read_root(): | ||
| return {"Hello": "World"} | ||
|
|
||
| client = TestClient(app) | ||
| response = client.get("/", headers={"x-api-key": "์๋ "}) | ||
| print(f"Status Code: {response.status_code}") | ||
| if response.status_code == 500: | ||
| print("Vulnerability confirmed!") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| from fastapi import FastAPI, Request | ||
| from fastapi.testclient import TestClient | ||
| from fastapi.responses import JSONResponse | ||
| import hmac | ||
| import os | ||
|
|
||
| app = FastAPI() | ||
|
|
||
| @app.middleware("http") | ||
| async def require_api_key(request: Request, call_next): | ||
| configured_keys = ["secret-key"] | ||
| provided_key = request.headers.get("x-api-key", "") | ||
| if not any( | ||
| hmac.compare_digest(provided_key, key) for key in configured_keys | ||
| ): | ||
| return JSONResponse(status_code=401, content={"error": "Invalid"}) | ||
| return await call_next(request) | ||
|
|
||
| @app.get("/") | ||
| def read_root(): | ||
| return {"Hello": "World"} | ||
|
|
||
| client = TestClient(app, raise_server_exceptions=False) | ||
| response = client.get("/", headers={"x-api-key": b"\xff".decode("latin-1")}) | ||
| print(f"Status Code: {response.status_code}") | ||
| if response.status_code == 500: | ||
| print("Vulnerability confirmed!") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๐ Security & Privacy | ๐ Major | ๐๏ธ Heavy lift
๋ฐํ์ API ํค ์์ค๋ฅผ credential registry/KV๋ก ์ด์ ํ์ธ์.
saas_web.py์get_configured_api_keys()๋ Line 97์์CODEC_CARVER_API_KEYS๋ฅผ ์ง์ ์ฝ์ต๋๋ค. ์ ์ธ์ฆ ํ๋ฆ๋ ์ด ๊ฐ์ ์ฌ์ฉํฉ๋๋ค. ํ๊ฒฝ ๋ณ์์์ ์ง์ ์ฝ๋ ๋ฐฉ์์ ํค ํ์ , ์ ๊ทผ ์ ์ด, ๊ฐ์ฌ ๊ฒฝ๋ก๋ฅผ ์ฐํํฉ๋๋ค.saas_web.py#L117-L117:get_configured_api_keys()๊ฐ credential registry/KV์์ ๋ฐํ์ ํค๋ฅผ ์ฝ๋๋ก ๋ณ๊ฒฝํ์ธ์.tests/test_saas_web.py#L642-L652: ํ๊ฒฝ ๋ณ์๋ฅผ ํจ์นํ์ง ๋ง๊ณ , ํ ์คํธ์ฉ registry/KV์ ํค๋ฅผ ์ค์ ํ์ธ์.As per coding guidelines, โ
saas_web.pymust source runtime API keys, database credentials, endpoints, and other secrets from the credential registry/KV rather than directly from environment variables.โ๐ Affects 2 files
saas_web.py#L117-L117(this comment)tests/test_saas_web.py#L642-L652๐ค Prompt for AI Agents
Source: Coding guidelines