정보처리안전확보지원사(情報処理安全確保支援士) 2024년 봄(레이와 6년) 오후 문제1 해설 ── JWT의 alg=none과 API 인가, WAF의 잠정 대책

· · 정보처리안전확보지원사, 등록 지원사, API, API 보안, JWT, 인증, 인가, WAF, Log4Shell, 정보보안, 취약점, IPA

「JWT의 서명을 검증하고 있으니, 이용자 ID는 신뢰할 수 있다」

이 말은 절반만 옳습니다.

정보처리안전확보지원사 시험 2024년 봄(레이와 6년) 오후 문제1은, 스마트폰에서 호출하는 API를 소재로 합니다1. 인증에 성공하면 JWT가 발급되고, 그 JWT를 붙여 이용자 정보를 조회·갱신하는 API를 호출합니다. 언뜻 보면 흔한 구성입니다.

그런데 진단에서는 다음 네 가지가 발견됩니다.

  1. JWT 헤더의 algnone으로 바꾸면, 서명 없는 JWT가 통과한다
  2. 올바른 JWT를 그대로 사용한 채 mid를 다른 이용자 ID로 바꾸면, 타인의 정보를 조회·갱신할 수 있다
  3. 사양에 없는 status=paid를 추가하면, 무료 이용자가 유료 이용자로 바뀐다
  4. 이메일로 도착하는 4자리 인증 코드를, 제한 없이 무차별 대입할 수 있다

네 가지 모두 「인증 관련 취약점」처럼 보이지만, 원인은 같지 않습니다. 토큰의 무결성, 오브젝트 단위 인가, 프로퍼티 단위 인가, 시도 횟수 제한이라는 서로 다른 경계가 무너져 있습니다.

후반부에서는 또 다른 논점이 더해집니다. 널리 사용되는 오픈소스 라이브러리에서 JNDI Lookup을 악용해 외부에서 코드를 실행할 수 있는 중대한 취약점이 공표됩니다. 수정판도, 완성된 WAF 규칙도 아직 없습니다. 그 사이 어떻게 영향을 확인하고, 어디를 WAF로 살펴보고, 왜 처음에는 「차단」이 아니라 「검지」로 하는지를 묻습니다.

이 글에서는 공식 해답 예2와 채점 강평3을 바탕으로, 각 설문의 답뿐 아니라 왜 그 답이 되는지, 실무에서는 어디까지 엄격하게 설계해야 하는지까지 정리합니다.

문제 전체 구조인증 코드, JWT, API 인가, 라이브러리 취약점의 각 단계에서 무너지는 신뢰 경계를 나타낸다시도 횟수 제한 없음alg=none 허용mid를 신뢰status=paidJNDI/LDAP/HTTP이용자 앱4자리 인증 코드JWT 발급이용자 API로그 출력취약한 라이브러리외부 코드 실행타인의 데이터 조회/갱신과금 상태 변경

그림1: 문제 전체 구조. 각 단계에서 서로 다른 신뢰 경계가 무너진다.

1. 먼저 결론

  • RESTful API가 세션을 갖지 않는 성질을 스테이트리스(stateless)라고 한다. 다만 서버가 데이터베이스나 이용자 상태를 전혀 갖지 않는다는 의미는 아니다
  • 4자리 인증 코드는 1만 가지. 1초에 10회면 평균 5,000회, 500초만에 성공한다. 유효 기간 10분보다 짧으므로, 만료 시간만으로는 막을 수 없다
  • alg=none에 대한 최소한의 대책은 JWT 헤더의 algNONE이 아님을 확인하는 것. 다만 실무에서는 허용할 알고리즘을 서버 측에서 고정한다
  • JWT가 올바르더라도 요청의 mid를 신뢰해서는 안 된다. JWT 안의 이용자 ID와 mid를 대조하거나, 더 안전하게는 mid를 받지 않고 JWT로부터 대상을 정한다
  • status=paid의 추가는 사양 외의 프로퍼티까지 내부 오브젝트에 결부시키는 Mass Assignment 문제. 갱신용 DTO를 허용 목록으로 사용하고, 과금 상태는 이용자가 변경하지 못하게 한다
  • 무차별 대입 대책의 해답은 연속 실패 횟수가 임곗값을 초과하면 계정을 잠그는 처리. 실무에서는 단계적 지연이나 발신지 단위의 제어도 함께 적용한다
  • 새로운 중대 취약점의 영향 확인에서는 파괴적인 명령이 아니라, 테스트 서버의 index.html에 대한 접근을 기록해 외부 코드 실행이 도달했는지를 확인한다
  • 공격 문자열은 HTTP 헤더에 들어가므로, WAF의 검사 대상은 Header. 대소문자 치환에 대응하는 정규 표현으로는, 예를 들어 \W[jJ][nN][dD][iI]\W를 사용한다
  • WAF를 처음에 「검지」로 설정하는 이점은 오탐으로 인한 업무 통신 차단을 막을 수 있다는 것. 알림을 받으면 공격인지를 정밀 조사하고, 조정 후에 차단으로 옮긴다
  • WAF는 잠정 대책이며, 근본 대책은 영향을 받는 라이브러리를 수정판으로 업데이트하는 것이다

2. 소재와 설문의 대응 관계

문제의 무대는 헬스케어 서비스를 새로 제공하려는 G사입니다. 이용자는 스마트폰 앱에서 식사나 체중 등을 입력하고, 건강 위험도 판정이나 식단 조언을 받습니다. 시스템은 클라우드 위에 구축되어 있으며, API 게이트웨이, 이벤트 기반 처리, 매니지드 데이터베이스를 조합하고 있습니다.

문제문의 제품명이나 서비스명은 추상화되어 있습니다. 본 기사에서도 IPA의 도표나 문장을 그대로 옮기지 않고, 설문을 이해하는 데 필요한 구조만을 바꾸어 표현합니다.

설문 주제 본 기사의 장
설문1 RESTful API의 성질 4장
설문2(1) 4자리 코드 무차별 대입 시간 5장
설문2(2) JWT의 alg=none 6장
설문2(3) mid를 이용한 타인 접근 7장
설문2(4) 사양 외 status를 받아들이는 결함 8장
설문2(5) 무차별 대입 대책 9장
설문3(1) 안전한 방법으로 취약점 존재를 확인하기 11장
설문3(2)(3) WAF가 보는 위치와 정규 표현 12장
설문3(4) 검지 모드의 이점과 운용 13장

채점 강평에서는 전체 정답률이 평균적이었다고 되어 있습니다. 한편 설문2(2)의 JWT 변조 대책과, 설문3(1)의 검증용 서버에 필요한 구조는 정답률이 다소 낮았다고 지적되어 있습니다. 둘 다 용어만 알아서는 풀 수 없습니다. 공격자가 어떤 값을 바꾸고, 그것이 어떤 처리로 흘러가, 어디서 신뢰되어 버렸는가를 추적할 필요가 있습니다.

3. 이 문제는 「인증 문제」 하나가 아니다

문제 전체를 신뢰 경계별로 나열하면 다음과 같습니다.

[이용자 ID・비밀번호]
          |
          v
[4자리 코드 확인] ---- 시도 횟수 제한 없음 ----> 무차별 대입
          |
          v
[JWT 발급]
          |
          v
[JWT 라이브러리] ------- alg=none 허용 ------> 이용자 ID 변조
          |
          v
[이용자 API]
    |             |
    |             +-- status를 통째로 전달 ----> 프로퍼티 단위 인가 불비
    |
    +-- mid를 신뢰 -----------------------> 오브젝트 단위 인가 불비

[외부 입력을 로그에 기록]
          |
          v
[취약한 라이브러리] ---- JNDI/LDAP/HTTP ------> 외부 코드 실행

여기서 가장 중요한 것은 다음 구분입니다.

