From f00362fb786723590d6c457d1af6d5144b817fc1 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 30 Aug 2026 21:30:39 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[CRITICAL]?= =?UTF-8?q?=20=EA=B5=AC=EC=A1=B0=20=EA=B2=80=EC=A6=9D=20=EC=8B=9C=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=EB=88=84=EB=9D=BD?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20DoS=20=EC=B7=A8?= =?UTF-8?q?=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 파일 파싱 엔드포인트에서 구조 검증을 위한 `PdfReader` 호출 시 특정 예외만 처리하여 그 외 예상치 못한 예외(예: `MemoryError`, `TypeError`) 발생 시 500 에러로 인한 DoS 상태 유발 가능성이 존재했습니다. 구조 검증 시 `except Exception:`으로 모든 예외를 안전하게 잡아내고, 예외 내용을 로깅한 뒤 클라이언트에는 415 상태 코드를 반환하도록 개선했습니다. 단위 테스트를 추가하여 415 응답과 올바른 로그가 남는지 확인했습니다. --- .jules/sentinel.md | 5 +++++ src/newsdom_api/main.py | 6 ++++-- tests/test_parse_endpoint.py | 17 +++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 2b5d819c..76f12a23 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -90,3 +90,8 @@ **Vulnerability:** The `_safe_upload_filename` function used `filename.replace`, `PurePosixPath`, and `re.sub` on unbounded client input, making it vulnerable to ReDoS or CPU/memory exhaustion (DoS) when fed extremely long strings. **Learning:** Even fast standard library functions like `PurePosixPath` and string replacements can cause significant lag when chained on strings in the megabytes. String processing operations should always bound their inputs first if the input is untrusted and can be arbitrarily large. **Prevention:** Cap the length of client-provided filename strings early by slicing them (e.g. `filename = filename[-512:]`) before doing more complex string parsing or regex replacements, especially when only the basename suffix is relevant. + +## 2025-03-10 - 구조 검증 시 예외 처리 누락으로 인한 DoS 취약점 방지 +**Vulnerability:** 파일 파싱 엔드포인트에서 `PdfReader` 등의 구조 검증 라이브러리가 손상된 파일에 대해 예상치 못한 예외(`MemoryError`, `TypeError` 등)를 발생시킬 경우 서버가 500 에러를 뱉으며 DoS 상태에 빠질 수 있는 취약점을 발견함. +**Learning:** 파싱 과정 중 발생할 수 있는 모든 예외를 명시적으로 나열하는 것은 현실적으로 불가능하며, 예상 외의 예외 발생 시 서비스 장애로 이어지는 위험이 있음. +**Prevention:** 구조 검증 호출부를 포괄적인 `except Exception:` 블록으로 감싸 500 에러 대신 415 상태 코드로 안전하게 대응하고, 시스템 문제를 파악하기 위해 실제 예외는 로깅하도록 개선함. diff --git a/src/newsdom_api/main.py b/src/newsdom_api/main.py index f61aafc2..be5bef93 100644 --- a/src/newsdom_api/main.py +++ b/src/newsdom_api/main.py @@ -22,7 +22,6 @@ from fastapi.responses import JSONResponse from fastapi.security import HTTPBearer from pypdf import PdfReader -from pypdf.errors import PdfReadError from .config import ( AuthenticationMode, @@ -193,7 +192,10 @@ def _validate_pdf_structure(file_path: Path) -> None: reader = PdfReader(file_path, strict=True) if len(reader.pages) < 1: raise ValueError("PDF has no pages") - except (PdfReadError, RecursionError, ValueError, OverflowError): + except Exception as exc: + LOGGER.error( + "Unhandled exception during PDF structure validation", exc_info=exc + ) raise HTTPException( status_code=415, detail=UNSUPPORTED_MEDIA_DETAIL, diff --git a/tests/test_parse_endpoint.py b/tests/test_parse_endpoint.py index 1491ada0..6fa8a033 100644 --- a/tests/test_parse_endpoint.py +++ b/tests/test_parse_endpoint.py @@ -93,6 +93,23 @@ def test_validate_pdf_structure_rejects_invalid_magic_bytes(tmp_path): assert exc_info.value.status_code == 415 assert exc_info.value.detail == "Unsupported Media Type" + + +def test_validate_pdf_structure_rejects_unhandled_exceptions( + monkeypatch, tmp_path, caplog +): + def reject_pdf(_stream, *, strict): + raise TypeError("malformed object") + + monkeypatch.setattr("newsdom_api.main.PdfReader", reject_pdf) + + with pytest.raises(HTTPException) as exc_info: + (tmp_path / "test.pdf").write_bytes(b"%PDF-1.4\n%%EOF") + _validate_pdf_structure(tmp_path / "test.pdf") + + assert exc_info.value.status_code == 415 + assert exc_info.value.detail == "Unsupported Media Type" + assert "Unhandled exception during PDF structure validation" in caplog.text assert exc_info.value.__cause__ is None