한국어
  • 보안
  • oauth
  • PKCE

간단한 OAuth 보안 재확인

OAuth에 의해 사용되는 보호 방안에 어느 정도로 정통하십니까? 시스템이 OAuth의 오픈 표준을 준수하고 있습니까? 사용자 인증 프로세스의 구현 중에 발생할 수 있는 잠재적 위험을 인식하고 계십니까? OAuth에 관해 배운 내용을 간단히 되짚어 보겠습니다.

Simeng
Simeng
Developer

소개

몇 일 전, 흥미로운 OAuth 취약점 관련 기사가 우리에게 도달했습니다. SALT 랩에서 제공하는 A-new-oauth-vulnerability-that-may-impact-hundreds-of-online-services. 이 특정 게시물은 OAuth 및 기타 기능을 구현하는데 널리 사용되는 프레임워크인 Expo에서 발견된 취약점을 강조합니다. 이는 특히 expo-auth-session 라이브러리에서 발견된 취약점을 다루며, 이는 이미 지정되고 적절히 해결되었습니다.

만약 당신이 OAuth에 관심이 있거나 우리와 같은 CIAM 관련 제품을 작업 중이라면, 이 기사를 읽어 보는 것을 강력히 추천합니다. 이는 상당히 자극적이며 유용한 통찰력을 제공합니다. 이러한 화이트햇 리포트는 가장 단순한 기능조차 취약점을 야기할 수 있다는 것을 상기시켜 줍니다. 사이버 보안과 인증과 관련하여, 우리는 사용자 정보의 보안과 개인 정보 보호를 확보하기 위해 너무나 조심스러울 수는 없습니다. 이 기사가 당신의 관심을 끈다면, 당신은 우리가 강력히 동의한다고 확신합니다.

처음 시작했을 때를 생각하게 합니다. 우리는 OAuth 및 OIDC 프로토콜의 세부 사항을 배우고 연구하는 데 많은 시간을 보냈습니다. 아픈 경험이었지만 보상을 알다 시피 막대했습니다. 팀의 모든 사람이 OAuth 전문가라고는 할 수 없지만, 모든 사람들은 보안과 철저함에 대한 지속적인 노력을 기울이는데 전념하고 있다는 것입니다. 이러한 헌신적인 노력 덕분에 Logto 제품은 현재의 모습까지 발전하게 되었습니다.

이 좋은 기회를 활용하여, 우리는 OAuth의 보안 세부 사항 중 일부를 여기서 다시 한 번 살펴보고 싶습니다.

OAuth 인증 코드 흐름 간략 설명

OAuth 2.0은 다양한 클라이언트 유형과 요구 사항에 맞게 변화되는 다양한 인증 흐름을 제공합니다. 이에는 Implicit Flow, Client Credentials Flow, Resource Owner Password Credentials Flow, 및 Device Authorization Flow 등이 포함됩니다. 하지만, 인증 코드 흐름은 가장 안전하고 널리 사용되는 흐름으로 손꼽힙니다. 다른 흐름들과는 달리, 이는 사용자 인증을 클라이언트 응용 프로그램에서 분리하고 인증 코드를 토큰으로 교환하는 과정을 포함합니다. 이 접근법은 민감한 토큰들이 클라이언트에 노출되는 것을 막는 추가적인 보안 계층을 제공합니다. 또한, 인증 코드 흐름은 서버 측 토큰 관리를 지원하여 처방성 안전성이 필요한 웹 응용 프로그램에 적합한 사용자 접근 제어를 제공합니다.

다음은 가장 단순한 Authorization Code Flow 다이어그램입니다:

인증 코드 인증 흐름에서 이루어지는 두 가장 중요한 요청과 이들 내부에서 중요한 역할을 하는 부수적인 요소들을 살펴봅시다.

인증 엔드포인트:

토큰 교환 엔드포인트:

클라이언트 자격 증명

OAuth에서, Client Credentials은 클라이언트 애플리케이션이 자신을 인증 서버에 인증하고 식별하는 데 사용되는 자격 증명을 참조합니다. 이 자격 증명은 클라이언트 등록 프로세스 중에 얻으며, 클라이언트가 인증 서버에 요청을 할 때 클라이언트의 신원을 확인하는 데 사용됩니다. (애플리케이션을 처음 등록했을 때 Logto의 Admin 콘솔에서 클라이언트 자격 증명을 찾을 수 있습니다.)