확인 묻는 것 이 문제에서 무너진 예
인증 당신은 누구인가 4자리 코드 무차별 대입
토큰 검증 그 신원 정보가 변조되지 않았는가 alg=none
오브젝트 단위 인가 그 이용자의 데이터에 접근해도 되는가 mid 치환
프로퍼티 단위 인가 그 항목을 변경해도 되는가 status=paid
입력에서 실행으로의 경계 외부 입력이 명령으로 해석되지 않는가 JNDI Lookup

바로 앞 확인에 성공했다는 사실은 다음 확인을 생략해도 되는 이유가 되지 않습니다. 올바른 JWT를 가진 이용자라 해도 타인의 데이터를 읽어도 되는 것은 아닙니다. 자신의 데이터를 갱신할 수 있는 이용자라 해도 과금 상태까지 변경해도 되는 것은 아닙니다.

이렇게 단계를 나눌 수 있게 되면, 설문마다의 답이 암기가 아니게 됩니다.

인증과 인가의 차이인증이 주체를 확인하고, 인가가 그 주체에게 허용된 조작을 확인한다인증누구인가인가무엇을 해도 되는가

그림2: 인증과 인가의 차이. 인증이 먼저이고, 인가는 별개의 확인이다.

4. 설문1 ── 스테이트리스란 무엇인가

설문1은 RESTful API 설계 원칙 중 하나로, 세션 관리를 하지 않는 성질을 묻습니다.

해답은 스테이트리스(stateless)입니다.

스테이트리스란, 서버가 바로 앞 요청의 대화 상태를 기억해 두지 않아도 각 요청만으로 처리에 필요한 정보가 모두 갖추어지는 것을 말합니다. 이 문제에서는 스마트폰 앱이 요청마다 JWT를 Authorization 헤더에 붙입니다. 서버는 그 JWT를 검증하여 해당 요청의 이용자를 식별합니다.

오해하기 쉬운 것은, 스테이트리스를 「서버는 상태를 아무것도 갖지 않는다」로 읽는 것입니다. 실제로는 다음 상태는 보통 그대로 가지고 있습니다.

  • 이용자 정보나 건강 데이터를 저장하는 데이터베이스
  • 과금 상태
  • 인증 코드의 값, 유효 기한, 실패 횟수
  • JWT 서명 키
  • 실효 목록을 사용하는 설계라면 그 실효 정보
  • 로그나 감사 기록

갖지 않는 것은, 대화를 이어가기 위해서만 존재하는 서버 측 세션 상태를, 각 API 호출의 전제로 삼지 않는다는 것입니다.

또한 스테이트리스라는 것은 안전성을 자동으로 높여 주지 않습니다. JWT를 매번 보내면 수평 분산은 쉬워지지만, JWT 검증을 잘못하면 그 오류 역시 모든 노드로 균일하게 퍼집니다. 아키텍처상의 성질과 보안상의 올바름은 별개입니다.

5. 설문2(1) ── 4자리 코드는 평균 500초 만에 뚫린다

인증 API는 이용자 ID와 비밀번호가 맞으면 이메일로 4자리 숫자를 보냅니다. 그 후 이용자 ID와 4자리 코드가 일치하면 JWT를 발급합니다. 코드는 생성 후 10분간 유효합니다.

진단에서는 1초에 10회 시도가 가능했습니다. 평균적으로 몇 초 만에 뚫을 수 있는지를 구합니다.

계산은 「후보 수의 절반」

4자리 숫자는 앞자리의 0을 포함해 다음과 같이 1만 가지입니다.

0000, 0001, 0002, ... , 9999

정답이 균등하게 선택된다면, 순서대로 중복 없이 시도하는 공격자가 정답에 도달하기까지의 평균 시도 횟수는 후보 수의 절반입니다.

평균 시도 횟수 = 10,000 ÷ 2 = 5,000회
평균 시간      = 5,000 ÷ 10회/초 = 500초

따라서 빈칸 b는 500입니다.

최댓값으로는 1,000초가 걸리지만, 문제가 묻는 것은 평균입니다. 그리고 코드의 유효 기간은 600초이므로, 평균 돌파 시간인 500초보다 깁니다. 이것이 「돌파될 가능성이 높다」고 판단된 이유입니다.

4자리 인증 코드의 시간 감각1만 가지를 1초에 10회로 시도하면 평균 5,000회, 500초 만에 맞으며, 유효 기간 600초보다 짧다평균 10,000 / 2 = 5,000회500초 미만 600초후보 수 10,000가지평균 돌파 시간 500초유효 기간 600초유효 기간 내 돌파 가능

그림9: 4자리 인증 코드의 시간 감각. 후보 수의 절반을 평균적으로 시도하면 유효 기간 내에 맞는다.

실효 시간만 줄여도 후보가 적으면 진다

인증 코드의 강도는 자릿수만으로도, 유효 기간만으로도 결정되지 않습니다.

유효 기간 중 시도할 수 있는 횟수
= 초당 시도 횟수 × 유효 기간
= 10 × 600
= 6,000회

중복되지 않는 값을 순서대로 시도한다면, 1만 가지 중 60%를 유효 기간 안에 확인할 수 있습니다. 실효 시간을 두더라도 시도 횟수를 제한하지 않으면 충분하지 않습니다.

현행 NIST SP 800-63B는 대역외 인증에 사용하는 단기 비밀에 대해 최소 6자리를 요구하고, 64비트 미만이면 시도 횟수 제한을 필수로 하고 있습니다. 또한 이메일을 대역외 인증에 사용하지 말 것을 요구하고 있습니다4. 시험에서는 주어진 4자리·이메일 발송이라는 사양 안에서 답하지만, 실무에서 새로 설계할 때는 그 전제 자체도 재검토해야 합니다.

6. 설문2(2) ── alg=none은 「공격자에게 검증 방식을 고르게 한」 문제

문제의 JWT는 헤더, 페이로드, 서명 세 부분으로 이루어집니다.

base64url(header).base64url(payload).base64url(signature)

헤더에는 서명에 사용하는 알고리즘으로 RS256이 기록되어 있었습니다. 페이로드에는 이용자 ID와 발급 시각, 유효 기한이 들어 있습니다.

진단자는 다음 두 가지를 변경했습니다.

  1. 헤더의 algRS256에서 NONE으로 변경한다
  2. 페이로드의 이용자 ID를 다른 이용자로 변경한다

그 JWT를 보내자 검증이 성공하여 다른 사람으로 위장할 수 있었습니다.

JWT alg=none 공격의 흐름올바른 JWT에서 alg를 none으로 바꾸고, 이용자 ID를 고쳐 써서 통과시키는 흐름헤더의 alg를 none으로서명 검증을 건너뜀올바른 JWTalg=RS256user=user01변조된 JWTalg=noneuser=user02서버가user02로 받아들임

그림3: JWT alg=none 공격의 흐름. 공격자가 검증 알고리즘을 고르고 있다.

none은 철자 오류가 아니다

RFC 7519에는 서명도 암호화도 갖지 않는 JWT로서 algnone인 「Unsecured JWT」가 정의되어 있습니다5. 따라서 none이라는 값이 사양상 아예 존재하지 않는 것은 아닙니다.

문제는 서명 있는 JWT만 받아야 할 API가 공격자가 지정한 none을 받아들였다는 것입니다.

취약한 처리를 개념적으로 적으면 다음과 같습니다.

1. JWT 헤더를 읽는다
2. 헤더에 적힌 alg를 보고 검증 방식을 고른다
3. alg가 none이면 서명 검증을 하지 않는다
4. 페이로드의 이용자 ID를 신뢰한다

공격자가 제어하는 입력으로부터 보안 강도 그 자체를 고르게 하고 있는 것입니다.

시험의 해답

설문은 수정 후 라이브러리 Q가 「어떤 데이터에 대해, 어떤 검증을 수행하는가」를 각각 20자 이내로 묻습니다.

해답 예는 다음과 같습니다.

항목 해답의 요점
검증 대상 데이터 JWT 헤더 안의 alg에 지정된 값
검증 내용 NONE이 아님을 검증한다

