본문으로 건너뛰기
고급약 40분

API 보안 — OWASP API Security Top 10을 점검 케이스로 옮기기

웹 화면용 Top 10과 API용 Top 10은 목록이 다르다. API에서는 인가 결함과 자원 소비 문제가 압도적으로 앞에 온다. OWASP API Security Top 10(2023)의 열 항목을 하나씩 우리 API의 점검 케이스로 번역하고, BOLA와 BFLA를 구분해 검증하며, 스펙에 없는 엔드포인트(섀도 API)를 찾아내고, 발견을 실제 위험으로 판정하는 기준을 세운다.

  • #APIBoan
  • #OWASP
  • #BOLA
  • #BFLA
  • #섀도API
  • #계약테스트

개념

API용 목록이 따로 있는 이유

OWASP는 웹 애플리케이션 Top 10과 별개로 API Security Top 10 을 낸다. 현재 판은 2023년판 이고 항목 ID는 API1:2023 ~ API10:2023 형식이다. 그 전 판은 2019년판이므로, 사내 문서나 벤더 리포트가 API1:2019를 인용하고 있다면 오래된 매핑을 쓰고 있는 것이다. 두 판 사이에 항목이 바뀌었으므로 번호만 보고 같은 것이라 생각하면 안 된다.

목록이 따로 있는 이유는 API에서 결함의 무게 중심이 다르기 때문이다. 화면이 없으니 XSS의 비중이 줄고, 대신 클라이언트가 보내는 모든 것을 서버가 스스로 판단해야 하는 상황이 늘어난다. 그래서 인가 결함이 1·3·5위를 차지한다.

ID항목한 줄 요약
API1:2023Broken Object Level Authorization (BOLA)객체 단위 소유권 검사 누락 — 남의 리소스 접근
API2:2023Broken Authentication인증 자체의 결함 — 토큰 검증·재설정·무차별 대입
API3:2023Broken Object Property Level Authorization객체의 특정 속성에 대한 읽기/쓰기 권한 누락
API4:2023Unrestricted Resource Consumption요청량·페이지 크기·업로드·과금 자원의 제한 부재
API5:2023Broken Function Level Authorization (BFLA)기능(역할) 단위 인가 누락 — 관리자 기능 호출 성공
API6:2023Unrestricted Access to Sensitive Business Flows업무 흐름 자체의 자동화 남용(매크로 구매, 대량 가입)
API7:2023Server Side Request Forgery (SSRF)서버가 클라이언트가 준 URL을 그대로 요청
API8:2023Security Misconfiguration설정 결함 — CORS, 기본 계정, 디버그 응답, 헤더
API9:2023Improper Inventory Management버전·환경·문서 관리 실패 — 섀도/좀비 API
API10:2023Unsafe Consumption of APIs우리가 호출하는 외부 API의 응답을 무비판적으로 신뢰

API3은 2019년판의 "Excessive Data Exposure"와 "Mass Assignment"가 한 항목으로 합쳐진 것이다. 읽기 방향(응답에 불필요한 필드가 섞여 나감)과 쓰기 방향(요청에 넣으면 안 되는 필드가 반영됨)을 같은 원인으로 본다는 뜻이다. 테스트할 때는 두 방향을 따로 케이스로 만든다.

API1(BOLA)과 API5(BFLA)를 구분해 검증한다

레슨 2의 수평·수직 권한 상승이 여기서 이름을 얻는다.

BOLA (API1) — "이 객체가 내 것인가"를 안 본다
  GET /api/v1/invoices/9821     내 계정으로 호출 → 남의 인보이스가 나온다
 
BFLA (API5) — "이 기능을 쓸 자격이 있는가"를 안 본다
  POST /api/v1/admin/users/13/suspend   일반 계정으로 호출 → 성공한다

두 케이스를 만드는 방법이 다르다.