클라이언트 자격 증명은 일반적으로 두 가지 구성 요소로 구성됩니다:

  1. 클라이언트 ID: 인증 서버에 의해 클라이언트 애플리케이션에 부여된 고유 식별자입니다. 이는 일반적으로 민감하다고 여겨지지 않는 공개 값입니다.
  2. 클라이언트 Secret: 클라이언트와 인증 서버만 알 수 있는 기밀이며 안전하게 저장된 값입니다. 이는 클라이언트 애플리케이션의 인증 수단으로 사용되며, 클라이언트가 인증 서버에 요청을 할 때 클라이언트의 신원을 확인하는 데 사용됩니다.

눈치채셨겠지만, 클라이언트 ID와 클라이언트 Secret 조합은 클라이언트를 인증하고 액세스 토큰을 획득하기 위해 토큰 요청 중에 사용됩니다.

클라이언트 자격 증명은 OAuth 흐름의 보안을 보장하는 데 중추적인 역할을 합니다. 인증 서버는 클라이언트 애플리케이션이 진짜인지 검증하고 보호된 자원에 대한 접근을 제어하는 데 이를 이용합니다. 클라이언트 자격 증명을 안전하게 처리하고 무단 접근으로부터 보호하는 것이 중요합니다. Logto는 두 가지 다른 보안 수준에 따라 클라이언트 애플리케이션을 분류합니다:

  • 기밀 클라이언트: 이에는 서버 렌더링 웹 애플리케이션 및 투 머신 (M2M) 애플리케이션이 포함됩니다. 기밀 클라이언트의 경우, 모든 인증 관련 자격 증명, 클라이언트 자격 증명을 포함하여서는 안전하게 서버에 저장됩니다. 또한, 모든 중간 교환 요청들은 데이터의 기밀성을 확보하기 위해서 암호화됩니다. 기밀 클라이언트의 경우 클라이언트 자격 증명 유출 위험이 매우 낮아, 본질적으로 더 안전하다고 볼 수 있습니다. 따라서, 기밀 클라이언트는 기본적으로 더 높은 보안 수준으로 간주됩니다. 토큰 교환 흐름에서는 Client Secret을 제출하는 것이 필수적입니다.
  • 공개 클라이언트: 이에는 싱글 페이지 웹 애플리케이션 (SPA) 및 네이티브 애플리케이션 등이 포함됩니다. 공개 클라이언트의 경우, 클라이언트 자격 증명은 일반적으로 클라이언트 측에 하드 코딩되어 있습니다. 즉, JavaScript 패키지 내부나 네이티브 플랫폼의 앱 패키지 등에 저장되어 있습니다. 이에 따라 클라이언트 자격 증명이 노출되는 위험이 기밀 클라이언트에 비해 더 높아집니다. 토큰 교환 흐름에서는 Client Secret을 제출하는 것이 OPTIONAL입니다. Logto는 기본적으로 공개 클라이언트에서 오는 자격 증명을 신뢰하지 않습니다.

State

OAuth 흐름에서 state 매개변수는 클라이언트가 인증 서버에 보내는 인증 요청에 포함된 임의로 생성된 값입니다. 그 목적은 인증 과정을 통한 클라이언트의 요청의 상태 또는 맥락을 유지하는 것입니다.

state 매개변수는 cross-site request forgery (CSRF) 공격을 예방하기 위한 보안 조치로 작동합니다. 인증 서버가 인증 및 권한 부여 후 사용자를 클라이언트 애플리케이션으로 다시 리다이렉트할 때, 이는 응답에 동일한 상태 값을 포함합니다. 클라이언트 애플리케이션은 MUST는 이 값을 인증 요청에서 보낸 원래 state 값과 비교해야 합니다.

상태 매개변수를 검증함으로써, 클라이언트는 인증 서버에서 받은 응답이 본래 보냈던 초기 요청에 해당하는 것임을 확인할 수 있습니다. 이는 공격자가 다른 사용자 또는 애플리케이션을 위해 의도된 응답을 받아들이도록 클라이언트를 속이려하는 공격을 방지하는 데 도움이 됩니다.

다음은 공격 시나리오 중 하나인 픽셔널 사례를 가정하여 CSRF 공격을 예로 들어 보겠습니다:

