제발 API 응답값 범위를 과도하게 잡지 말라고

Share
제발 API 응답값 범위를 과도하게 잡지 말라고

평화롭게 진단 보고서 쓰고 있던 와중에 다른 파트 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들의 영향도까지 한 번 더 체크하는 습관을 들여야겠다.

Read more

AI가 공격하는 초고속 시대, 방어의 진짜 핵심은 '브레이크'? 통제된 자율성이 이끄는 보안 3.0

AI가 공격하는 초고속 시대, 방어의 진짜 핵심은 '브레이크'? 통제된 자율성이 이끄는 보안 3.0

최근 생성형 AI와 자율형 에이전트(Agentic AI) 기술이 급격히 발전하면서 사이버 위협 지형은 전례 없는 대격변을 맞이하고 있다. 공격자들은 AI를 활용해 악성코드 제작, 취약점 탐색, 피싱 공격 과정을 자동화하며 머신 속도로 타깃을 정밀 공격하고 있다. 반면 이를 막아내야 하는 방어 측의 현실은 여전히 수동 조사와 파편화된 매뉴얼 작업에 의존하고 있다.

By m1ngiunus
"주님, 제 삶을 도와주세요"라는 기도가 당신의 삶을 흔드는 이유

"주님, 제 삶을 도와주세요"라는 기도가 당신의 삶을 흔드는 이유

열심에 비해 늘 불안하고 흔들리는 당신에게 매일 간절하게 기도하고 삶의 자리에서 최선을 다해 노력하는데도, 왜 우리의 삶은 늘 작은 바람에도 쉽게 흔들리고 불안한 걸까? 많은 신앙인들이 다급한 순간이나 난감한 형편에 처할 때 "주님, 제 형편을 좀 바꿔주세요", "주님, 저를 좀 도와주시고 저와 함께해 주세요"라고 기도한다.

By m1ngiunus