BOLA 케이스 만들기BFLA 케이스 만들기
출발점식별자를 받는 모든 엔드포인트역할이 갈리는 모든 엔드포인트
필요한 준비서로 다른 소유자의 리소스 2세트최소 권한 계정 + 관리자 API 목록
조작 대상경로 파라미터, 쿼리, 본문의 ID, 배열 안의 IDHTTP 메서드, 경로 접두사, 역할 헤더
자주 빠지는 곳배치/일괄 API의 ID 배열, 파일 다운로드 경로문서에 없는 내부 관리 엔드포인트

BFLA에서 메서드를 바꿔 보는 것 을 잊기 쉽다. GET /api/v1/users/13은 403인데 DELETE나 PUT은 라우팅이 다른 핸들러를 타면서 검사를 건너뛰는 경우가 실제로 있다. HEAD·OPTIONS도 포함해 메서드 전수를 확인한다.

API4 — 자원 소비, QA가 가장 잘 만드는 케이스

모듈 16에서 배운 성능 테스트 감각이 그대로 보안 케이스가 되는 영역이다. 질문은 하나다. "이 API가 제한 없이 비싸질 수 있는 입력이 있는가."

RC-01 페이지 크기        ?limit=1000000 → 서버가 상한으로 잘라야 한다
RC-02 정렬·필터 조합     인덱스 없는 컬럼 정렬 요청 → 타임아웃/풀스캔
RC-03 깊은 페이지네이션  ?offset=5000000 → 응답 시간 급증
RC-04 파일 업로드        용량·확장자·동시 업로드 수 제한
RC-05 압축 폭탄          작은 요청이 큰 처리로 전개되는 경로
RC-06 GraphQL 중첩 질의  깊이/복잡도 제한 (모듈 12 레슨 6 참조)
RC-07 과금 자원 호출     SMS·이메일 발송, 외부 API 호출이 붙은 엔드포인트
RC-08 인증 전 엔드포인트 로그인·가입·인증번호 발송의 속도 제한

RC-07이 사업적으로 가장 아픈 항목이다. 인증번호 재발송 API에 속도 제한이 없으면 공격자는 데이터를 못 가져가도 문자 발송 비용을 무한히 태울 수 있다. 이런 케이스는 스테이징에서 소규모로 확인하고, 실측값(요청 N회에 실제 발송 N회, 건당 단가)을 보고서에 넣는다. 금액으로 환산된 영향은 우선순위를 즉시 바꾼다.

API6 — 업무 흐름의 남용

API6은 "취약점"처럼 생기지 않아서 가장 자주 무시된다. 각 요청은 정상이고 권한도 맞다. 문제는 그 흐름을 사람이 아닌 스크립트가 반복한다는 것 이다.

흐름남용 시나리오점검 질문
한정 상품 구매매크로가 재고를 전량 선점1계정·1IP·1기기당 제한이 있는가
회원 가입프로모션 쿠폰 목적 대량 가입동일 기기·유사 이메일 패턴 탐지가 있는가
예약 생성·취소좌석 점유 후 반복 취소취소율 기반 제한이 있는가
후기 작성평점 조작구매 이력 연결과 작성 빈도 제한이 있는가

QA의 점검 방식은 정상 속도와 비정상 속도의 경계를 실측하는 것 이다. "10초에 5번까지는 통과, 6번째부터 429"처럼 관측된 임계값을 적고, 그 임계값이 기획이 의도한 값인지 확인한다. 기획서에 임계값이 아예 없다면 그것 자체가 결함이다.

API7(SSRF)과 API10 — 방향이 반대인 두 항목

API7 SSRF          : 클라이언트가 준 URL을 우리 서버가 요청한다
                     프로필 이미지 URL, 웹훅 등록, PDF 렌더링, 링크 미리보기
 
API10 Unsafe Consumption : 우리가 호출한 외부 API의 응답을 그대로 믿는다
                     결제사 콜백, 배송사 조회 결과, 외부 인증 서버 응답

SSRF 점검 케이스는 "우리 서버가 내부 주소로 요청하는가"를 본다. 여기서 중요한 것은 실제로 내부망을 뒤지는 것이 아니라, 요청이 차단되는지만 확인하는 것 이다. 승인된 테스트 대상 안에서, 응답 코드·응답 시간·서버 로그로 차단 여부를 판정하면 충분하다.