CSRF 공격: 사기적인 소셜 계정 바인딩 - 문제

적절한 상태 검증 기능이 있으면 클라이언트는 공격을 감지하고 사용자가 공격자의 웹사이트로 리다이렉트되는 것을 방지할 수 있습니다.

CSRF 공격: Fraud to bind social account - solution

PKCE

앞서 언급한 바와 같이, SPA 웹 앱과 네이티브 애플리케이션과 같은 공개 클라이언트는 인증 자격 증명, 인증 서버가 발행한 인증 코드를 포함하여 유출 위험이 더 높습니다.

PKCE는 Proof Key for Code Exchange의 약자입니다. 이는 공개 클라이언트의 보안을 강화하는 OAuth 2.0 Authorization Code Flow의 확장입니다.

PKCE는 공격자가 인증 코드를 가로채고 클라이언트의 지식 없이 액세스 토큰을 얻는 것, 즉 인증 코드 가로채기 공격의 위험을 줄이기 위해 도입되었습니다. 이러한 유형의 공격은 클라이언트 애플리케이션이 클라이언트 시크릿을 안전하게 저장할 수 없는 환경에서 더욱 일반적입니다.

PKCE를 구현하려면 클라이언트 애플리케이션은 무작위 코드 검증자를 생성하고 특정 해싱 알고리즘을 사용하여 그것에서 코드 도전을 파생시킵니다(일반적으로 SHA-256). 코드 도전은 인증 서버에 보내는 초기 인증 요청에 포함됩니다.

인증 서버가 인증 코드를 발행하면 클라이언트 애플리케이션은 토큰 요청에 원래의 코드 검증자를 포함합니다. 서버는 코드 검증자가 저장된 코드 도전과 일치하는지 확인하고 그런 다음에만 액세스 토큰을 발행합니다.

PKCE를 사용함으로써 클라이언트 애플리케이션은 인증 코드만으로는 액세스 토큰을 얻는 것이 충분하지 않다는 것을 보장합니다. 이 메커니즘은 인증 흐름에 추가적인 보안 계층을 더해줍니다, 특히 클라이언트 시크릿의 저장이 어려운 공개 클라이언트에게 특히 그러합니다.

Logto는 모든 공개 클라이언트 유형 애플리케이션에 대하여 PKCE를 유일한 인증 흐름으로 사용합니다. 그러나, 기밀 클라이언트에 대해서는 PKCE와 생략될 수 있습니다.

Redirect URI

Redirect URI(Uniform Resource Identifier)는 OAuth에서 인증과 권한 부여 과정을 완료하면 인증 서버가 사용자를 다시 리다이렉트하는 특정 엔드포인트 또는 URL을 참조합니다.

OAuth 흐름 중, 클라이언트 애플리케이션은 초기 인증 요청의 일부로 리다이렉트 URI를 포함합니다. 이 URI는 사용자가 클라이언트에 성공적으로 인증하고 권한을 허용한 후 리다이렉트되는 콜백 URL 역할을 합니다.

사용자가 인증 과정을 완료하면, 인증 서버는 응답을 생성하고 이에 인증 코드를 포함하며 사용자를 명시된 리다이렉트 URI로 다시 리다이렉트합니다.

리다이렉트 URI의 유효성 검사는 OAuth 흐름의 보안 및 무결성을 확보하는데 있어 필수적인 단계로, 인증 요청과 그 후의 리디렉션이 유효하고 신뢰할 수 있는지를 검증하는 과정을 의미합니다.

원래의 OAuth vulnerability report로 돌아와 보시기 바랍니다. (다음 섹션은 원래 post에서 참조되었습니다)

사용자가 Expo Go의 모바일 앱을 사용하여 "페이스북 로그인"을 클릭하면, 그것은 사용자에게 다음 링크로 리다이렉트합니다.

https://auth.expo.io/@moreisless3/me321/start?authUrl=https://www.facebook.com/v6.0/dialog/oauth?code_challenge=...&display=popup&auth_nonce=...&code_challenge_method=S256&redirect_uri=https://auth.expo.io/@moreisless3/me321&client_id=3287341734837076&response_type=code,token&state=gBpzi0quEg&scope=public_profile,email&returnUrl=exp://192.168.14.41:19000/--/expo-auth-session

