펜테스트짐


훈련을 통해 당신의 실력을 향상시켜보세요.



CSRF (크로스 사이트 요청 위조)

모두 7명의 회원님이 완료했어요.


 실습 환경

  실습을 하시려면 로그인이 필요합니다.

이 훈련에서는 CSRF(Cross-Site Request Forgery) 취약점에 대해 살펴보고, CSRF 공격에 취약한 실습 환경을 통해CSRF 취약점을 직접 연습해보고 테스트하는 방법을 익힙니다.



CSRF란?


CSRF(Cross-Site Request Forgery, 크로스 사이트 요청 위조, XSRF)는 사용자가 원하지 않는 행위, 정확히 말해서 공격자가 의도한 행위를 웹 애플리케이션에 요청하게 하는 취약점입니다. 클라이언트측 웹 취약점 중 하나이며, 세션 라이딩(Sessiong Riding)이라고도 불립니다. 공격자는 이메일이나 SNS, 채팅 메시지 등 피싱 공격을 통해 사용자의 웹 브라우저가 공격자에 의해 작성된 악의적인 URL을 방문하도록 유도하는 사회공학 기법을 활용합니다. 악의적인 URL을 방문한 사용자의 웹 브라우저는 CSRF에 취약한 웹 애플리케이션에 사용자의 의지와는 상관없이 공격자에게 이득이 되는 요청을 보내게 됩니다.


CSRF 동작 원리


CSRF는 HTTP 프로토콜의 무상태성(Stateless)과 비연결성(Connectionless) 특성을 이용한 취약점입니다. 이 두 가지 특성으로 인해 웹은 사용자 식별과 상태 추적을 위해 쿠키와 세션을 주로 사용합니다. CSRF는 바로 이 세션과 쿠키를 이용해 사용자를 인증하고 세션을 추적하는 웹 애플리케이션에서 발생하는 취약점입니다. 

세션과 쿠키를 인증 수단으로 사용하는 웹 애플리케이션에서는 사용자가 로그인을 한 이후부터는 별도의 인증 과정이 없어도 요청을 보낸 사용자가 누구인지 식별할 수 있고 로그인된 사용자의 상태를 유지할 수 있습니다. 이는 사용자가 최초 로그인할 때 서버에 의해 발급(Set-Cookie 응답 헤더를 통해 발급됩니다.)된 세션 토큰을 후속 요청을 보낼 때마다 자동으로 Cookie 요청 헤더에 추가하여 요청하는 웹 브라우저의 동작 방식 때문입니다. Cookie 헤더에 세션 토큰이 포함된 요청을 받은 웹 애플리케이션은 서버에 보관 중인 사용자별 세션 토큰과 사용자의 웹 브라우저에 의해 전송된 쿠키 안의 토큰을 비교하여 사용자를 판단하게 됩니다.


세션, 쿠키 기반의 웹 애플리케이션 동작 방식


문제는 여기에서 발생합니다. 요청을 보낸 실제 주체가 누구인지 쿠키를 통해 전송된 토큰을 통해 판단한다는 것이죠. 웹 애플리케이션 입장에서는 사용자의 토큰이 사용된 요청은 그저 사용자의 요청일 뿐입니다. 따라서, 공격자가 사용자의 세션 토큰을 이용해 악의적인 요청을 보낸다면 웹 애플리케이션은 악의적인 요청과 사용자의 정상적인 요청을 따로 구별해 낼 방도가 없는 것 입니다.


프로필 변경을 수행하는 웹 페이지를 생각해봅시다. 해당 웹 페이지를 방문하면 아래와 같이 사용자명, 이메일 주소를 수정할 수 있는 화면이 표시됩니다.


프로필 변경 웹 페이지


원래 저장되어있는 홍길동 사용자의 이메일 주소는 gildong@bugbountyclub.com입니다. 홍길동은 이메일 주소를 gildong@pentestgym.com으로 변경하고 "Save" 버튼을 클릭합니다.

이때 서버로 전송되는 요청은 아래와 같습니다. 

POST /edit_profile.php HTTP/1.1 
Host: vulnerable-web.com
Cookie: session_token=Token_Value_For_Gildong
...생략...

