개요

  • PKCE(픽시)의 개념에 대해 정리한다.
  • PKCE를 사용하면 뭐가 좋아지는지 정리한다.
  • 실제 HTTP 요청응답에서는 어떤 모양인지도 정리한다.

PKCE(픽시)란?

  • RFC7636으로 정의되어 있다. (그렇게 길지 않다. 시간되면 읽어보자.)
  • Authorization Code flow 의 확장팩이다.
  • Authorization Code Grant를 사용하는 OAuth 2.0 public client는 인가 코드 가로채기 공격(the authorization code interception attack)에 취약한데 이 부분을 해결하기 위해 나온 방법이다.
  • OAuth 2.0에서는 옵션이지만 OAuth 2.1부터는 필수가 되는 것 같다. 참고로 OAuth 2.1은 2024년 12월 시점에도 검토중인 상태이다. (https://oauth.net/2.1/)

인가 코드 가로채기 공격 (The Authorization Code Interception Attack)

인가 코드 가로채기 공격의 구조도이다. 스마트폰과 같은 디바이스에서 앱스토어를 통해 설치된 악의적인 어플리케이션이 다른 어플리케이션(OAuth 2.0 App) 으로 발행된 인증코드를 가로채는 공격이다.

인증코드가로채기공격
(출처: https://datatracker.ietf.org/doc/html/rfc7636)

PKCE를 사용한 인증 흐름

PKCE를 사용한 인증 흐름

  • OAuth 클라이언트가 인가 코드를 요청할 때 자신이 만든 특정한 값(code_verifier)를 함께 인가서버로 보낸다.
  • 인가 서버로부터 인가 코드(Authz code)를 받은 후에 억세스 토큰을 요청할 때도 code_verifier를 함께 보낸다.
  • 토큰 발행 서버는 code_verifier 값을 함께 확인해서 정당한 값일 때만 토큰을 발행한다.
  • 이렇게 하면 뭐가 좋아지는가? 설령 제삼자 어플리케이션이 인가 코드를 얻어내도, code_verifier는 이 값을 발행한 정당한 원래의 OAuth 클라이언트 밖에 모르기 때문에 결과적으로 억세스 토큰를 얻어낼 수 없다!

보안상 의의, 중요 포인트

  • 문제가 있을 경우 인가 토큰을 받은 후에 액세스 토큰을 발행하는 곳에서 플로우를 멈출 수 있다.
  • 정보보안 CIA관점에서 말하자면 기밀성(Confidentiality)이 강해진다고 볼 수도 있겠다.
  • 픽시는 OAuth 토큰을 발행할 때까지의 흐름을 안전하게 지키기 위한 구조이다. 거꾸로 말하면 이미 발행된 토큰을 안전하게 보관하는 것은 PKCE와 상관이 없는 다른 문제이다.
  • PKCE는 본래 모바일 앱을 위해 개발된 기능이지만 서버 앱들도 모두 적용하도록 권장하고 있다. 이는 인가 코드 주입(Authorization Code Injection) 공격에 대한 방어도 되기 때문이다.

PKCE를 사용한 흐름

  1. 사용자가 App에 사용 요청을하면 App이 Code Verifier(Secret) 라고 부르는 길이 43~128 의 랜덤한 문자열을 만든다.

  2. App이 Code Verifier를 해시 함수(ex sha256)를 통과시켜서 해시 값을 만든다. 이 것을 Code Challenge (Public Hash)라고 부른다. 이 것을 Base64인코딩해서 사용자에게 건네준다.

  3. 사용자가 Auth 서버의 인증 엔드포인트(보통 /auth)로 요청을 보낸다.
    • 이 때 response_type 파라메터의 값은 code이다. 인가코드를 얻기 위함을 나타낸다.
    • client_id로 CLIENT_ID를 지정하고, redirect_uri로 사용자가 인증한 후에 돌아올 URL(앱 서버의 URL)을 지정한다. scope은 API가 접근할 scope을 지정한다.
    • state 파라메터는 원래 CSRF보호를 위해서 사용되었지만 현재는 PKCE가 CSRF보호를 제공한다. (서버가 PCKE를 지원하지 않는다면 state는 랜덤한 값을 지정해야 한다.)
    • code_challenge 파라메터를 포함한다. 2번과정에서 만든 값이다.
    • code_challenge_method 파라메터는 해시함수명을 지정한다. 여기서는 s256 이다.
  4. 제대로 처리 되었다면 사용자는 App으로 리다이렉트된다. 이 리다이렉트URL에는 Auth 서버가 발생한 인가코드와 state값이 포함되어 있다. https://exmaple-app.com/redirect?code=AUTH_CODE_HERE&state=XXXXX 이 때 App은 인가코드 요청에 보냈던 state값이 이 state값이 일치하는지 체크해야 한다. CSRF공격을 막기 위함이다.

  5. App은 Auth 서버에게 억세스 토큰을 요청한다. 이는 백채널로 이루어진다. 일반적으로 유저는 볼 수 없다. (HTTP 통신을 관찰할 수 있다면 가능하다)
POST https://authorization-server.com/token

grant_type=authorization_code&code=AUTH_CODE_HERE&redirect_uri=REDIRECT_URI&code_verifier=VERIFIER_STRING(PKCE를사용하는경우)&client_id=CLIENT_ID&client_secret=CLIENT_SECRET
  • 이 과정에서는 App에 의해 code_verifier가 Auth 서버로 전달된다. (※ 3번과정에서는 code_verifier의 해시값인 code_challenge가 사용자에 의해서 Auth 서버로 전달되었다. Auth 서버는 code_verifier의 해시값을 구해서 code_challenge와 일치하는지를 확인하는 것으로 2번과정의 App과 현재 요청을 보낸 App이 동일한 App인지 판단한다)
  • 또한, client_id와 client_secret을 보냄으로서 Auth 서버는 이 클라이언트가 실재한다(정당한 클라이언트로부터의 요청이다)는 것을 알 수 있다.
  1. 정상적으로 처리되었다면 다음과 같은 응답이 반환된다.
{
    "token_type": "Bearer",
    "access_token": "Rsejrij3doijfooV3IOa",
    "expires_in": 3600,
    "scope": "photos",
    "refresh_token": "klAHEIRJIODLE44"
}

추기: state와 PKCE에서 제공하는 CSRF대책의 차이

둘다 CSRF 보호를 제공하는데 어떻게 다른가?

  • state에 의한 검증에서는 OAuth클라이언트(앱)측에서 자신이 최초에 인가 서버에 제출한 state와, 지정한 redirect_uri에 의해 되돌아온 요청의 state가 동일한지를 체크한다. 클라이언트가 OAuth서버를 검증하는 것이다.
  • PKCE를 이용한 검증에서는 OAuth 서버가 클라이언트를 검증한다. (정당한 App이 보내온 code_challenge 값과 code_challenge_method 값을 토대로 나중에 억세스 토큰 달라고 요청이 온 App이 제출한 code_verifier를 검증한다.)
  • 결과적으로는 비슷하지만 다층방어의 개념에서 보면 양쪽 모두 제대로 구현하는 게 좋아보인다.

참고 링크

  • OAuth grant types 정리: Burp Academy-OAuth grant types
  • https://www.ssemi.net/what-is-the-oauth2/
  • https://juniortech.tistory.com/15
  • https://qiita.com/TakahikoKawasaki/items/185d34814eb9f7ac7ef3
  • https://www.oauth.com/playground/authorization-code-with-pkce.html
  • https://datatracker.ietf.org/doc/html/rfc7636
  • https://en.wikipedia.org/wiki/OAuth
  • state와 PKCE차이: https://qiita.com/ist-n-m/items/992c67b803ff460818ec