SSRF-01 사설 대역 주소를 URL로 제출 → 거부되어야 한다
SSRF-02 리다이렉트로 사설 대역으로 유도 → 최종 목적지도 검사하는가
SSRF-03 DNS가 사설 IP로 해석되는 도메인 → 해석 결과 기준으로 막는가
SSRF-04 file:// 등 비 HTTP 스킴 → 허용 스킴 화이트리스트가 있는가
SSRF-05 응답 본문이 그대로 노출되는가 → 노출되면 영향이 크게 커진다

API10은 QA가 잘 보는 자리다. 외부 연동 테스트에서 "정상 응답"만 넣어 보는 습관 이 이 항목을 놓치게 한다. 모듈 12(레슨 5)의 컨슈머 주도 계약 테스트에서 배운 대로, 외부가 보낼 수 있는 이상한 응답 — 필드 누락, 타입 불일치, 음수 금액, 과도하게 긴 문자열, 리다이렉트, 500 — 을 스텁으로 주입해 우리 쪽이 어떻게 되는지 본다.

API9 — 섀도 API와 좀비 API

문서에 있는 API만 테스트하면 문서에 없는 API는 영원히 테스트되지 않는다.

이름정의찾는 법
섀도(Shadow) API문서·스펙에 없지만 살아 있는 엔드포인트게이트웨이 접근 로그의 경로 집계
좀비(Zombie) API구버전이라 폐기했다고 믿지만 응답하는 엔드포인트/v1, /v2 병행 호출 비교
미공개 환경스테이징·데모가 인터넷에 열려 있음인증서 투명성 로그, 서브도메인 목록(사내 자산 대장 기준)

모듈 12(레슨 4)에서 OpenAPI 계약 테스트를 배웠다. 그 자산을 여기서 반대 방향으로 쓴다. 계약 테스트는 "스펙에 있는 것이 구현과 맞는가"를 보지만, API9 점검은 "구현에 있는데 스펙에 없는 것은 무엇인가" 를 본다.

# 게이트웨이 접근 로그의 경로를 정규화해 집계하고 스펙과 대조하는 골격
# (사내 로그 접근 권한이 있는 환경에서만 실행한다)
awk '{print $7}' access.log \
  | sed -E 's#/[0-9]+#/{id}#g; s#\?.*##' \
  | sort | uniq -c | sort -rn > observed_paths.txt
 
# openapi.yaml 에서 선언된 경로 목록
python3 - <<'PY' > declared_paths.txt
import yaml, sys
spec = yaml.safe_load(open("openapi.yaml"))
for p in sorted(spec.get("paths", {})):
    print(p)
PY
 
# 관측되지만 선언되지 않은 경로 = 섀도 API 후보
comm -23 <(awk '{print $2}' observed_paths.txt | sort -u) <(sort -u declared_paths.txt)

이 대조를 분기마다 한 번 돌려 목록을 내놓는 것만으로도 API9 대응의 대부분이 끝난다. 후보가 나오면 각각에 대해 소유자·용도·폐기 가능 여부를 묻고, 남길 것은 스펙에 등록해 계약 테스트 대상에 넣는다.

점검 케이스로의 번역표

열 항목을 그대로 외울 필요는 없다. 엔드포인트 하나를 받으면 어떤 질문을 순서대로 던지는가 를 몸에 익히는 편이 실용적이다.

순서질문대응 항목
1인증 없이 호출하면?API2
2ID를 남의 것으로 바꾸면?API1
3권한이 낮은 계정으로 호출하면? 메서드를 바꾸면?API5
4응답에 필요 없는 필드가 있는가? 요청에 넣으면 안 되는 필드가 반영되는가?API3
5크게·많이·빠르게 요청하면?API4, API6
6URL을 받는 파라미터가 있는가?API7
7오류 응답이 내부 정보를 흘리는가? CORS·헤더 설정은?API8
8이 경로가 스펙에 있는가? 구버전도 살아 있는가?API9
9이 API가 호출하는 외부 응답이 비정상이면?API10

