Express 팀의 csrf와
csurf 모듈은 암호화 기능의 사용에 대해 의문을
가지는 이슈가 자주 올라옵니다. 이는 CSRF 토큰이 작동하는 방식을 잘못 이해해서 발생하는
의문이라고 생각합니다. 어째든, 빠르게 진실을 알아봅시다!
이 문서를 읽고, 질문이 있거나 무언가 잘못된 것이 있다고 생각하면 이슈를 생성하세요!
CSRF 공격은 어떻게 하는거죠?
공격자가 자신의 피싱 사이트에 다음과 같은 AJAX 버튼 또는 폼을 만들어 놓고 타겟의
사이트로 위조 요청을 보냅니다:
1 | <form action="https://my.site.com/me/something-destructive" method="POST"> |
이것이 문제가 되는 이유는 공격자가 AJAX 등을 통해 DELETE 메서드와 같은 요청을 원본
사이트로 문제없이 보낼 수 있기 때문입니다. 만약 원본 사이트에서 세션 데이터나 중요한
개인 정보를 다룬다면 아주 중대한 문제입니다. 만약 기술적인 지식이 전무한 사용자가
이러한 상황을 만난다면 신용카드 정보 또는 사회 보장 정보를 입력할지도 모릅니다.
CSRF 공격을 어떻게 방어하나요?
JSON API만 사용
제한된 CORS 환경에서 JavaScript를 통한 AJAX 호출만 사용합니다. 이 방법은 <form>이JSON 같은 정보를 전송할 수 없다는 점을 이용하여 요청 값을 JSON만 허용하는
방법입니다. 이 방법은 상기한 형태의 폼을 통한 공격 가능성을 제거합니다.
CORS 비활성화
CSRF 공격을 배제하는 가장 첫 번째 방법은 cross-origin 요청을 비활성화하는 것입니다.
만약 CORS를 허용하려면 사이드 이펙트(부수효과)를 발생시키지 않는 OPTIONS, HEAD, GET
메서드만 허용시켜야 합니다.
불행히도, 위의 메서드를 통한 요청들은 JavaScript를 사용하지 않으므로 모두 차단되지
않습니다. (따라서 CORS를 적용할 수 없습니다)
헤더의 레퍼러(referer) 확인
불행히도, 레퍼러 헤더를 확인하는 것은 약간 좋지 않은 문제가 있습니다. 하지만 원본
사이트가 아닌 해커의 사이트로부터 들어오는 요청은 언제나 막습니다. 이 방법은 문제가
발생할 여지가 없습니다.
예를 들어, 레퍼러 헤더가 자신의 서버가 아닐 땐 세션을 로드할 수 없습니다.
GET 메서드가 사이드 이펙트를 일으키지 않게하기
GET 요청이 데이터베이스의 그 어떤 관련된 데이터도 변경할 수 없게 해야 합니다.
이는 주로 초보자들이 많이 하는 실수이며 무려 어플리케이션의 CSRF 공격을 넘어서 수 많은
공격을 가능하게 만듭니다.
POST 사용 자제
왜냐하면 <form>은 GET과 POST 메서드만 허용하기 때문입니다. 이러한 방식 대신PUT이나 PATCH, DELETE 같은 메서드를 사용하면 공격자가 사이트를 공격할 수 있는
방법이 크게 줄어듭니다.
Method Override 사용하지 않기
많은 어플리케이션들이 기본 폼에 PUT 과 PATCH, DELETE 요청을 지원하기 위해
method-override를 사용합니다.
하지만, 이것은, 요청을 완전히 취약점이 없는 취약점으로 변경했습니다.
그러니 어플리케이션에 method-override를 사용하지말고 그냥 AJAX를 쓰는게 낫습니다.
오래된 브라우저는 지원하지 않기
오래된 브라우저는 CORS와 보안 정책을 지원하지 않습니다. 단순히 오래된 브라우저(주로
기술적인 지식이 부족한 컴맹들이 사용하며, 이들은 공격하기 더 쉽습니다)의 지원을 끊는
것 만으로도 CSRF 공격 경로를 최소화 할 수 있습니다.
CSRF 토큰
UNBEKNOWN
아아, 마지막 해결법은 바로 CSRF 토큰을 사용하는 것입니다. CSRF 토큰이 어떻게
작동하냐구요?
- 서버가 클라이언트로 토큰을 전송합니다.
- 클라이언트가 폼을 토큰과 함께 제출합니다.
- 토큰이 올바르지 않으면 서버에서 요청을 거부합니다.
공격자는 타겟 사이트에서 CSRF 토큰을 얻는 방법을 찾으려 할 것이고 이 때 JavaScript를
쓸 것입니다. 따라서 사이트가 CORS를 지원하지 않는다면 공격자가 CSRF 토큰을 얻을 수
있는 방법이 없습니다. 취약점을 제거하세요.
반드시 AJAX를 통해 CSRF 토큰에 접근할 수 없도록 하세요!/csrf 같이 바로 토큰을 받아올 수 있는 라우트는 만들지 말고, 특히 그 라우트에 CORS를
지원하지 마세요.
토큰은 추측할 수 없어야 하며, 이렇게 하면 공격자가 몇 번의 시도만으로 토큰을 얻기
힘들어집니다. 또한, 따로 암호학적인 보안이 필요하지 않습니다. 공격은 서버의 브루트 포스
공격이 아닌 사용자가 모르는 사이에 한두 번의 클릭으로 이루어집니다.
BREACH 공격
여기선 salt를 함께 제공해야 합니다. BREACH 공격은 아주 간단합니다: 만약 서버가HTTPS+gzip를 통해 같거나 비슷한 응답을 여러 번 한다면, 공격자는 응답 본문의 컨텐츠를
예측할 수 있습니다. (HTTPS를 완전히 쓸모없게 만듭니다) 해결법? 각 응답을 약간씩 다르게
만들면 됩니다.
그러므로, CSRF 토큰은 각 요청을 기준으로 매번 다르게 생성됩니다. 하지만 서버는
각 요청에 포함된 토큰이 유효한지 확인할 수 있어야 합니다:
- 암호학적으로 안전한 CSRF 토큰은 이제 서버에 의해서만 알려진, (가정) CSRF
“비밀”입니다. - 이제 CSRF 토큰은 secret과 salt의 해시입니다.
추가적인 내용은 다음을 참고하세요:
- BREACH
- CRIME
- BREACH 공격에 대한 방어
- [Salt란?][2]
- 안전한 패스워드 저장 (한국어)
참고로 CSRF은 BREACH 공격을 해결 하지 않습니다. 하지만 모듈은 간단히 요청을
무작위화 시켜 BREACH 공격을 완화시켜줍니다.
Salt는 안전한 암호화를 할 필요가 없습니다
왜냐하면 클라이언트는 salt를 알고 있기 때문입니다!!!
서버는 <salt>;<token>를 전송할 것이고 클라이언트는 서버로 같은 값을 요청에 포함하여
보낼 것 입니다. 그리고 서버는 <secret>+<salt>=<token>이 맞는지 확인할 것입니다.
salt는 반드시 토큰과 함께 전송되어야 합니다. 이렇지 않다면 서버는 토큰이 확실한지
확인할 수 없습니다.
이것은 간단한 암호학적인 방법입니다.
더 많은 방법들이 있지만 더 복잡하며 이 문제에 대해 효과적이지 않습니다.
토큰 생성은 빨라야 합니다!
왜냐하면 토큰은 매 요청마다 생성되어야 하기 때문입니다!
이 작업은 간단히 Math.random().toString(36).slice(2) 이렇게만 해도 충분하며
뿐만 아니라 매우 고성능입니다! 각 요청에 대해 OpenSSL과 같은 암호학적으로 암호화된
토큰은 필요하지 않습니다.
secret(데이터)은 비밀일 필요가 없습니다
하지만 이것은. 데이터베이스를 사용하는 세션 스토어를 쓴다면 클라이언트는 절대 DB에
저장된 secret을 읽을 수 없습니다. 하지만 쿠키 세션을 사용한다면 secret은 쿠키에
저장되고 클라이언트에게 보내질 것입니다. 그러므로, 쿠키 세션이 httpOnly를
사용하도록 하고 클라이언트가 secret을 client-side JavaScript로 읽을 수 없도록 해야
합니다!
CSRF 토큰을 잘못 사용하는 경우
JSON AJAX 요청에 CSRF 토큰을 사용
전술했듯이, 만약 CORS를 지원하지 않고 API가 JSON에 한정되어 있다면, AJAX 요청엔
CSRF 토큰을 포함시킬 수 있는 방법이 없습니다.
CSRF 토큰을 AJAX로 전송
어플리케이션에 GET /csrf 같은 라우트는 절때 만들어선 안되며 CORS를 활성화해서도
안됩니다. CSRF 토큰을 API 응답 본문에 포함하여 전송하면 안됩니다.
결론
최근 웹은 점점 JSON API를 사용하는 추세이며 브라우저는 더 많은 보안 정책으로 더
안전해지고 있기에 CSRF의 중요성은 점점 떨어지고 있습니다. 오래된 브라우저의 사이트
접속을 차단하고 많은 사이트 API를 JSON API로 변경하면 근본적으로 CSRF 토큰을 사용할
필요가 없어집니다. 그러나 안전을 위해, 언제든 가능할 때, 특히 구현에 사소한 상황이
아닐 경우 여전히 이것들을 구현하는 것이 좋습니다.
오타나 오역이 있을 수 있습니다. 문제를 발견했다면, 수정해서 PR을 넣어주세요. 많은
도움이 됩니다!