From ed4d5fd30fa4891a8aa27a6f5c47d60d195a892f Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Wed, 19 Aug 2026 17:19:15 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[HIGH]=20?= =?UTF-8?q?=ED=97=A4=EB=8D=94=EB=A5=BC=20=ED=86=B5=ED=95=9C=20DoS=20?= =?UTF-8?q?=EC=B7=A8=EC=95=BD=EC=A0=90=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * `hmac.compare_digest`에서 문자열을 바이트(`.encode('utf-8')`)로 인코딩하여 비교하도록 수정했습니다. * `x-api-key` 헤더를 통해 non-ASCII 문자를 전송하면 `hmac.compare_digest`에서 500 서버 에러(TypeError)가 발생하여 서비스 거부 공격(DoS)에 악용될 수 있는 취약점을 해결했습니다. * 테스트 파일 `test_saas_web.py`에 해당 취약점을 확인하고 예외가 발생하지 않는지 검증하는 테스트를 추가했습니다. --- .jules/sentinel.md | 4 ++++ saas_web.py | 2 +- tests/test_saas_web.py | 25 +++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 9c9d083b..01e5e793 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -65,3 +65,7 @@ **Vulnerability:** Path traversal in `media_shrinker.py` via unresolved `..` segments or symlink escapes before deriving conversion output paths. **Learning:** `Path.relative_to()` is only a lexical containment check unless both the source and root have first been resolved into canonical absolute paths. Relative paths and symlinks can otherwise bypass root-boundary assumptions. **Prevention:** Resolve both source and root once, reject sources outside the resolved root with a sanitized `MediaShrinkerError`, and derive `rel_source` from the resolved paths before planning outputs. +## 2024-10-24 - hmac.compare_digest TypeError 취약점 해결 +**취약점:** `hmac.compare_digest` 함수가 non-ASCII 문자를 포함한 문자열을 비교할 때 `TypeError`를 발생시키는 것을 발견했습니다. 이는 악의적인 사용자가 헤더에 이러한 문자를 주입하여 500 서버 에러(DoS 공격)를 유발할 수 있습니다. +**학습:** 파이썬의 `hmac.compare_digest` 함수는 인코딩된 바이트 객체가 아닌 일반 문자열(non-ASCII 포함)을 입력받을 경우 타입 에러를 던집니다. 이는 요청을 처리하는 미들웨어 단에서 예외 처리되지 않으면 서버의 비정상적인 동작을 초래합니다. +**예방:** 항상 `hmac.compare_digest`로 값을 비교하기 전 두 문자열을 명시적으로 바이트(`.encode('utf-8')`)로 변환하여 에러 발생을 원천적으로 차단해야 합니다. diff --git a/saas_web.py b/saas_web.py index 63265e94..5b280f05 100644 --- a/saas_web.py +++ b/saas_web.py @@ -114,7 +114,7 @@ async def require_api_key(request: Request, call_next): if configured_keys and not (request.method == "GET" and request.url.path == "/"): provided_key = request.headers.get("x-api-key", "") if not any( - hmac.compare_digest(provided_key, key) for key in configured_keys + hmac.compare_digest(provided_key.encode('utf-8'), key.encode('utf-8')) for key in configured_keys ): return JSONResponse( status_code=401, diff --git a/tests/test_saas_web.py b/tests/test_saas_web.py index 3b57e033..4139540b 100644 --- a/tests/test_saas_web.py +++ b/tests/test_saas_web.py @@ -1222,6 +1222,31 @@ def test_video_content_type_accepted_by_validator(self): ) ) + def test_auth_unicode_encode_error(self): + import os + from starlette.requests import Request + import asyncio + + async def call_next(request): + return {"status": "ok"} + + os.environ["CODEC_CARVER_API_KEYS"] = "valid_key" + scope = { + 'type': 'http', + 'method': 'POST', + 'path': '/shrink', + 'headers': [(b'x-api-key', 'invalid_key😀'.encode('utf-8'))], + 'query_string': b'', + 'client': ('127.0.0.1', 12345), + 'server': ('127.0.0.1', 80), + } + request = Request(scope) + try: + response = asyncio.run(saas_web.require_api_key(request, call_next)) + self.assertEqual(response.status_code, 401) + finally: + del os.environ["CODEC_CARVER_API_KEYS"] + if __name__ == "__main__": unittest.main()