username=홍길동&email=gildong@pentestgym.com

홍길동은 현재 로그인되어 있는 상태이므로 웹 브라우저는 Cookie 요청 헤더를 통해 세션 토큰을 포함시킵니다. 웹 서버는 위의 요청을 받고 서버에 저장된 세션 토큰의 값과 비교하고 일치할 경우 홍길동 사용자의 요청이라 판단하고, 홍길동 회원의 프로필 정보에서 이메일 주소를 gildong@pentestgym.com으로 수정할 것입니다. 기능은 매우 정상적으로 수행되었고 별 문제가 없어 보입니다.

하지만 이런 경우는 어떨까요?

공격자가 http://vulnerable-web.com/edit_profile.php로 요청을 하는 악의적인 폼 양식을 만듭니다. 이 때 이메일 주소는 공격자의 이메일 주소로 변경되도록 작성합니다. 공격자가 만든 웹 페이지를 http://other-web.com/malicious.html 라고 하죠. 그 후 공격자는 홍길동이 자주 방문하는 웹 사이트에 공격자의 웹 페이지의 링크가 포함된 게시글을 등록하거나, 홍길동에게 링크가 포함된 피싱 메일을 보냅니다. 만약 홍길동이 http://vulnerable-web.com에 로그인된 상태에서 공격자가 만든 웹 페이지를 방문하면 홍길동의 웹 브라우저는 아래와 같은 요청을 웹 서버로 전송하게 됩니다.

POST /edit_profile.php HTTP/1.1
Host: vulnerable-web.com
Origin: http://other-web.com
Referer: http://other-web.com/malicious_form.html
Cookie: session_token=Token_Value_For_Gildong
...생략...

username=홍길동&email=attacker@evil.com

먼저 살펴본 정상적인 요청과 다른 점은 Body의 email 매개변수의 값이 공격자의 이메일 주소라는 점입니다. 이 요청은 웹 애플리케이션 입장에서 정상적인 요청으로 간주되고 서버는 홍길동 회원의 이메일 주소를 공격자의 이메일 주소인 attacker@evil.com으로 변경합니다. 다시 말해서 공격자는 프로필 변경 기능에 존재하는 CSRF 취약점을 통해 다른 사용자의 이메일 주소를 공격자의 이메일 주소로 수정할 수 있는 것이죠. 만약 웹 애플리케이션에서 제공되는 패스워드 초기화 기능이 패스워드 재설정 링크나 초기화된 패스워드를 프로필 정보에 등록된 이메일 주소로 보내도록 구현되어 있다면 공격자는 피해자의 패스워드를 초기화하고 계정을 탈취할 수 있습니다.


CSRF 공격을 활용해 사용자 계정을 탈취할 수 있음



웹 애플리케이션이 CSRF 공격에 취약하기 위해서는 아래와 같은 세 가지 충족되어야 할 조건이 있습니다. 위에서 살펴본 사례는 세 가지 조건을 충족합니다.

  • 권한이 요구되는 민감한 기능이어야 합니다. 웹 애플리케이션마다 다르지만 일반적으로 프로필 변경이나 회원 등록/삭제, 게시글 등록/수정/삭제 등의 관리자 권한이 요구되는 기능 등 이 있습니다.
  • HTTP 쿠키만 사용해 사용자 세션을 추적해야 합니다. 
  • 요청에 사용되는 모든 매개변수를 공격자가 알 수 있어야 합니다. 공격자는 의도한대로 요청을 구성해야 하므로 요청에 필요한 모든 매개변수를 사전에 모두 알 수 있어야 합니다. 


반면, CSRF 공격은 로그인된 사용자의 세션 토큰을 이용한다는 점으로 인해 한계가 발생하기도 합니다. 바로 공격자가 웹 애플리케이션에서 할 수 있는 행위가 피해자의 권한에 종속된다는 점입니다. 따라서 피해자의 계정과 사용 환경에 한해 영향을 끼칠 수 있습니다. 하지만 피해자가 관리자 권한이 있는 사용자라면 공격자는 관리자 권한이 요구되는 데이터에 접근하거나 기능을 사용할 수 있게 되고, 웹 애플리케이션 전체를 손상시킬 정도로 매우 심각한 수준의 취약점이라는 것을 알 수 있습니다. 


