> ## Content Index
> Fetch the complete content index at: https://blog.m1ngiunus.me/llms.txt
> Use this file to discover other available public pages before exploring further.

# 제발 API 응답값 범위를 과도하게 잡지 말라고
- URL: https://blog.m1ngiunus.me/please-stop-setting-such-excessively-broad-ranges-for-api-response-values/
- Published: 2026-10-16T05:00:00.000Z
- Updated: 2026-10-20T05:04:53.000Z
- Author: m1ngiunus
- Tags: 보안

평화롭게 진단 보고서 쓰고 있던 와중에 다른 파트 PL님께서 나를 호출하셨다. 갑자기 왜지 하는 마음으로 갔는데 혹시 XXX 프로젝트 기억하냐고 물으신다. 진단을 하긴 했는데 6월달이라 기억이 가물가물했었다. 확인해보니 해당 서비스 내 일부 변경 건이 있어 해당 건에 대해 점검을 진행했었고, 특별히 도출된 취약점이 없어서 양호 보고서가 발행되었었다.

해당 서비스에 대해 다른 파트에서 점검을 진행했는데 내가 점검했던 범위 밖 기능에서 취약점이 발견되었던 것이다. 내가 점검한 건이 아닌데 불려나가니 내가 잘못한 것만 같은 느낌이 들어 억울하긴 하다. 그런데 이 취약점 사례가 API 설계 미스로 실무에서 정말 자주 볼 수 있는 유형이다 보니, 이번 기회에 한 번 정리는 해두면 좋을 것 같아 글을 남겨본다.

---

문제가 발생한 기능은 '비밀번호 찾기'다. 해당 서비스에서는 아이디를 입력해서 계정 존재 여부를 확인받은 후, 연동된 전화번호로 인증번호를 받아 일치할 경우 비밀번호를 재설정하는 플로우였다. 문제는 아이디를 입력하고 확인 버튼을 누를 때 호출되는 API의 응답 값이 심상치가 않았다.

`/login/checkId` API는 POST 방식으로 `id`를 전달받는데, JSON 형태의 응답 본문(`Response Body`)에 이런 정보들이 고스란히 담겨 있었다.

- SHA512로 해시된 비밀번호
- 이름
- 부서
- 전화번호
- 이메일
- 역할(권한)
- 기타 등등...

비밀번호 찾기 페이지는 로그인을 하지 않은 익명의 사용자 누구나 접근할 수 있는 영역이다. 즉, 공격자가 ID만 알고 있으면 해당 기능과 전혀 무관한 개인정보는 물론, 해시된 비밀번호까지 별도의 인증 없이 통째로 열람할 수 있는 상태였던 것이다.

#### 이 취약점의 정체: OWASP API3:2023 (BOPLA)

이 사례는 OWASP API Security Top 10 (2023)의 `API3:2023 - Broken Object Property Level Authorization (BOPLA, 객체 속성 수준의 인가 결함)`에 정확히 부합하는 교과서적인 패턴이다.

OWASP 2019 버전에서는 이 문제가 두 가지 카테고리로 나뉘어 있었다.

1. **API3:2019 Excessive Data Exposure (과도한 데이터 노출)**: 조회 시 불필요한 속성까지 전부 던져주는 문제
2. **API6:2019 Mass Assignment (대량 할당)**: 수정/생성 시 허용되지 않은 속성(예: `role`, `isAdmin`)까지 파라미터로 받아 DB에 반영해 버리는 문제

2023년 개정판에서는 이 둘을 하나로 묶어 BOPLA(API3:2023)로 재정의했다. 본질적으로 두 문제 모두 "클라이언트가 특정 엔드포인트나 객체에 접근할 권한이 있더라도, 그 객체 내부의 세부 속성(Property/Field)을 읽거나 쓸 권한이 있는지 검증하지 않았다"는 공통 원인을 공유하기 때문이다.

이번 케이스는 BOPLA 중에서도 전형적인 읽기 관점의 결함(기존 Excessive Data Exposure)에 해당한다.

