← 개발 로그 목록

website: 피드 이미지 업로드 매직바이트 검증 추가

/ 3분 분량 / 개발 로그

피드 이미지 업로드 API의 파일 검증이 `Content-Type` 헤더만 보고 있었다는 걸 보안 현황 점검(#403) 중에 발견해서, 파일 내용 자체를 확인하도록 고친 PR이다.

기존 FeedImageService.validate()는 요청에 실려 온 Content-Type이 image/jpeg, image/png, image/webp, image/gif 중 하나인지만 확인하고 통과시키고 있었다. 문제는 이 헤더가 클라이언트가 요청을 만들 때 임의로 지정하는 값이라는 점이다. 실행 파일이든 스크립트든 확장자와 Content-Type만 이미지처럼 꾸미면 검증을 그대로 통과해서 저장소에 올라갈 수 있는 구조였다.

고치는 방법은 단순했다. Content-Type 검사를 없애는 게 아니라, 그 위에 파일 앞부분 바이트를 직접 읽어서 실제 포맷 시그니처(매직바이트)가 맞는지 검사하는 단계를 하나 더 얹었다. JPEG는 FF D8 FF, PNG는 89 50 4E 47 0D 0A 1A 0A, GIF는 GIF8, WEBP는 RIFF로 시작해서 12바이트째부터 WEBP가 나오는 구조라 이 네 가지를 hasValidImageSignature()에서 각각 체크하도록 했다. Content-Type과 매직바이트 둘 다 통과해야 업로드가 되는 구조라, 헤더 위조만으로는 더 이상 뚫리지 않는다.

이 검사를 넣으면서 예상했던 대로 기존 테스트들이 걸렸다. 정상 업로드 테스트들이 MockMultipartFile을 만들 때 내용으로 "data".getBytes() 같은 걸 그냥 넣고 있었는데, 이제는 그게 실제 이미지가 아니라서 검증에서 걸린다. 그래서 포맷별로 진짜 시그니처 바이트를 담은 맵(VALID_IMAGE_BYTES)을 테스트 쪽에 만들어서 기존 픽스처를 전부 교체했다. 그리고 이 PR의 핵심을 실제로 검증하는 테스트도 하나 추가했다—Content-Type은 image/png라고 우기지만 실제 바이트는 "not-an-image"인 경우가 여전히 막히는지 확인하는 케이스다. 컨트롤러 테스트 쪽도 마찬가지로 PNG 시그니처를 넣어주는 식으로 손봤다.

커밋 이력을 보면 두 번째 커밋이 "Potential fix for pull request finding"이라는 메시지로 Copilot Autofix가 올린 걸로 되어 있다. 첫 커밋에서 구현과 테스트를 다 끝내놓은 상태였는데, 이후 자동화된 보안 스캔 쪽에서 뭔가 지적한 게 있어서 그에 대한 수정이 추가로 들어간 흐름으로 보인다.

이 PR은 병합되지 않고 닫힌 상태다. 방향 자체는 맞다고 생각하는데, 왜 머지되지 않았는지는 이 데이터만으로는 알 수 없다. 다만 매직바이트 검사가 파일 포맷의 앞부분만 확인하는 방식이라 완전한 콘텐츠 검증은 아니라는 한계는 남아있고, 나중에 이 부분을 다시 다룬다면 이미지 디코딩까지 시도해보는 방식이나 별도 안티바이러스 스캔 연동 같은 것도 검토해볼 만하다는 생각이 든다.