응답에서 auth.expo.io는 다음과 같이 쿠키를 설정합니다: ru=exp://192.168.14.41:19000/--/expo-auth-session. 값 RU는 나중에 5단계에서 Return Url로 사용됩니다. 그런 다음 사용자에게 확인 메시지를 보여 주고, 사용자가 승인하면 - 그것은 사용자를 Facebook 로그인으로 리다이렉트하여 인증 흐름을 계속 합니다.

이 페이지는 쿼리 매개 변수 “returnUrl”을 읽고 그에 따라 쿠키를 설정합니다.

returnUrl을 hTTps://attacker.com으로 변경해봅시다(https는 허용되지 않으므로, 대문자를 삽입해 보았습니다. 이렇게 하면 성공했습니다), 그러면 쿠키의 RU(Return Url)가 https://attacker.com으로 설정됩니다.

위의 경우에서, 원래의 redirect_uri 매개변수를 버림으로써, Expo는 적절한 확인 없이 returnUrl이라는 새로운 매개 변수를 도입했습니다. 이 눈치채지 못한 것은 공격자에게 페이스북에서 반환된 인증 코드에 접근할 기회를 제공했습니다. 자세한 사항은 원래의 post를 참조하십시오.

리다이렉트 URI 검증은 여러 중요한 목적을 수행합니다:

  1. 피싱 공격 방지: 리다이렉트 URI를 검증함으로써 인증 서버는 사용자가 신뢰할 수 있고 인증된 엔드 포인트로 사용자를 다시 리다이렉트한다는 것을 확인합니다. 이는 공격자가 사용자를 악의적인 또는 무단 위치로 리다이렉트하는 것을 방지하는 데 도움을 줍니다.
  2. 오픈 리다이렉트 대처: 오픈 리다이렉트는 사용자를 악의적인 웹사이트로 리다이렉트하는데 이용될 수 있는 취약점입니다. 리다이렉트 URI를 검증하면 인증 서버는 리다이렉트가 허가된 도메인 또는 신뢰할 수 있는 도메인 세트의 경계 내에서 유지되도록 할 수 있습니다.
  3. 인증 응답의 올바른 라우팅 보장: 리다이렉트 URI를 검증하면 인증 서버가 사용자를 의도한 클라이언트 애플리케이션으로 다시 리다이렉트하는 것을 보장할 수 있습니다. 이는 응답(예: 인증 코드 또는 액세스 토큰)이 올바른 목적지로 전달된다는 것을 보장합니다.

Logto에서는 모든 유형의 응용 프로그램에 대해 redirect_uri 등록이 필수적입니다. 우리는 받은 값과 Logto 서버에 등록된 값을 비교하고 일치하는지 확인합니다. 이는 모든 사용자 지정 검색 매개 변수를 포함합니다. 만약 값이 없거나 잘못되거나 redirect_uri 값과 일치하지 않는 인증 요청이 유효성 검사에 실패하면, 파일에 등록된 redirect_uri로 잘못된 리다이렉트 URI 오류가 반환됩니다.

요약

그들의 복잡하고 미묘한 특성 때문에, 이러한 세부 사항들이 종종 누락되는 것은 이해할 수 있습니다. 어떤 것들은 state 같은 임의의 문자열일 뿐입니다.

그러나, 이러한 보안 조치들이 사용자 인증에 보호 계층을 추가해줌으로서 CSRF 공격, 인증 코드 가로채기, 무단 리다이렉트와 같은 위험을 완화함은 주의해야 합니다.

이것들은 오직 OAuth 프로토콜이 제공하는 포괄적인 보안 기능의 일부일 뿐입니다. OAuth는 안전한 인증 및 인가를 위한 견고한 프레임워크를 제공합니다. 또한 실제 제품 응용 프로그램의 다양한 요구 사항을 충족시키기 위한 유연하며 개방적인 엔드포인트를 제공합니다.

개발자 및 서비스 제공자로서, 사용자 인증 흐름의 보안 수준을 지속적으로 우선시하는 것이 중요합니다. 경계를 유지하고, 최선의 관행을 준수하고, OAuth 생태계의 최신 동향을 추적하는 것은 사용자 신원과 민감한 데이터의 보호와 완결성을 확보하는 데 필수적입니다. 우리는 OAuth의 구현에 있어 최고의 보안 표준을 준수하고 사용자의 개인 정보 보호와 신뢰를 지키는 데 지속적으로 약속드릴 것입니다.