#### 개발자는 왜 이런 실수를 범할까?

실무에서 이런 취약점이 발생하는 근본적인 이유는 보통 두 가지다.

1. **ORM Entity의 무지성 직렬화 (엔티티 직접 노출)** 백엔드 개발 시 DB 테이블과 1:1 매핑된 `User` 엔티티 객체를 조회한 뒤, 별도의 가공 없이 Jackson이나 FastJSON 같은 직렬화 라이브러리를 통해 객체 통째로 `Response`에 던져버리는 경우다.
2. **"화면 제어 = 보안"이라는 착각**" 프론트엔드에서는 아이디 중복 여부나 마스킹된 전화번호만 화면에 뿌리니까 괜찮겠지"라고 생각하는 안일함이다. 하지만 웹/모바일 클라이언트는 공격자가 완벽하게 통제할 수 있는 영역이다. 브라우저 개발자 도구(F12)나 Burp Suite 같은 프록시 도구를 켜는 순간, 화면에 렌더링되지 않은 백엔드 응답 전체가 적나라하게 드러난다.

#### 공격 관점에서의 파급력

단순히 "정보가 좀 더 보인다" 수준으로 치부할 문제가 아니다.

- **대량 정보 스크래핑(Data Scraping)**: 로그인 전 API이므로 Rate Limit이나 캡차(CAPTCHA)가 걸려있지 않다면, 일반적인 ID 패턴을 브루트포싱하여 전사 임직원의 이름, 부서, 이메일, 전화번호, 권한 체계를 손쉽게 덤프할 수 있다.
- **정교한 스피어 피싱(Spear Phishing)**: 특정 부서의 직책자 정보를 정확히 타깃팅하여 "OOO 부서 OOO 님, 비밀번호 재설정이 필요합니다" 형태의 초개인화 피싱 공격으로 연계할 수 있다.
- **오프라인 해시 크래킹**: SHA512는 안전한 단방향 해시로 취급되지만, 키 스트레칭(PBKDF2, bcrypt, Argon2 등)이 적용되지 않은 단순 해시라면 GPU를 활용한 사전 대입(Dictionary Attack)이나 레인보우 테이블 공격에 매우 취약하다.

#### 조치 방안: 어떻게 막아야 할까?

해결책은 생각보다 명확하지만, 개발 프로세스 전반에 원칙이 자리 잡아야 한다.

- **최소 권한의 Response DTO(Data Transfer Object) 설계** 엔드포인트마다 화면이나 비즈니스 로직에 **반드시 필요한 최소한의 데이터만 반환하는 전용 DTO**를 만들어야 한다. 아이디 확인 API라면 단순히 `{ "isExist": true }`나 본인 확인용 마스킹 전화번호(`010-****-1234`)만 담은 객체를 반환하는 것이 정석이다.
- **민감 속성 직렬화 제외** 비밀번호 해시, Salt, 권한 정보 등은 비즈니스 로직상 절대 클라이언트에 내려가서는 안 된다. 프레임워크 수준에서 `@JsonIgnore` 처리하거나, DTO 매핑 시 아예 매핑 대상에서 제외해야 한다.
- **응답 스키마 유효성 검증(Response Validation)** Swagger/OpenAPI Spec 등을 통해 API 응답 규격을 명시하고, 정의되지 않은 속성이 응답에 포함되어 나가는 경우 빌드/테스트 단계에서 실패하도록 CI/CD 파이프라인에 검증 체계를 두는 것도 좋은 방법이다.

진단 범위(Scope) 밖에서 터진 일이라 초반엔 억울한 마음이 컸지만, 한편으로는 API 기반 환경에서 '프로퍼티 레벨의 인가'가 왜 강조되는지 다시 한번 실감하게 된 계기였다. 점검 스코프를 정의할 때 기능 단위뿐 아니라 공통으로 엮여 있는 비인증 API들의 영향도까지 한 번 더 체크하는 습관을 들여야겠다.