CSRF의 영향


CSRF의 영향은 웹 애플리케이션에서 피해자에게 주어진 권한이나 사용할 수 있는 기능에 따라 다릅니다. 위에서 살펴본 사례와 같이 피해자의 이메일 주소를 변경하고 패스워드 초기화 기능을 통해 계정 탈취까지 이어질 수 있습니다. 또한 피해자가 관리자이거나 그와 유사한 권한을 보유하고 있다면 공격자는 민감한 데이터를 유출하거나 데이터베이스를 삭제하는 등 웹 애플리케이션 전체를 장악하고 손상시킬 수 있습니다.


CSRF 예방


다음은 CSRF 공격을 예방하기 위한 몇 가지 방법입니다. CSRF 공격에 취약한 웹 애플리케이션은 충족되어야 할 세 가지 조건이 있었고 아래에 소개되는 방법들은 해당 조건들이 충족되지 않도록 공격자가 알 수 없는 새로운 매개변수를 추가하거나 웹 브라우저의 세션 토큰 자동 추가를 제한하여 CSRF 공격을 차단하도록 도와줍니다. 


Referer 요청 헤더 검증

Referer 요청 헤더 기반의 방어를 쉽게 우회할 수 있다는 사실을 모르는 개발자가 종종 사용하는 방법으로 요청에 포함된 Referer 요청 헤더의 값을 검증하는 방법입니다. Referer 요청 헤더의 값이 자신과 동일한 도메인이라면 요청을 수행하고, 다른 도메인이라면 공격으로 간주하고 요청을 차단하는 방법입니다. 안전하게 구현한다면 CSRF 공격에 대한 방어 수단이 될 수도 있을지 모르지만 오로지 이 방법 하나에만 의존해서 CSRF 공격을 방어하겠다고 생각해서는 절대 안됩니다. 


Anti-CSRF 토큰

사용자 세션을 추적하기 위해 기존의 세션 토큰 이외에 추가적인 임의의 토큰을 사용하는 방법입니다. 이 토큰은 사용자의 웹 브라우저와 서버만 알고 있으며 주로 사용자가 어떤 웹 페이지를 로딩할 때 아래와 같이 숨겨진 폼 필드에 저장되었다가 폼이 제출될 때 서버로 다시 전송됩니다.

<input type="hidden" id="security_token" name="security_token" value="토큰값">

제출된 토큰을 받은 서버는 해당 사용자의 세션 변수에 저장해두었던 토큰과 비교하여 일치할 경우 요청을 수행합니다. 올바르게 구현한 경우에는 효과적으로 CSRF 공격을 예방할 수 있으며 많은 웹 애플리케이션 개발 프레임워크에서 CSRF를 방어하기 위해 제공됩니다. 하지만 표면적으로는 Anti-CSRF 토큰이 구현되어있다 하더라도 웹 애플리케이션의 토큰 검증 방식이나 개발자의 실수에 의해 여전히 취약할 수 있습니다. 쉽게 유추할 수 있는 토큰을 사용하거나 토큰이 공격자에게 유출되는 경우는 말할 것도 없고 토큰이 매개변수로 전달되는 경우에만 검증을 하거나 토큰이 사용자의 세션과 연결되어 있지 않은 경우 등 구현상의 헛점이 있다면 이를 이용해 우회할 수 있습니다.  


이중 제출(Double submit) 쿠키 