판정 — API 결함의 등급을 정하는 세 질문

레슨 1·2에서 쓴 기준이 API에서도 같다. 다만 API는 자동화가 쉽다는 점 때문에 규모 요인이 특히 크게 작동한다.

(1) 인증이 필요한가, 아닌가
    비인증 BOLA는 거의 항상 Critical, 인증 후 BOLA는 규모에 따라 달라진다
(2) 식별자를 열거할 수 있는가
    순번이면 전량, UUID면 다른 경로로 ID를 얻을 수 있는지에 달려 있다
(3) 보상 통제가 실제로 작동하는가
    WAF 규칙, 속도 제한, 이상 탐지가 "설정되어 있다"가 아니라
    실제 요청을 막았는지 로그로 확인한다

(3)을 확인하지 않고 "WAF가 있으니 중간 등급"으로 낮추는 것이 흔한 실수다. 보상 통제는 테스트로 확인된 것만 감점 요인으로 인정한다 는 규칙을 팀 기준으로 못 박아 두면 논쟁이 줄어든다.

실무에서 왜 필요한가

API는 화면보다 수명이 길고, 화면보다 조용히 늘어난다. 모바일 앱 v1을 위해 만든 엔드포인트는 앱이 v5가 되어도 살아 있고, 파트너 연동용으로 급히 뚫은 경로는 문서에 없는 채로 남는다. 화면 기준 테스트 계획은 이 증가를 따라가지 못한다. 엔드포인트 인벤토리를 테스트 계획의 1급 자산으로 두는 것 이 API 보안 테스트의 출발점이고, 그건 도구가 아니라 프로세스의 문제다.

또 API 보안 케이스는 회귀에 남기기 가장 쉬운 보안 테스트 다. UI 보안 테스트는 화면이 바뀌면 깨지지만, API 인가 매트릭스는 계약이 바뀌지 않는 한 유지된다. 레슨 2에서 만든 3축 매트릭스에 이 레슨의 API4·API3 케이스를 얹으면, 그대로 CI에서 도는 보안 회귀 스위트가 된다.

실습 과제

과제 1 — 엔드포인트 하나를 10개 질문으로 통과시키기 (25분)

담당 서비스에서 식별자를 받고 쓰기 동작이 있는 엔드포인트 하나를 고른다. 본문의 번역표 9행을 순서대로 적용해 실행 가능한 케이스 12개 이상 을 만든다. 각 케이스에 기대 응답과 판정 근거(상태 코드·본문·부작용)를 적는다. 만들 수 없는 케이스가 있으면 "해당 없음"과 그 이유를 적는다 — 이유를 적는 과정에서 빠진 통제가 드러난다.

과제 2 — 섀도 API 찾기 (25분)

접근 로그(또는 프록시 기록, 모바일 앱 트래픽 캡처)에서 최근 7일간 호출된 경로를 정규화해 집계하고, OpenAPI 스펙의 경로 목록과 대조한다. 로그 접근이 불가하면 개발자 도구 네트워크 탭으로 주요 화면 10개를 순회하며 호출 경로를 모으는 것으로 대체한다. 결과를 선언·관측 2×2 표 로 정리한다.

스펙에 있음스펙에 없음
호출됨정상섀도 API 후보
호출 안 됨좀비 후보(폐기 검토)—

각 후보에 소유자와 처리 방침(등록/폐기/보류)을 붙인다.

과제 3 — 자원 소비와 업무 흐름 임계값 실측 (20분)

과제 1의 엔드포인트와, 과금이나 재고가 걸린 흐름 하나를 골라 임계값을 실측한다. 스테이징에서, 사전에 합의된 소규모 요청량으로만 수행한다. limit 상한, 속도 제한이 걸리는 지점, 429 응답 후 복구 시간을 기록하고, 기획 문서의 의도값과 비교한다. 문서에 값이 없으면 "정책 부재"로 보고한다.