문제문의 취약점에 대한 직접적인 수정으로서는 이것으로 정답입니다.

실무에서는 「NONE만 아니면 된다」로 끝내지 않는다

여기서는 시험의 해답과 실무의 권장 사항을 구분할 필요가 있습니다.

RFC 8725는 JWT 라이브러리가 호출하는 쪽에 허용 알고리즘 집합을 지정하게 하고, 그 집합 밖의 것은 사용해서는 안 된다고 규정하고 있습니다6. 즉 다음과 같은 사고방식입니다.

나쁜 사고방식:
  token.header.alg != "none" 이면 받아들인다

좋은 사고방식:
  serverConfig.allowedAlgorithms 에 포함된 경우에만 받아들인다
  예: allowedAlgorithms = ["RS256"]

none만 거부하더라도, 다른 취약한 알고리즘이나 공개키 방식과 대칭키 방식을 혼동하는 알고리즘 혼동이 남을 가능성이 있습니다. 받아들일 조건을 부정형으로 늘려 가는 것이 아니라, 허용할 조건을 좁게 고정하는 것이 원칙입니다.

JWT 검증에서는 알고리즘뿐 아니라, 용도에 따라 적어도 다음을 확인합니다.

항목 확인할 것
서명 예상한 키와 알고리즘으로 검증할 수 있는가
iss 신뢰하는 발행자인가
aud 이 API를 대상으로 발급된 토큰인가
exp 유효 기한 내인가
nbf 이용 개시 시각보다 이전은 아닌가
sub 또는 이용자 ID 애플리케이션상 유효한 주체인가
토큰 종류 ID 토큰과 액세스 토큰 등을 혼동하지 않았는가

이 문제에서는 페이로드의 키 이름이 user이지만, 실무에서는 표준인 sub를 쓰거나, 독자적인 클레임의 의미를 명확히 정의합니다.

안전한 JWT 검증과 위험한 JWT 검증위험한 검증은 alg에 의존하고, 안전한 검증은 서버 측 허용 목록을 사용한다안전한 검증서버 설정의 허용 알고리즘예 - RS256JWT 헤더의 alg가허용 목록에 포함되는지 확인서명・iss・aud・exp를 검증위험한 검증JWT 헤더의 alg를 읽는다alg가 none이면 받아들인다

그림4: 안전한 검증과 위험한 검증. 실무에서는 허용할 알고리즘을 좁게 고정한다.

Base64url은 암호화가 아니다

또 하나, JWT에서 자주 일어나는 오해가 있습니다. 헤더와 페이로드는 base64url로 표현되지만, 이것은 암호화가 아닙니다. 누구나 디코딩해서 읽을 수 있습니다.

서명이 보증하는 것은, 올바르게 검증되었을 경우에 한해 내용이 발급 후 변조되지 않았다는 것입니다. 비밀로 하고 싶은 개인정보를 서명이 있는 JWT의 페이로드에 넣어도 된다는 의미는 아닙니다.

7. 설문2(3) ── 올바른 JWT라도 mid를 바꾸면 타인의 정보를 읽을 수 있었다

다음은 JWT 자체를 변조하지 않는 공격입니다.

이용자 API는 GET 또는 PUT으로 mid라는 이용자 ID를 받습니다. 공통 모듈 P는 그 mid에 연결된 이용자 정보를 데이터베이스에서 조회·갱신합니다.

공격의 구도는 단순합니다.

JWT의 이용자 ID: user01    ← 올바르게 서명된 JWT
요청의 mid:      user02   ← 공격자가 변경

JWT의 서명은 올바르므로 인증은 성공합니다. 하지만 API는 mid=user02를 그대로 신뢰하여 user02의 정보를 반환합니다.

이것은 OWASP API Security Top 10 2023에서 말하는 Broken Object Level Authorization(BOLA)에 해당하는 전형적인 사례입니다. 이용자가 지정한 오브젝트 ID를 사용해 데이터에 접근할 때는 매번 그 오브젝트에 대한 인가를 확인해야 합니다7.

BOLA 공격올바른 JWT를 사용하면서, 요청의 mid를 다른 이용자 ID로 바꾼다JWT: user01mid: user02mid를 신뢰공격자이용자 APIDB에서 user02의 정보를 반환

그림5: BOLA 공격. 인증은 통과하지만 인가는 확인하지 않는다.

설문의 해답

표5의 밑줄 ②는 공통 모듈 P의 호출 처리에 추가할 처리를 40자 이내로 묻습니다.

해답 예는 다음과 같습니다.

JWT에 포함된 이용자 ID가 mid의 값과 일치하는지를 검증하는 처리

공통 모듈 P에서 검증하는 이점은 GET과 PUT 양쪽, 그리고 앞으로 P를 이용할 다른 API에도 같은 인가를 쉽게 적용할 수 있다는 것입니다. 각 화면이나 각 엔드포인트에 같은 비교 처리를 복사해 넣으면 어딘가 한 곳에서 빠지게 됩니다.

더 안전한 설계는 mid를 받지 않는 것

자기 자신의 정보만 조회·갱신하는 API라면, 클라이언트로부터 이용자 ID를 받을 필요가 없습니다.

GET /users/me
Authorization: Bearer <JWT>

서버 측에서는 검증이 끝난 JWT로부터 주체를 꺼냅니다.

principal = validateJwt(request.authorization)
userId = principal.subject
return repository.getUser(userId)

갱신도 마찬가지입니다.

principal = validateJwt(request.authorization)
input = validateProfileUpdate(request.body)
repository.updateProfile(
    userId = principal.subject,
    name = input.name,
    age = input.age
)

비교 처리는 작성하면 지킬 수 있습니다. 하지만 대상 ID를 외부에서 받지 않는 설계라면, 비교 처리를 빠뜨리는 종류의 버그 자체를 줄일 수 있습니다.

관리자가 다른 사람의 이용자 정보를 조작할 필요가 있다면, 다음과 같이 나눕니다.

PUT /users/me                  일반 이용자용
PUT /admin/users/{userId}      관리자용

관리자용에는 별도의 권한, 감사 로그, 필요하다면 재인증을 요구합니다. 「일반 이용자 API에 관리자인 경우만 예외를 추가하는」 방식보다 인가 정책의 경계가 더 잘 보이게 됩니다.

BOLA 방지 방법요청의 mid를 사용하지 않고, JWT의 주체로부터 대상을 정하거나 대조한다GET /users/me + JWTJWT의 sub를 취득일치불일치mid 없음이용자APImid가 있으면sub와 일치하는가자신의 데이터를 반환403 거부JWT의 sub로 DB 검색

그림6: BOLA를 막는 방법. mid를 받지 않거나, JWT의 주체와 대조한다.

인증과 인가를 한 문장으로 구분하기

시험에서도 실무에서도 다음과 같은 표현이 도움이 됩니다.

  • 인증: 누구인가
  • 인가: 그 사람이 무엇을 해도 되는가

JWT의 서명 검증이 성공했다는 것은 「이 토큰이 나타내는 주체를 신뢰할 수 있다」는 데까지입니다. 「그 주체가 user02를 읽어도 된다」는 별도로 확인해야 합니다.

8. 설문2(4) ── status=paid는 프로퍼티 단위 인가 결함

이용자 API의 사양에는 갱신용 파라미터로 다음이 정의되어 있습니다.

mid   이용자 ID
name  이름
age   나이

그런데 진단자는 사양에 없는 다음 값을 추가했습니다.

status=paid

그러자 무료 이용자의 상태가 유료 이용자로 바뀌었습니다.

문제문에 따르면 서비스 L은 받은 파라미터를 검증하지 않고 모두 공통 모듈 P로 넘기고 있었습니다. P는 그대로 데이터베이스를 갱신할 수 있는 구조였습니다.

빈칸 c의 해답은 공통 모듈 P입니다.

Mass Assignment사양에 없는 status=paid가 추가되어, 내부 오브젝트에 통째로 반영된다공격자가 status=paid를 추가자동 바인딩DB 저장API 사양mid / name / age요청 바디공통 모듈 P과금 상태가 paid로 변경