웹 브라우저의 SOP(Same Origin Policy, 동일 출처 정책)에 의해 다른 도메인의 응답(쿠키 등)에 접근할 수 없는 점을 이용한 방법입니다. SOP에 의해 공격자의 웹 사이트에서는 취약한 웹 사이트에서 발급하는 쿠키를 읽거나 변경할 수 없으므로 공격자는 CSRF 공격에 필요한 모든 매개변수의 값을 알지 못하게 되고 CSRF 취약 조건을 충족킬 수 없게 됩니다. 이중 제출 쿠키 방법은 위의 Anti-CSRF 토큰 방식에서 살펴본 것과는 달리 서버 세션에 Anti-CSRF 토큰을 저장하지 않고 쿠키로 클라이언트에게 발급하는 방식입니다. 따라서 클라이언트는 Set-Cookie 헤더를 통해 두 개의 쿠키를 발급받습니다. 하나는 사용자 인증을 위한 세션 토큰이고 다른 하나는 CSRF 공격을 방어하기 위한 Anti-CSRF 토큰입니다. 이후 쿠키로 발급된 토큰은 웹 애플리케이션의 폼에 숨겨진 필드로 추가됩니다. 사용자가 폼을 제출할 때 쿠키에 저장된 Anti-CSRF 토큰은 웹 브라우저에 의해 자동으로 서버로 전송되며, 서버는 쿠키를 통해 받은 토큰과 요청 매개변수로 보내진 토큰을 비교하여 일치할 경우 요청을 수행합니다.  

하지만 웹 애플리케이션에 공격자가 HTTP 응답 헤더를 조작할 수 있는 취약점이 있다면 Set-Cookie 헤더를 응답에 주입하고 이를 통해 우회될 수 있습니다.


SameSite 쿠키

쿠키는 보안을 강화할 수 있는 플래그가 제공됩니다. 이 중 SameSite 플래그는 요청이 발생한 출처가 동일한 경우에만 자동으로 세션 토큰을 추가하도록 제한합니다. 다시 말해 bugbountyclub.com/edit_profile.php로 요청을 할 경우 bugbountyclub.com 웹 사이트에서 시작된 요청에만 사용자의 세션 토큰이 추가되고, evil.com과 같이 도메인이 다른 공격자의 웹 사이트에서 발생한 요청에는 사용자의 세션 토큰이 포함되지 않는 것이죠. CSRF를 예방할 수 있는 효과적이 방법이지만 SameSite 플래그는 일부 최신 브라우저에서만 사용 가능합니다. 

SameSite 플래그는 서버에서 Set-Cookie 헤더를 통해 쿠키를 발급할 때 아래와 같이 설정됩니다.

Set-Cookie: Cookie_name=Cookie_value; SameSite=지시자

지시자(Directive)의 종류에는 아래의 세 가지가 있습니다.

  • None : 별도 검증없이 쿠키를 추가합니다. 별도 지시자를 지정하지 않았을 때 대부분의 웹 브라우저에서 기본값으로 설정되는 값입니다. 하지만 크롬80 이상부터는 아래의 Lax를 기본값으로 사용하며 None으로 설정시 Secure 플래그가 강제됩니다.
  • Lax : <a href=...>, <form method="GET"...>과 같이 응답을 핸들링할 수 없는 GET 메소드를 통한 요청에만 쿠키를 자동으로 추가하고 그외 요청에는 쿠키를 보내지 않습니다.
  • Strict: 가장 강한 설정으로 다른 도메인에서의 시작된 모든 요청에 쿠키를 추가하지 않습니다.


패스워드 확인

요청할 때 사용자의 현재 패스워드를 입력받고 입력된 패스워드가 정확할 경우에만 요청을 수행하는 방법입니다. 사용자의 패스워드는 서버와 사용자만 알고 있으므로 공격자가 모든 매개변수를 알아야 하는 조건이 충족되지 않도록 할 수 있습니다.


Captcha 사용

요청마다 갱신되는 Captcha(캡차) 검증 역시 공격자가 알 수 없는 새로운 매개변수를 추가해 CSRF 공격을 차단하는 방법입니다. 


CSRF 테스트 방법


CSRF 공격에 대한 보호 조치가 전혀 되어 있지 않은 웹 애플리케이션이라고 가정합니다.


Step 1. 웹 애플리케이션 기능 점검

테스트 중인 웹 애플리케이션의 기능을 살펴봅니다. 만일 회원 가입 및 로그인 기능이 있다면 테스트 계정을 만들고 로그인하여 인증된 계정으로 기능을 살펴봅니다. 웹 애플리케이션에 구현되어 있는 기능 중에 아래의 CSRF 취약 조건을 모두 충족하는 요청을 찾습니다. 

  • 권한이 요구되는 기능
  • 쿠키에만 의존하여 사용자 세션을 추적하는 기능
  • 모든 매개변수를 사전에 알 수 있는 요청


