CSRF 대응 방안

DATE : 2024/2/1

CSRF 공격 절차

이번 주 Post에서는 12주차 : 1/17에 공부했던 CSRF 공격에 대해 마무리 지어볼까 한다.

CSRF 정의부터 되새겨보면 CSRF란?!

사용자가 서버로 임의의 요청을 보내도록 만드는 공격으로

공격자가 요청을 완벽하게 완성할 수 있느냐 없느냐가 공격 가능 여부를 결정한다고 했었다.

"요청을 보내도록 만든다" 라는 건, 서버로 요청을 보내는 모든 포인트가

잠재적인 공격 포인트가 될 수 있다는 뜻이지만 그렇다고 또 모든 요청이 중요한 요청이라 할 순 없다. 😂

예를 들어, 마이페이지로 이동할 때 서버로 날아가는 요청이 있고

GET /mypage.php HTTP/1.1
...
POST /mypage.php HTTP/1.1
...
new_password=1019&id=hanhxx

개인 정보를 수정할 때 서버로 날아가는 요청이 있다고 생각해보자.

둘 다 요청이긴 하지만, 공격자 관점에서 봤을 때 단순히 마이페이지를 요청하는 건

그리 큰 메리트가 없을 것이다.

하지만 개인정보를 수정하는 요청이라면?

현재 packet에서는 id & new_password parameter만 전달되고 있기 때문에 공격자가 원하는 값으로

비밀번호를 수정하도록 요청을 만들 수 있을 것이다.

이처럼 CSRF 공격을 하기 전에는 중요한 요청이 무엇인지 선별하는 과정이 선행된다.

공격을 수행할 요청을 선별한 후에는 그 요청을 공격자가 완성할 수 있는지 체크해봐야 한다.

앞에서도 말했듯이 공격자가 요청을 완성할 수 있느냐 없느냐가 공격 가능 여부를 결정하기 때문!

위에서 개인정보 수정 시 날아간 Packet을 봤을 때 공격자는

(1) /mypage.php - POST method로 전달

(2) 비밀번호를 수정할 사용자의 아이디와 바꿀 비밀번호가 파라미터로 필요

어렵지 않게 (1), (2)와 같은 정보를 파악할 수 있다.

만약 Packet을 GET method로 바꾼 다음 서버로 전달했을 때 응답이 정상적으로 돌아온다면,

GET method로 요청을 보낼 수 있기 때문에 공격자는

GET /mypage.php?id=hanhxx&new_password=1019 HTTP/1.1

위와 같이 요청을 만들어 CSRF 공격을 수행할 수 있게 된다.

GET method로 요청을 보내지 못한다면?? XSS 취약점이 존재하는 경우

<form method="post" action="~~/mypage.php">
<input type="hidden" name="id" value="hanhxx"> 
<input type="hidden" name="new_password" value="1019">
</form>

<form> tag를 활용해 POST method로 CSRF 공격을 수행하면 된다!

이렇게 해서 전반적인 CSRF 공격 절차를 정리해보면 다음과 같다 :

(1) 중요한 요청이 무엇인지 선별한다.

(2) 해당 요청을 공격자가 완성할 수 있는지 체크

(3) Get method로 요청을 보낼 수 있는지 확인한다. (링크 형태로 클릭 유도)

(4) XSS 취약점을 찾아 Post method로 요청을 보낸다. (Form tag 활용)

CSRF 대응 방안에 대해 알아보기 전에 공격 절차를 정리한 이유는

공격이 이루어지는 흐름에 따라 대응 방안이 어떻게 만들어졌는지 보기 위함이다!

CSRF 공격을 수행하는 과정이 전반적으로 이해되었다면 이젠 본 주제인 대응 방안에 대해 얘기해보자.


CSRF 대응 방안

CSRF 공격을 수행하는 과정에서 우리는 요청을 크게 Get method or Post method로 보낼 수 있다.

개인적으로 Get method로 이루어지는 CSRF 공격이 더 간단하다 보니

Post method로 전달되는 요청이라 할지라도 Get method로 변환해서 요청을 시도해보고는 한다.

또한 XSS 취약점이 존재하지 않는 경우에는 Post method로 CSRF 공격을 수행할 수 없다 보니

이런 부분을 고려했을 때 "Get method를 허용하지 않으면 되겠다" 생각할 수 있다.

하지만 이는 반대로 XSS 취약점이 존재하는 경우에는 Post method로 CSRF 공격을 수행할 수 있기 때문에

100점 짜리 방안이라 할 수 없다.

그렇다면 CSRF 공격이 가능한 이유를 다시 되짚어 보자.

공격자가 CSRF 공격을 수행할 수 있는 근본적인 이유는..! 바로, 요청을 완성할 수 있었기 때문이다.

그러면 요청을 완성하지 못하도록 만들면 되는 거 아닌가??

그래서 나온 게 CSRF TOKEN이다.

사용자가 마이페이지에 접속할 때 서버에서는 몰래 CSRF TOKEN을 만들어서

<input type="hidden" name="csrf_token" value="djWjrhwjWjrhqmffkqmffk">

페이지 어딘가 숨겨둔다.