자가 체크리스트

  • OWASP API Security Top 10의 현재 판이 2023년판이고 항목 ID가 APIn:2023 형식임을 안다
  • BOLA(API1)와 BFLA(API5)를 구분하고 각각의 케이스 생성 방법을 말할 수 있다
  • API3의 읽기 방향(과도한 응답 필드)과 쓰기 방향(매스 어사인먼트)을 따로 케이스로 만든다
  • 자원 소비(API4) 점검 항목 6개 이상을 열거할 수 있다
  • 업무 흐름 남용(API6)을 임계값 실측으로 점검할 수 있다
  • SSRF(API7) 점검을 차단 확인 수준으로 안전하게 설계할 수 있다
  • 섀도 API와 좀비 API를 정의하고 스펙 대조로 찾아낼 수 있다
  • 외부 API 응답의 비정상 케이스를 스텁으로 주입해 API10을 점검할 수 있다
  • 보상 통제는 테스트로 확인된 것만 심각도 감점 요인으로 인정한다

흔한 실수

  • 웹 Top 10 항목을 그대로 API에 적용한다. 목록이 다르고 무게 중심이 다르다. 인가 항목을 세 개로 나눠 보는 관점이 빠진다.
  • 2019년판 번호를 그대로 쓴다. 벤더 리포트에 API1:2019가 있으면 매핑을 현행 판으로 다시 맞춰야 한다.
  • GET만 확인하고 넘어간다. 라우팅이 메서드별로 다른 핸들러를 타면 PUT·DELETE에서만 인가가 빠진 경우가 나온다.
  • 문서에 있는 엔드포인트만 테스트한다. 섀도 API는 정의상 문서에 없다. 관측 기반으로 찾지 않으면 영원히 안 보인다.
  • 속도 제한 테스트를 운영에서 돌린다. 범위 합의 없이 하면 장애를 만들거나 스스로 차단 대상이 된다. 스테이징·합의된 요청량이 원칙이다.
  • "WAF가 막아 준다"로 심각도를 낮춘다. 확인하지 않은 보상 통제는 근거가 아니다. 실제 요청이 차단됐다는 로그를 본 뒤에만 감점한다.
  • 외부 연동을 정상 응답으로만 테스트한다. API10은 상대가 이상해질 때 우리가 어떻게 되는지의 문제다.

참고 자료

낯선 말 풀이

이 레슨에 나온 약어입니다. 본문에서도 처음 나올 때 점선으로 표시해 두었습니다 (제목과 링크 글자는 그대로 둡니다).

API — 응용 프로그램 인터페이스
프로그램끼리 주고받는 창구. 사람이 화면 버튼을 누르는 대신, 프로그램이 정해진 주소로 요청을 보내 답을 받는다.
OWASP — 오왁스프
웹과 앱 보안 자료를 공개로 만드는 비영리 단체. 취약점 목록과 점검 기준이 유명하다.
ID — 식별자
무엇 하나를 다른 것과 구별하기 위해 붙인 이름이나 번호.
XSS — 크로스 사이트 스크립팅
입력창에 넣은 글자가 화면에서 명령으로 실행되게 만드는 공격.
URL — 인터넷 주소
웹에서 무언가의 위치를 가리키는 주소 한 줄.
CORS — 교차 출처 자원 공유
웹 화면이 다른 주소의 창구를 부를 수 있는지를 정하는 규칙. 허용되지 않으면 브라우저가 막는다.
HTTP — 하이퍼텍스트 전송 규약
웹에서 요청과 응답을 주고받는 약속. "무엇을 달라"고 보내면 "여기 있다" 또는 "그런 건 없다"가 돌아온다.
QA — 품질보증
만든 것이 쓸 만한지 확인하고, 애초에 덜 망가지게 만드는 방법을 정하는 일. "테스트"는 그 일의 일부다.
OpenAPI — 오픈에이피아이 명세
창구가 어떤 주소로 무엇을 받고 무엇을 돌려주는지 정해진 형식으로 적어 둔 문서.
UI — 사용자 인터페이스
사람이 직접 보고 누르는 부분 — 화면, 버튼, 입력창.
CI — 지속적 통합
코드를 올릴 때마다 자동으로 합쳐서 빌드하고 시험까지 돌리는 방식.

용어집 전체 보기