Step 2. 공격 코드 작성

공격에 사용할 코드를 작성합니다. 찾아낸 요청에서 사용되는 HTTP 메소드가 GET인지, POST인지에 따라 다른 방법을 이용해야 합니다.


GET 요청

GET 요청인 경우에는 취약한 진입점(Endpoint)을 src 속성으로 갖는 <img> 태그를 사용하여 HTML 페이지를 작성합니다. 피해자가 이 웹 페이지를 방문하면 <img> 태그는 이미지를 로드하기 위해 지정된 URL로 접속을 시도하게 됩니다. 이 URL 주소는 공격자가 의도한 행위를 하는 URL이며 웹 브라우저는 이 URL이 실제 이미지를 가져오기 위한 요청인지, 다른 악의적인 행위를 하는 요청인지 구분할 수가 없습니다. 웹 브라우저에 의해 요청은 정상적으로 전송되고 사용자는 자신도 모르게 웹 애플리케이션에 의도하지 않은 요청을 하게 됩니다. 

<img src="http://vulnerable-web.com/edit_profille.php?param_1=som_value&param_2=some_value"/>


POST 요청

POST 요청인 경우에는 취약한 웹 애플리케이션의 진입점(Endpoint)으로 요청을 전송하고, 요청에 사용되는 모든 매개변수와 값이 숨겨진(hidden) 필드로 포함된 폼을 만들어야 합니다. 간단한 스크립트를 사용하면 사용자의 폼 제출 등의 행동이 없어도 폼이 자동으로 제출되게 할 수 있습니다.

<form action="http://vulnerable-web.com/edit_profile.php" method="POST">
<input type="hidden" name="param_1" value="some_value">
<input type="hidden" name="param_2" value="some_value">
  ...생략...
<input type="hidden" name="parameter_N" value="some_value">
</form>
<script>
document.forms[0].submit(); // 로딩될 때 자동으로 폼이 제출되도록 구성함.
</script>


Step 3. 취약 여부 테스트

이 단계는 테스터의 입장에서 일반적으로 사용할 수 있는 방법에 대해 설명합니다. 실제 공격 상황에서는 이메일, SNS 등을 통해 피해자가 공격 코드가 포함된 외부 웹 페이지를 방문하도록 유도해야 하며, 피해자가 공격 코드가 포함된 웹 사이트를 방문하는 순간에 동일한 웹 브라우저 환경에서 취약한 애플리케이션에 로그인이 되어 있도록 추가적인 작업이 필요할 수 있습니다. 하지만 테스트를 할 때 이러한 사항들은 테스터가 공격자, 피해자의 역할을 모두 제어하므로 굳이 고려할 필요는 없습니다.

테스터가 해야 할 일은 만일 테스터가 제어하는 웹 사이트가 있다면 본인의 웹 사이트에 이전 단계에서 작성한 공격 코드가 포함된 웹 페이지를 호스팅한 후 방문하거나 로컬 환경의 웹 브라우저를 통해 공격 코드가 포함된 웹 페이지를 방문하여 CSRF 공격이 수행되는지 확인하는 것입니다. 

아래의 과정을 따라 취약 여부를 테스트합니다.

  ➀ 테스트 중인 웹 애플리케이션에서 계정을 만들 수 있다면 피해자 계정을 만듭니다.

  ➁ 피해자의 계정으로 로그인을 합니다.

  ➂ 테스터의 로컬 환경에서 공격 코드를 작성합니다. 또는 로컬 환경이 아닌 인터넷으로 접속할 수 있는 환경(예: heroku)을 이용해도 괜찮습니다.

  ➃ 피해자로 로그인되어 있는 웹 브라우저와 동일한 웹 브라우저를 이용해 공격 코드가 포함된 웹 페이지를 방문합니다.

  ➄ 공격자가 의도한 행위의 요청이 수행되는지 확인합니다. 