이때 서버는 csrf_token에 대한 정보를 DB든, Session이든 어딘가 저장을 해두고 있다가

사용자가 마이페이지에서 개인 정보 수정을 요청할 때, 아래와 같이 csrf_token을 함께 보내면

POST /mypage.php HTTP/1.1
...
id=hanhxx&new_password=1019$csrf_token=djWjrhwjWjrhqmffkqmffk

자신이 가지고 있는 토큰 정보와 비교해서 일치하는 경우에 개인 정보를 수정해준다.

이처럼 사용자가 마이페이지에 들어가기 전까지 모르는 랜덤한 값을 요청에 포함하도록 만들어

공격자가 요청을 완성하지 못하도록 만드는 것이 CSRF TOKEN의 용도이다.

하지만,, CSRF_TOKEN을 사용하면 정말로 CSRF 공격이 불가능해질까?

CSRF_TOKEN은 우회 가능하다.

<iframe> tag를 사용해 토큰이 들어있는 페이지를 불러오게 되면 페이지 안에 있는 토큰을 추출할 수 있다.

🤔,, 그렇다면 CSRF_TOKEN도 근본적인 해결을 되지 못하는 거 같다.

다른 방안을 생각해보기 위해 CSRF 공격이 이루어지는 패턴을 생각해봤더니

뭔가 이상한 점을 발견하게 된다..!

원래 개인 정보를 수정하는 행위는 마이페이지에서만! 일어나는 게 정상인데

가만 보니 CSRF 공격이 이루어지면 웬 게시판 페이지에서 개인 정보 수정 요청이 날아오는 게 아니던가?!

Request Header 중에는 요청을 어디에서 보냈는지 나타내는 referer header가 존재한다.

의도한 대로면 mypage.php에서만 개인 정보 수정을 요청할 수 있으니 referer header를 확인해서

다른 페이지에서 날아온 요청은 받지 않으면 되지 않을까?!

referer header를 제대로 체크한다면 이를 우회할 수 있는 방법은 없다고 한다.

하지만 "제대로" 라는 게 만족하기 어렵다는 게 문제다..!

에러가 발생하면 크게 서비스를 중단하든지, 그냥 눈 감고 넘어가든지 두 가지 종착지가 있는데

하나의 서비스를 제공하는 입장에서 처음부터 끝까지 모든 프로그램을 동일한 사람이 만드는 게 아니기에

코드를 수정하고 추가하는 과정을 거치다 보면 사실 어디에서 에러가 발생할 지

잠재적인 포인트를 예측할 수 없다..!

그렇다고 에러가 발생할 때마다 중단할 거야?? No! 이기 때문에

잘못된 예외 처리를 하는 경우가 발생하게 된다.

그 중 한 가지 예시를 보자면 우리는 앞서 csrf token이나 referer header 처럼

request에 포함되는 정보를 확인함으로써 CSRF 공격에 대응하자고 했는데

서비스를 제공하는 입장에서 "csrf token, referrer header가 있으면 검사를 하고 없으면 검사를 하지 마!"

POST /mypage.php HTTP/1.1
...
id=hanhxx&new_password=1019&csrf_token=djWjrhwjWjrhqmffkqmffk

라는 방식으로 구현했다면

POST /mypage.php HTTP/1.1
...
id=hanhxx&new_password=1019

우리는 단순히 request에 들어있던 값을 지움으로써 우회가 가능해질 수도 있다.

혹은 referer header 없이 요청을 보내게 하기 위해서

<meta name="referrer" content="no-referrer">
<meta name="referrer" content="none">

<meta> tag를 작성할 수도 있다.

자! 그럼 궁극적으로, 정말, CSRF 공격에 대응하기 위한 방법을 알아보자.

앞선 과정을 통해 언급되었던 CSRF TOKEN, Referer Header가 아닌 근본적인 해결책은 바로,

인증 정보를 포함하는 요청을 만드는 것이다.

POST /mypage.php HTTP/1.1
...
id=hanhxx&new_password=1019

비밀번호를 수정하기 위해서 위와 같이 예측 가능한 값으로 이루어진 요청을 사용하는 게 아닌

공격자가 모르는 동시에 인증 목적으로 사용할 수 있는 값을 포함하자는 것이다.

POST /mypage.php HTTP/1.1
...
id=hanhxx&new_password=1019&old_password=1018

예를 들어, 비밀번호를 수정할 때 이전에 사용하던 비밀번호를 함께 입력하든지

POST /mypage.php HTTP/1.1
...
id=hanhxx&new_password=1019&PIN=101833

사용자 핸드폰으로 전달되는 인증 번호를 입력하는 식의 방법을 통해 요청을 만드는 것이

근본적인 CSRF 대응 방안이라 할 수 있다!

사용자 핸드폰으로 전달되는 정보는 사용자만 받아볼 수 있는 정보이고

기존 비밀번호 또한 비밀번호를 쓰던 사용자만 아는 정보이기 때문에

이러한 인증 정보를 포함하는 요청은 공격자가 완성할 수 없으므로 CSRF 공격을 막을 수 있게 된다!

Last updated