그림7: Mass Assignment. 사양 외 프로퍼티가 내부 오브젝트에 통째로 반영된다.

BOLA와의 차이

앞 장의 mid 치환과 이번 status 추가는 비슷해 보이지만, 지켜야 할 단위가 다릅니다.

취약점 공격자가 바꾸는 것 원래 확인해야 할 것
mid 치환 대상 오브젝트 이 이용자가 이 이용자 레코드에 접근해도 되는가
status 추가 오브젝트 내 프로퍼티 이 이용자가 이 항목을 변경해도 되는가

OWASP API Security Top 10 2023에서는 후자를 Broken Object Property Level Authorization으로 다루며, 이전의 Mass Assignment를 이 분류에 포함하고 있습니다8.

「JSON을 그대로 엔티티에 넣기」는 위험하다

취약한 구현은 개념적으로는 다음과 같습니다.

entity = repository.find(body.mid)
bindAllProperties(entity, body)
repository.save(entity)

화면에는 nameage만 입력란이 있더라도, 공격자는 HTTP 요청을 직접 만들 수 있습니다. UI에 없는 항목이라고 해서 그것이 보안 경계가 되지는 않습니다.

안전한 구현은 갱신 가능한 항목을 명시합니다.

input = parseExactSchema(body, fields = ["name", "age"])

entity = repository.find(authenticatedUserId)
entity.name = input.name
entity.age  = input.age
repository.save(entity)

여기서 중요한 것은 두 가지입니다.

  1. 갱신용 입력 타입에는 이용자가 변경해도 되는 항목만 둔다
  2. 사양 외의 알 수 없는 항목을 무시하는 것이 아니라, 가능하면 오류로 거부한다

알 수 없는 항목을 조용히 무시하면 공격이 실패했다는 사실은 감출 수 있지만, 클라이언트의 구현 실수나 공격의 징후를 놓치게 됩니다. 호환성상의 이유가 없다면 엄격한 스키마로 거부하는 편이 조사하기 쉬워집니다.

항목 단위 인가갱신용 DTO는 허용 목록만 가지고, 알 수 없는 프로퍼티는 거부한다스키마 검증아니오전용 경로갱신용 DTO - 허용 목록nameage요청 바디허용된항목뿐인가엔티티의 name/age를 갱신오류를 반환결제 서비스검증된 통지status=paid를 갱신

그림8: 항목 단위 인가. 갱신 가능한 항목을 허용 목록으로 제한하고, 과금 상태는 별도 경로로 변경한다.

status는 결제 결과로부터만 변경한다

status=paid는 이용자 프로필의 일부가 아닙니다. 결제가 성공했다는 서버 측의 사실로부터 도출되는 상태입니다.

이용자 프로필 갱신
  -> name / age만 변경 가능

결제 서비스로부터의 검증된 통지
  -> paymentId를 대조
  -> 중복 처리를 방지
  -> status를 paid로 변경

같은 데이터베이스 열에 저장하고 있더라도, 변경할 권한과 경로는 별개입니다. 내부 엔티티를 그대로 외부 API의 입력 타입으로 사용하면 이 경계가 사라집니다.

9. 설문2(5) ── 무차별 대입 대책은 실패 횟수를 상태로 보유한다

4자리 코드의 무차별 대입에 대해, 표5의 빈칸 d에 들어갈 처리를 30자 이내로 답합니다. 임곗값은 10입니다.

해답 예는 다음과 같습니다.

연속 실패 횟수가 임곗값을 초과하면 계정을 잠그는 처리

이 부분은 설문1의 스테이트리스와 모순되지 않습니다. API 호출의 대화 상태를 서버 세션으로 갖지 않는 것과, 보안 판단에 필요한 실패 횟수를 영속화하는 것은 별개입니다.

시도 횟수 제한의 유무제한이 없으면 평균 500초에 뚫리지만, 실패 횟수 제한으로 공격 속도를 억제할 수 있다제한 있음공격 속도 급감계정 잠금실패 10회로 잠금단계적 지연제한 없음약 500초인증 성공1초에 10회 시도

그림10: 횟수 제한의 유무. 실패 횟수 제한으로 무차별 대입을 실질적으로 막을 수 있다.

실무에서는 「영구 잠금 일변도」로 하지 않는다

계정 단위의 횟수 제한은 필요하지만, 공격자가 다른 사람의 이용자 ID를 알고 있는 경우, 일부러 10회 실패시켜 정당한 이용자를 몰아낼 수 있습니다. 그래서 실무에서는 다음을 조합합니다.

제어 역할
계정 단위 실패 횟수 하나의 계정에 대한 무차별 대입을 막는다
단계적 대기 시간 정당한 이용자의 입력 실수는 허용하면서 공격 속도를 늦춘다
발신지 IP・단말・ASN 등의 제어 다수의 계정에 소수 회씩 시도하는 공격을 억제한다
위험도 기반 판정 평소와 다른 지역・단말・속도를 강하게 제한한다
이용자에게 통지 공격이나 오조작을 알아챌 수 있게 한다
안전한 복구 절차 잠금을 해제하는 창구가 공격 경로가 되지 않게 한다

나아가 코드를 재전송했을 때 실패 횟수를 0으로 되돌려서는 안 됩니다. 공격자가 재전송 API를 호출할 때마다 시도 여력을 되살릴 수 있기 때문입니다. 현행 NIST SP 800-63B도 새로운 인증 비밀을 생성하더라도 실패 횟수를 재설정하지 말 것을 요구하고 있습니다4.

인증 코드는 한 번만 사용할 수 있게 한다

문제문에서는 유효 기한이 중심이지만, 실무에서는 다음도 필요합니다.

  • 성공한 코드는 즉시 무효화한다
  • 같은 코드의 재사용을 거부한다
  • 코드 자체를 로그에 남기지 않는다
  • 코드 대조의 성공・실패로부터 이용자의 존재 여부를 추측할 수 없는 응답으로 만든다
  • 코드 발송 API에도 횟수 제한을 둔다

짧은 비밀을 사용하는 이상, 무작위 생성만으로 안전성을 맡길 수는 없습니다.

인증 코드 대책자릿수・실효 시간에 더해 시도 횟수 제한・재사용 거부・통지 등으로 보호한다인증 코드자릿수를 늘린다유효 기한을 짧게 한다시도 횟수 제한성공 후 무효화재전송 시 실패 횟수를 재설정하지 않음코드를 로그에 남기지 않음발신지 단위 제어

그림11: 인증 코드 대책. 자릿수와 실효 시간뿐 아니라 시도 제어와 운용을 함께 조합한다.

10. 설문2의 네 가지를 한 장으로 구분하기

설문2에서 혼동하기 쉬운 논점을, 공격자가 제어한 값을 기준으로 정리합니다.

공격 공격자가 바꾼 값 신뢰해서는 안 됐던 부분 근본 대책
JWT 변조 JWT 헤더의 alg, 페이로드의 이용자 ID 토큰 스스로가 신고하는 검증 알고리즘 허용 알고리즘을 서버 측에서 고정한다
타인의 정보 취득 요청의 mid 클라이언트가 지정한 대상 ID JWT의 주체와 대조하거나, 대상 ID를 JWT로부터 정한다
유료 이용자로의 변경 사양 외 status 자동 바인딩된 모든 프로퍼티 갱신 가능한 프로퍼티를 허용 목록화한다
4자리 코드 돌파 otp의 후보 무제한 인증 시도 실패 횟수 제한, 지연, 위험도 판정을 넣는다

「입력값을 검증한다」는 말로 모든 것을 뭉뚱그리지 않는 것이 중요합니다.

  • alg는 암호 처리의 정책
  • mid는 오브젝트 단위 인가
  • status는 프로퍼티 단위 인가
  • otp는 온라인 추측에 대한 내성

같은 HTTP 요청 안에 있더라도, 지켜야 할 이유는 저마다 다릅니다.