만약 테스트 중인 웹 애플리케이션 내부에 공격자의 공격 코드를 주입할 수 있는 HTML 인젝션이나 XSS 취약점이 있는 진입점(Endpoint)을 발견했다면 해당 취약점과 CSRF를 연계할 수 있습니다. 이 경우 HTML 인젝션이나 XSS 취약점을 통해 테스트 중인 웹 애플리케이션 내부의 다른 웹 페이지에 주입된 악성 링크를 통해 요청이 시작되므로 웹 브라우저의 SOP(Same-Origin Policy, 동일 출처 정책)에 위배되지 않습니다. 이는 크로스 도메인 요청시에만 CSRF 공격을 차단하는 SameSite 쿠키 등의 방어 수단은 더 이상 효과가 없다는 것을 의미합니다. 또한 피해자는 이미 로그인을 통해 인증된 사용자일 확률이 높으므로 공격 효과와 성공율을 향상시킬 수 있는 방법입니다.

 

실습 문제 풀이


혹시 위의 가상 실습 환경을 생성하여 실습을 해보셨나요? 아직 해보지 않으셨다면 아래 내용을 읽어보시기에 앞서 직접 실습해보시는 것을 권장합니다. 먼저 실습을 해보신 후 자신의 풀이 방법을 아래에 설명된 풀이 방법과 비교해보세요. 


Exercise 1

이 문제에서는 사용자가 본인의 프로필 정보상의 사용자명, 이메일, 성별, 생일을 수정할 수 있습니다.


Exercise 2

문제 1과의 차이점은 GET 메소드가 아닌 POST 메소드와 Body 매개변수로 요청이 전송된다는 점입니다. POST 요청에 대해 CSRF 공격 코드를 구성하는 것은 GET 요청과는 다른 방법을 사용해야 합니다. POST 요청의 경우에는 숨겨진 필드가 있는 폼(<form>...</form>)을 이용하여 CSRF 공격을 할 수 있습니다. 


Exercise 3

개발자는 이제 다른 도메인을 통해 전송되는 요청을 차단했습니다. 개발자는 Referer 요청 헤더가 있다면 헤더의 값을 검사하여 다른 도메인에서의 요청을 수행하지 않고 차단했습니다. 하지만 개발자가 구현한 CSRF 공격에 대한 보호 기능은 논리적인 결함이 있습니다.


Exercise 4

개발자는 문제 3에 있던 논리적 결함을 제거하여 Referer 헤더 기반의 보호 조치를 개선하였습니다. 개발자는 웹 애플리케이션과 동일한 도메인에서 시작된 요청인지 판단하기 위해 Referer 헤더의 값을 검증합니다. Referer 헤더에 pentestgym.com이라는 문자열이 포함되어 있는지 검사하고 포함되어 있다면 요청을 수행합니다. 하지만 Referer 헤더값을 비교하는 기능이 여전히 안전하지 않게 구현되었습니다.


Exercise 5

개발자는 이제 Anti-CSRF 토큰을 통해 CSRF 공격을 차단하는 방식으로 필터링을 변경했습니다. 프로필 수정 요청을 보내면 아래와 같이 POST Body에 csrf_token 매개변수가 추가되어 전송됩니다.

csrf_token=임의의 토큰&user_name=Spike+spigel&email=spike%40bugbountyclub.com&gender=M&birth_date=1998-01-14&submit=Change

서버는 전달된 csrf_token 매개변수의 값과 서버 세션에 저장되어 있는 토큰의 값을 비교합니다. 두 값이 일치하면 요청이 수행되고 일치하지 않으면 "Invalid token!!" 메시지를 표시하고 요청을 차단합니다. 이 방법은 일반적으로는 검증이 안전하게 구현된 경우에는 효과적으로 CSRF 공격을 방어할 수 있습니다.

하지만 일부 웹 애플리케이션은 개발자의 실수로 인해 클라이언트로 부터 오는 요청에 Anti-CSRF 토큰이 있을 때만 토큰의 유효성을 검사하도록 구현됩니다. 


Exercise 6

종종 어떤 웹 애플리케이션은 매개변수로 전달된 Anti-CSRF 토큰을 서버 세션에 저장된 토큰과 일치하는지 검증하지 않을 수 있습니다. 이런 경우 유효한 Anti-CSRF 토큰과 동일한 길이의 임의의 문자열로 토큰을 변경하여 요청해보는 것을 시도해보세요. 가끔 요청이 거부되지 않고 수행되는 경우가 있습니다.



참고 문헌