11. 설문3(1) ── 파괴하지 않고 외부 코드 실행을 확인한다

서비스 시작 후, 널리 사용되는 오픈소스 라이브러리 H에서 중대한 취약점 V가 공표됩니다. 문제문의 흐름은 다음과 같습니다.

  1. 공격자가 JNDI Lookup을 포함한 문자열을 HTTP 헤더에 넣어 보낸다
  2. 공격 대상 서버가 그 값을 로그에 출력한다
  3. 취약한 라이브러리가 JNDI Lookup을 평가하여 공격용 LDAP 서버에 문의한다
  4. LDAP 응답이 공격용 HTTP 서버의 URL을 반환한다
  5. 공격 대상 서버가 클래스 파일을 취득하여 명령을 실행한다

이것은 고유 명칭을 감춘 Log4Shell(CVE-2021-44228)형 공격으로 읽을 수 있습니다. Apache의 설명에서도, 공격자가 로그 메시지나 파라미터를 제어할 수 있으면 LDAP 서버로부터 읽어들인 임의 코드를 실행할 수 있는 취약점으로 설명되어 있습니다9.

Log4Shell형 취약점 확인 흐름무해한 콜백으로 JNDI에서 외부 코드 실행까지의 연쇄가 통하는지 확인한다x-api-version에jndi/ldap 문자열 주입JNDI LookupHTTP URL 응답GET을 기록도달 확인공격자취약한 서버로그 처리악용 LDAP 서버악용 HTTP 서버index.html테스트 서버접근 로그취약점 확인

그림12: Log4Shell형 취약점 확인 흐름. 파괴적인 명령이 아니라 HTTP 접근 기록으로 도달 여부를 확인한다.

검증 코드는 무해한 HTTP 접근만 일으킨다

G사는 시스템에 영향을 주지 않는 검증 코드를 실행해, 외부에서 취약점 V를 악용할 수 있는지 확인합니다. 검증 코드가 수행하는 명령은 테스트 서버의 index.html을 취득하는 것뿐입니다.

설문3(1)은 테스트 서버에 무엇을 구현하면 명령이 실행되었음을 확인할 수 있는지를 묻습니다.

해답 예는 다음과 같습니다.

테스트 서버의 index.html에 대한 접근을 기록하고 확인하는 구조

웹 서버의 접근 로그에 공격 대상 서버로부터의 GET이 남아 있으면, 적어도 다음 연쇄가 통과했음을 확인할 수 있습니다.

외부 HTTP 요청
  -> 로그 처리
  -> JNDI Lookup
  -> LDAP 응답
  -> 클래스 취득
  -> 검증 명령 실행
  -> 테스트 서버로의 HTTP 접근

왜 「화면에 문자를 표시」하는 것만으로는 부족한가

공격 대상은 서버입니다. 이용자의 브라우저 화면에 변화가 나타난다는 보장은 없습니다. 또한 취약점이 존재하더라도, 도중의 외부 발신 통신이 방화벽에서 차단되는 경우가 있습니다.

테스트 서버 측에서 접근을 기록하면, 공격 대상 서버가 외부에 도달했다는 관측 가능한 증거가 됩니다.

실무에서 같은 종류의 검증을 수행할 때는 반드시 다음을 지킵니다.

  • 대상 시스템의 소유자로부터 명시적인 승인을 받는다
  • 운영 환경에 영향이 없거나 허용할 수 있는 검증 방법으로 한다
  • 쓰기・삭제・설정 변경 등의 파괴적인 명령을 사용하지 않는다
  • 검증용 도메인이나 서버를 자사에서 관리한다
  • 검증 시각, 발신지, 대상, 예상되는 콜백을 기록한다
  • 검증 후 임시 LDAP・HTTP 서버나 자격 증명을 철거한다

「임의 코드 실행을 확인하는」 것과 「임의의 위험한 코드를 실행하는」 것은 같지 않습니다. 목적을 달성하는 데 필요한 최소한의 부작용으로 그칩니다.

12. 설문3(2)(3) ── WAF는 HTTP 헤더를 검사한다

서비스 N의 WAF는 검사 대상으로 GET, POST, PUT, ANY, Header, COOKIE, Multipart를 선택할 수 있습니다.

공격 코드는 x-api-version이라는 HTTP 헤더의 값에 들어갑니다. 따라서 표6의 빈칸 e와 f는 둘 다 Header입니다.

본문에 적힌 위치를 그대로 WAF 검사 대상에 대응시킨다

여기서는 일반 지식보다 문제문의 데이터 흐름을 읽는 것이 관건입니다.

공격 문자열이 놓이는 위치:
  x-api-version 헤더
          |
          v
WAF의 검사 대상:
  Header

GET 파라미터도 POST 본문도 아닙니다. WAF의 기능 목록을 보고 「공격 같으니까 ANY」로 고르는 것이 아니라, 문제문에서 공격자가 값을 넣은 위치를 답합니다.

대소문자 치환에 대응한다

최초의 안은 개념적으로는 다음과 같은 규칙이었습니다.

Header  \Wjndi\W  차단
Header  \Wldap\W  차단

하지만 jNdI처럼 대소문자를 뒤섞으면, 소문자만으로 이루어진 단순한 패턴을 회피할 수 있습니다.

설문3(3)의 해답 예는 다음 중 하나입니다.

\W[jJ][nN][dD][iI]\W
\W(j|J)(n|N)(d|D)(i|I)\W

문제 책자에서는 백슬래시가 일본어 환경의 글리프상 원화 기호(¥)처럼 보이는 경우가 있지만, 정규 표현으로서는 \W입니다. \W는 영숫자와 밑줄(언더스코어) 이외의 문자와 일치합니다. JNDI Lookup 구문에서는 jndi의 앞뒤에 ${: 같은 비단어 문자가 나타나므로, 그것까지 포함해서 보고 있습니다.

같은 사고방식으로 ldap 쪽도 대소문자를 구분하지 않는 형태로 만들 수 있습니다.

\W[lL][dD][aA][pP]\W

이 정규 표현을 「완전한 Log4Shell 대책」으로 여기지 않는다

시험은 문제문에 제시된 회피 방법에 대응하는 정규 표현을 답하는 것입니다. 실제 공격에서는 문자열 분할, 다른 Lookup, 인코딩, 다른 프로토콜 등 시그니처만으로는 포괄하기 어려운 변형이 있을 수 있습니다.

따라서 실무에서의 자리매김은 다음과 같습니다.

  1. 현재 파악된 공격 패턴을 WAF로 잠정적으로 막는다
  2. 영향을 받는 라이브러리가 실제로 포함되어 있는지 조사한다
  3. 외부로 향하는 LDAP・RMI・불필요한 HTTP 통신을 제한한다
  4. 수정판으로 업데이트한다
  5. 업데이트 후에도 로그를 확인하여 침해 여부를 조사한다

WAF는 수정판이 나올 때까지 시간을 버는 계층입니다.

WAF의 자리매김WAF는 잠정 완화 계층이며, 근본 대책은 라이브러리를 수정판으로 업데이트하는 것이다WAF 규칙검지/차단외부 발신 통신 제한중대 취약점 공표영향 확인잠정 완화공격 패턴을 일시적으로 막는다악용 경로를 막는다수정판 라이브러리로 업데이트사후 확인과 재발 방지

그림13: WAF의 자리매김. WAF는 수정판이 나올 때까지 시간을 버는 것이며, 근본 대책은 업데이트다.

13. 설문3(4) ── 처음에 「검지」를 사용하는 이유

변경 후의 WAF 규칙에 대해, 등록 지원사인 Z씨는 운영 개시 후 일정 기간은 동작을 「차단」이 아니라 「검지」로 하도록 조언합니다.

설문은 검지로 설정하는 이점과, 피해를 최소화하기 위해 실시해야 할 내용을 각각 25자 이내로 묻습니다.

해답 예는 다음과 같습니다.

항목 해답의 요점
이점 오탐으로 인한 차단을 막을 수 있다
실시해야 할 내용 알림을 받으면 공격인지를 정밀 조사한다

검지 모드는 「아무것도 하지 않는 모드」가 아니다

검지 모드에서는 규칙에 일치한 통신을 통과시키면서, 로그에 기록하고 알림을 냅니다. 정상적인 API 호출 안에 우연히 jndildap이라는 문자열이 들어가는 경우에도, 곧바로 업무를 멈추지 않습니다.

그 대신 운용 측에는 다음이 필요합니다.

알림 수신
   |
   v
대상 요청을 확인
   |
   +-- 정상 통신 -> 규칙을 좁힌다, 예외 조건을 검토
   |
   +-- 공격      -> 대상을 격리, 로그 보전, 영향 조사, 차단으로

알림을 아무도 보지 않는다면, 검지 모드에는 방어 효과가 없습니다. 검지는 관측해서 판단하는 운용과 한 쌍입니다.

검지에서 차단으로 옮기는 흐름

일반적인 도입 절차는 다음과 같습니다.

  1. 검지 모드로 실제 트래픽에 적용한다
  2. 오탐과 정탐을 분류한다
  3. 대상 헤더, 경로, API, 문자 경계 등을 조정한다
  4. 정상 통신에 대한 영향이 허용 가능한지 확인한다
  5. 차단 모드로 옮긴다
  6. 차단 건수와 업무 영향을 모니터링한다

다만 이것은 평상시의 원칙입니다. 취약점이 중대하고, 실제로 악용되고 있으며, 대체 수단이 없는 경우에는, 오탐으로 인한 중단보다 침해 피해가 더 크다고 판단해 처음부터 차단하는 경우도 있습니다. 시험의 상황에서는 서비스를 지금까지와 다름없이 이용할 수 있는지 확인하기 위해, 우선 검지를 선택하고 있습니다.

WAF 검지에서 차단으로검지 모드에서 알림을 관측하고, 오탐을 조정한 후 차단 모드로 전환한다실제 트래픽오탐공격검지 모드알림 발생공격인가오탐인가규칙을 조정차단 모드로차단 건수와 업무 영향을 모니터링

그림14: 검지에서 차단으로. 먼저 관측・조정하고, 영향이 허용 가능함을 확인한 후 차단으로 옮긴다.

14. WAF는 잠정 대책, 업데이트가 근본 대책

문제문에서는 라이브러리 H의 공식 사이트에 수정판도 잠정 대책도 아직 없고, 클라우드 사업자의 포괄적인 WAF 규칙도 최대 72시간이 걸리는 상황이었습니다. 그래서 G사는 스스로 영향을 확인하고, 파악된 패턴만이라도 일시적으로 막습니다.

이 순서는 인시던트 대응의 기본형입니다.

단계 목적 이 문제에서의 대응
영향 확인 자사가 실제로 위험한지 판단한다 무해한 콜백으로 외부 악용 가능 여부를 확인
잠정 완화 수정까지의 시간을 번다 WAF 규칙, 검지・차단, 외부 발신 통신 제한
근본 수정 취약한 원인을 제거한다 수정판 라이브러리로 업데이트
사후 확인 이미 악용되지 않았는지 조사한다 WAF・애플리케이션・DNS・프록시 등의 로그 조사
재발 방지 다음 판단을 빠르게 한다 의존 관계 목록화, SBOM, 업데이트 절차, 연락 경로

「사용하는지 모른다」가 가장 큰 지연 요인이 된다

문제문에서는 G사가 F사에 라이브러리 H를 사용하는지 문의해도, 상세한 구성 분석이 필요해 답변에 시간이 걸린다고 되어 있습니다.

실무에서는 중대한 취약점이 공표된 후에야 비로소 JAR 파일을 찾기 시작하면 대응이 늦어집니다. 적어도 다음은 평상시부터 갖추어 두어야 합니다.

  • 직접 의존과 전이 의존의 목록
  • 배포물에 실제로 포함된 컴포넌트와 버전
  • 어느 서비스・컨테이너・단말에 배포되어 있는가
  • 의존 라이브러리를 업데이트해 재빌드・재배포하는 절차
  • 긴급 변경을 승인하는 연락 경로
  • 외부 발신 통신의 허용 대상과, 중단했을 때의 영향
  • 로그의 보존 위치와 검색 방법

SBOM은 목적이 아닙니다. 「이 취약점이 어느 실행 중인 시스템에 영향을 미치는가」를 단시간에 답하기 위한 색인입니다.

업데이트만으로 조사를 끝내지 않는다

취약점 공표 전후로 이미 공격을 받았을 가능성이 있습니다. 수정판으로 업데이트해 앞으로의 악용을 막더라도, 이미 침해된 자격 증명이나 이미 설치된 백도어까지 사라지는 것은 아닙니다.

Log4Shell형이라면 적어도 다음 관점을 조사합니다.

  • JNDI나 LDAP을 나타내는 수상한 문자열을 포함한 HTTP 요청
  • 애플리케이션 서버에서 외부 LDAP・RMI・HTTP로의 통신
  • 평소와 다른 자식 프로세스의 실행
  • 수상한 JAR, class, 스크립트, 실행 파일의 생성
  • 클라우드 자격 증명이나 환경 변수에 대한 접근
  • 업데이트 전후의 인증・권한 변경・외부 송신

WAF 로그만으로 「공격받지 않았다」고 단정하지 않는 것이 중요합니다. WAF를 거치지 않는 내부 경로나, 과거에 보존되지 않은 로그가 있기 때문입니다.

15. 시험에서 점수를 따기 쉬운 읽는 법

이 문제는 지식 문제라기보다, 사양과 구현의 차이를 읽는 문제입니다.

15.1 표의 「사양」과 「구현」을 구분한다

status 문제에서는 API 사양에 없는 값이 구현에서는 통과하고 있습니다.

사양:
  mid / name / age

구현:
  받은 파라미터를 모두 P로 보낸다

이 차이가 보이면 빈칸 c가 공통 모듈 P임을 알 수 있습니다.

15.2 공격자가 변경한 값에 선을 긋는다

각 공격에서 변경된 값은 다음과 같습니다.

  • JWT 헤더의 alg
  • JWT 페이로드의 이용자 ID
  • API 파라미터의 mid
  • 사양 외의 status
  • 인증 API의 otp
  • HTTP 헤더의 x-api-version

설문은 거의 모두 「그 값이 어디서 검증되어야 하는가」를 묻고 있습니다.

15.3 해답은 문제문의 용어로 되돌린다

실무에서는 「BOLA」, 「Mass Assignment」, 「rate limiting」이라 부를 수 있습니다. 하지만 설문이 요구하는 것은 문제문의 구성에 맞춘 구체적인 처리입니다.

나쁜 예:

인가를 적절히 수행한다.

좋은 예:

JWT에 포함된 이용자 ID가 mid의 값과 일치하는지 검증한다.

나쁜 예:

브루트포스 대책을 세운다.

좋은 예:

연속 실패 횟수가 임곗값을 초과하면 계정을 잠근다.

추상적인 명칭을 알고 있는 것만으로는 글자 수 안에서 채점 가능한 답이 되지 않습니다.

15.4 WAF는 「어디에 들어갔는가」를 추적한다

WAF의 검사 대상은 공격 종류로부터 추측하는 것이 아니라, 공격 문자열이 담긴 위치로부터 정합니다.

x-api-version 헤더에 넣었다
        ↓
검사 대상은 Header

채점 강평에서 설문3(1)의 정답률이 다소 낮았던 것도, 그림6의 공격 흐름에 맞지 않는 답이 많았기 때문입니다. 공격 절차를 화살표로 다시 적어 보는 것만으로도 무엇을 관측해야 하는지가 보입니다.

16. 실무 API 리뷰에서 사용할 수 있는 체크리스트

이 문제를 실제 설계・코드 리뷰로 가져가기 위한 체크리스트입니다.

JWT 검증

  • 허용할 서명 알고리즘을 서버 설정에서 고정하고 있다
  • none이나 예상 밖의 알고리즘을 거부한다
  • 서명, iss, aud, exp, nbf를 용도에 맞게 검증한다
  • ID 토큰, 액세스 토큰, 리프레시 토큰을 혼동하지 않는다
  • 키 로테이션과 실효 시의 절차가 있다
  • JWT 페이로드에 비밀로 해야 할 정보를 넣지 않는다

오브젝트 단위 인가

  • 요청 안의 ID를 바꾸었을 때 타인의 데이터에 도달할 수 없다
  • 목록, 상세, 갱신, 삭제, 다운로드 전부에서 인가하고 있다
  • 인가는 화면이 아니라 데이터에 도달하는 공통 계층에서 실시하고 있다
  • 자기 전용 API에서는 대상 ID를 토큰으로부터 도출할 수 없는지 검토했다
  • 관리자용 조작은 일반 이용자용 API와 정책을 나누고 있다

프로퍼티 단위 인가

  • 외부 입력 타입과 데이터베이스 엔티티를 나누고 있다
  • 갱신 가능한 항목을 허용 목록으로 열거하고 있다
  • 사양 외의 프로퍼티를 거부하거나 감사하고 있다
  • 권한, 과금, 승인, 소유자 등의 상태를 이용자 입력으로부터 변경할 수 없다
  • 응답에도 불필요한 기밀 프로퍼티를 포함하지 않는다

인증 시도

  • 계정 단위의 실패 횟수 제한이 있다
  • 단계적 지연이나 발신지 단위의 제어가 있다
  • 코드를 재발급해도 실패 횟수가 재설정되지 않는다
  • 인증 코드는 한 번만 사용할 수 있다
  • 인증 코드나 비밀번호를 로그에 남기지 않는다
  • 잠금 해제・복구 절차가 또 다른 취약한 인증 경로가 되지 않는다

중대한 의존 라이브러리 취약점

  • 실행 중인 서비스와 의존 버전을 대응시킬 수 있다
  • 무해한 방법으로 영향을 검증하는 절차가 있다
  • WAF나 외부 발신 통신 제한 등의 잠정책을 적용할 수 있다
  • 검지 알림을 담당자가 확인하는 운용이 있다
  • 수정판으로 업데이트하는 긴급 릴리스 경로가 있다
  • 업데이트 전에 악용되었을 가능성을 로그로부터 조사한다

17. 이 문제에서 드러나는 공통 부품의 양면성

이 문제에서는 JWT 관리 라이브러리 Q와 공통 모듈 P라는 두 개의 공통 부품이 등장합니다.

공통 부품에는 큰 이점이 있습니다.

  • 한 곳을 고치면 이용하는 모든 API에 수정을 반영할 수 있다
  • 인가나 검증의 구현을 각 기능마다 중복시키지 않아도 된다
  • 테스트 대상을 집약할 수 있다
  • 로그나 감사의 형식을 통일할 수 있다

반면, 실수 역시 전체로 퍼집니다.

  • 라이브러리 Q가 alg=none을 받아들이면, JWT를 사용하는 모든 API가 위험해진다
  • 공통 모듈 P가 임의의 midstatus를 받아들이면, GET과 PUT 양쪽이 위험해진다
  • 취약한 라이브러리 H가 기반에서 사용되면, HTTP 헤더를 로그에 남기는 여러 경로가 공격 표면이 된다

따라서 공통화해야 할 것은 단순한 데이터 접근이 아닙니다. 보안 불변 조건을 공통화하고, 그 공통 부품을 단독으로 엄격하게 검증할 필요가 있습니다.

예를 들어 P의 계약을 다음과 같이 합니다.

P.getOwnUser(authenticatedSubject)
P.updateOwnProfile(authenticatedSubject, ProfileUpdate{name, age})

다음과 같은 저수준 API를 일반 호출 측에 그대로 공개하지 않는 편이 안전합니다.

P.getUser(arbitraryMid)
P.updateUser(arbitraryMap)

후자가 필요한 것은 관리 처리 등 한정된 경로뿐입니다. 저수준의 자유도를 모든 API에 나누어 주면, 각 호출 측이 매번 올바르게 사용할 것을 기대하는 설계가 되어 버립니다.

18. 정리

2024년 봄(레이와 6년) 오후 문제1은, API 보안의 논점을 하나씩 분리해서 읽는 문제입니다.

JWT를 사용하고 있다는 것이 안전한 인증을 의미하지는 않습니다. 서명 알고리즘을 공격자가 고르게 하면 이용자 ID를 고쳐 쓸 수 있습니다.

JWT의 서명이 올바르다는 것이 올바른 인가를 의미하지는 않습니다. 요청의 mid를 신뢰하면 정당한 이용자가 타인의 정보에 접근할 수 있습니다.

자신의 오브젝트를 갱신할 수 있다는 것이 모든 프로퍼티를 변경해도 된다는 것을 의미하지는 않습니다. status와 같은 내부 상태를 자동 바인딩하면 권한이나 과금 상태를 고쳐 쓸 수 있습니다.

인증 코드에 유효 기한이 있다는 것이 무차별 대입에 강하다는 것을 의미하지는 않습니다. 후보 수와 시도 속도를 계산해 실패 횟수를 제한할 필요가 있습니다.

WAF에 규칙을 넣는다는 것이 취약점을 수정했다는 것을 의미하지는 않습니다. 검지와 차단으로 시간을 벌고, 영향을 확인하며, 최종적으로는 라이브러리를 업데이트합니다.

취약점과 대책의 대응표각 취약점과 그에 대한 신뢰 경계, 대책을 대응시킨다토큰 검증오브젝트 단위 인가프로퍼티 단위 인가인증 시도 제어입력에서 실행으로JWT 변조허용 알고리즘을 고정mid 치환JWT 주체와 대조/mid 불필요status=paid갱신 DTO를 허용 목록화4자리 코드 무차별 대입실패 횟수 제한・지연Log4Shell형 취약점라이브러리 업데이트/WAF

그림15: 취약점과 대책 대응표. 무너진 경계별로 대책을 나눈다.

이 문제를 관통하는 원칙은 하나입니다.

바로 앞 검증에 성공했다는 사실을, 다음 신뢰 경계를 생략하는 이유로 삼지 않는다.

이 시리즈의 이전 글에서는 2023년 가을(레이와 5년) 오후 문제1의 저장형 XSS와, 2023년 가을(레이와 5년) 오후 문제2의 방문객용 Wi-Fi를 통한 정보 유출을 다루었습니다. 웹사이트 전체의 점검 관점은, IPA 「안전한 웹사이트 만드는 법」을 체크리스트로 활용하기도 참고해 주세요.

최종 정리바로 앞 검증에 성공했더라도 다음 신뢰 경계를 생략하지 않는다는 것을 보여준다인증 성공JWT 서명 검증오브젝트 단위 인가프로퍼티 단위 인가시도 횟수 제한입력에서 실행으로의 경계WAF/라이브러리 업데이트

그림16: 최종 정리. 신뢰 경계는 단계적으로 확인하며, 어느 하나도 생략해서는 안 된다.

참고 링크

  1. IPA, 2024년도 봄(레이와 6년도) 정보처리안전확보지원사 시험 오후 문제 책자. 본 기사가 다루는 문제문입니다. 

  2. IPA, 2024년도 봄(레이와 6년도) 정보처리안전확보지원사 시험 오후 해답 예. 각 설문의 공식 해답 예입니다. 

  3. IPA, 2024년도 봄(레이와 6년도) 정보처리안전확보지원사 시험 오후 채점 강평. 정답률과 오답 경향에 대한 설명입니다. 

  4. NIST, SP 800-63B: Authentication and Authenticator Management. 단기 비밀의 자릿수, 시도 횟수 제한, 재발급 시의 실패 횟수, 이메일을 대역외 인증에 사용하지 않는 것 등을 제시합니다.  2

  5. RFC Editor, RFC 7519: JSON Web Token (JWT). Unsecured JWT와 alg=none을 포함한 JWT의 사양입니다. 

  6. RFC Editor, RFC 8725: JSON Web Token Best Current Practices. 허용 알고리즘의 고정, 발행자・주체・audience의 검증 등을 정한 BCP입니다. 

  7. OWASP, API1:2023 Broken Object Level Authorization. 이용자가 지정하는 오브젝트 ID마다 인가를 확인해야 할 필요성을 설명합니다. 

  8. OWASP, API3:2023 Broken Object Property Level Authorization. Mass Assignment를 포함해, 프로퍼티 단위 인가 결함과 대책을 설명합니다. 

  9. Apache Logging Services, Security. CVE-2021-44228의 영향, JNDI와 LDAP을 통한 코드 실행, 수정판을 설명합니다. 

같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.

정보처리안전확보지원사(情報処理安全確保支援士) 2023년 가을(레이와 5년) 오후 문제1 해설 ── 16건의 리뷰가 2건만 표시되는 저장형 XSS

정보처리안전확보지원사 시험 2023년 가을(레이와 5년) 오후 문제1을 소재로, 저장형 XSS 공격의 흐름을 해설합니다. 문자 수 제한이 분할 게시로 깨진 이유, 세션ID가 외부로 전송되지 않고 이미지로 반출된 경로, 그리고 거기서 효과가 있었던...

이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.

이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.

웹사이트 제작

회원용 API나 스마트폰 연동에서는 JWT 검증, 오브젝트 단위 인가, 변경 가능한 프로퍼티 제한이 그대로 웹 시스템의 안전성으로 직결되기 때문입니다.

자주 묻는 질문

이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.

JWT 서명 검증에 성공했는데, 왜 다른 사람으로 위장할 수 있나요?
이 문제에서는 JWT 관리 라이브러리가 JWT 헤더의 alg를 공격자가 지정한 대로 그대로 받아들여, alg=none인 JWT를 서명 없이도 올바른 것으로 취급했습니다. 따라서 페이로드의 이용자 ID를 고쳐 써도 검증에 성공합니다. 시험의 해답은 검증 대상을 JWT 헤더의 alg로 하고, 그 값이 NONE이 아님을 확인하는 것입니다. 다만 실무에서는 NONE만 거부하는 것으로는 충분하지 않습니다. 서버 측 설정으로 허용할 알고리즘을 RS256 등으로 고정하고, 토큰이 스스로 신고한 알고리즘을 그대로 선택에 사용하지 않도록 합니다. 나아가 issuer, audience, 유효기한, subject 등도 용도에 맞게 검증합니다.
JWT 안의 이용자 ID와 요청의 mid를 비교하면 인가 대책으로 충분한가요?
이 문제의 해답으로서는 충분합니다. 공통 모듈 P에서 JWT에 포함된 이용자 ID와 mid가 일치하는지 검증하면, 타인의 mid를 지정하는 공격을 막을 수 있습니다. 다만 자기 자신의 정보만 다루는 API라면, 실무에서는 mid를 클라이언트로부터 받지 않고 검증이 끝난 JWT의 subject로부터 이용자 ID를 정하는 설계가 더 안전합니다. 예를 들어 GET /users/me나 PUT /users/me로 하면, 비교 처리를 구현에서 빠뜨리는 실수 자체를 일으키기 어렵게 만들 수 있습니다. 관리자가 다른 사람을 조작하는 API는 별도의 엔드포인트와 인가 정책으로 나눕니다.
status를 추가하는 공격은 왜 일반적인 입력값 검증만으로는 막을 수 없나요?
name의 길이나 age의 범위를 검증하더라도, 애초에 받아서는 안 될 status를 자동 바인딩하여 내부 오브젝트로 넘기고 있다면 막을 수 없기 때문입니다. 문제는 값의 형식이 아니라, 그 프로퍼티를 이용자가 변경해도 되는가라는 프로퍼티 단위의 인가입니다. 갱신용 입력 타입에는 name과 age만 정의하고, 알 수 없는 프로퍼티는 거부합니다. 과금 상태는 결제 서비스의 성공 결과 등 서버가 신뢰할 수 있는 이벤트로부터만 변경해야 합니다.
4자리 인증 코드는 10분이면 만료되는데, 왜 위험한가요?
0000부터 9999까지의 후보는 1만 가지뿐이며, 1초에 10회 시도할 수 있다면 평균 5,000회, 즉 500초면 맞히기 때문입니다. 유효 기간인 10분은 600초이므로, 중복되지 않는 후보를 순서대로 시도하면 유효 기간 안에 6,000가지를 확인할 수 있습니다. 만료 시간만으로는 무차별 대입을 막을 수 없습니다. 후보 수, 시도 속도, 시도 횟수 상한을 함께 설계해야 합니다.
설문의 대책은 계정 잠금이지만, 실무에서도 즉시 잠금만으로 충분한가요?
아닙니다. 문제의 빈칸에는 「연속 실패 횟수가 임곗값을 초과하면 계정을 잠그는 처리」가 들어가지만, 고정적인 영구 잠금만으로는 공격자가 고의로 타인의 계정을 잠그는 서비스 방해를 일으킬 수 있습니다. 실무에서는 계정 단위의 실패 횟수, 단계적인 대기 시간, 발신지나 단말의 위험도 판정, 통지, 복구 절차를 조합합니다. 새 코드를 발급해도 실패 횟수를 0으로 되돌리지 않는 것도 중요합니다.
WAF를 차단이 아니라 검지로 설정하는 의미는 무엇인가요?
정상적인 문자열을 잘못 공격으로 판정했을 때도 업무 통신을 멈추지 않고 넘어갈 수 있다는 점입니다. 문제의 해답에서는 이점이 「오탐에 의한 차단을 막을 수 있다」는 것이고, 실시해야 할 내용이 「알림을 받으면 공격인지를 정밀 조사한다」는 것입니다. 검지 모드는 방치하기 위한 설정이 아닙니다. 로그를 확인해 오탐을 걸러내고, 규칙을 조정하고, 차단으로 옮기기 위한 관찰 기간으로 사용합니다. 이미 알려진 중대 취약점이 실제로 악용되고 있는 긴급 상황에서는, 가용성 리스크와 비교해 먼저 차단하는 판단도 있을 수 있습니다.
이 문제의 라이브러리 H는 Log4j인가요?
문제문은 제품명을 밝히지 않지만, JNDI Lookup, LDAP 서버, HTTP 서버로부터의 클래스 취득, HTTP 헤더에 담긴 문자열, CVSS v3.1의 높은 기본값이라는 공격 흐름은, Log4Shell로 알려진 CVE-2021-44228을 추상화한 것으로 읽는 것이 자연스럽습니다. 본 기사에서는 그 대응 관계를 설명하지만, 시험에서는 고유 명칭을 답할 필요가 없습니다. 주어진 공격 절차와 WAF 사양만으로 해답을 낼 수 있습니다.
이 문제에서 실무로 가져가야 할 것은 무엇인가요?
인증에 성공한 것, JWT가 변조되지 않은 것, 대상 오브젝트에 접근해도 되는 것, 대상 프로퍼티를 변경해도 되는 것은 모두 별개의 확인이라는 점입니다. 더불어 짧은 인증 코드에는 시도 횟수 제한이 필요하며, 중대한 라이브러리 취약점에서는 영향 확인, 잠정 방어, 근본 수정을 병행해서 진행합니다. 공통 부품에 인가를 집약하는 것, 입력 스키마를 허용 목록으로 만드는 것, JWT의 검증 조건을 서버 측에서 고정하는 것, 의존 라이브러리를 파악해 갱신할 수 있는 상태로 유지하는 것이 실무의 핵심입니다.

저자 프로필

기사 저자의 프로필 페이지입니다.

Go Komura

합동회사 코무라소프트 대표

Windows 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.

블로그 목록으로 돌아가기