XorCsrf 토큰이란 무엇이고, 왜 써야 하는가(feat. BREACH Attack)

eora21·2024년 6월 19일
0

이전 글에서 웹소켓 보안을 위한 csrf 토큰을 적용하던 중, xorCsrf 토큰의 존재를 알게 되었습니다.
xorCsrf 토큰을 왜 사용해야 하며, 어떠한 의도가 담긴 것인지 확인해 보도록 하겠습니다.

csrf 토큰 Body 전송 시 BREACH 공격 취약

csrf를 검증하기 위해서는 클라이언트에서 POST, PUT, PATCH, DELETE 등의 요청 시 csrf 토큰을 함께 제출해야 합니다.

쿠키와 헤더에 담아 더블 서밋 쿠키 패턴을 사용할 수도 있고, body에만 담아 세션 내의 토큰과 비교할 수도 있습니다.
이 외에도 여러 방법이 있겠지만, 여기서 중요한 건 body에 담기는 secret 데이터는 BREACH 공격에 의해 유출될 가능성이 있다는 것입니다.

BREACH 공격이란?

해당 논문은 여기에서 확인하실 수 있습니다.

BREACH(Browser Reconnaissance and Exfiltration via Adaptive Compression of Hypertext)는 HTTPS에서 HTTP 압축 사용 시의 취약점입니다.

정말 간단하게 정리하자면

  • 해당 공격은 http의 body 압축을 이용하여 공격한다.
  • body에 포함된 secret 값과 일치하는 파라미터를 작성하면 압축률이 변경되어 응답 크기가 줄어들게 된다.
  • 여러 번의 요청을 통해 한 글자씩 secret 값을 알아낼 수 있다.

로 보입니다. 제가 전공자도 아니거니와, 읽히는 대로 정리한 것이기 때문에 실제 내용과 다를 수 있지만 중요한 점은 결국 body에 담기는 secret 값이 항상 같을 시 BREACH 공격에 취약하다는 점일 듯 합니다.

대처법은?

해당 공격을 막을 수 있는 방법은 꽤 많은 것으로 보입니다.
우선적으로, 'secret 값을 body에 담아 보낼 수 밖에 없는 경우'를 가정하도록 하겠습니다.

HTTP 압축 미사용

해당 공격은 HTTP 압축을 통해 이루어지므로, 크로스 오리진 요청 혹은 헤더가 없는 요청 시 압축을 사용하지 않음으로 공격을 방어할 수 있습니다.
그러나 압축을 사용하지 않는다는 건 그만큼 송수신 데이터가 이전보다 커짐을 의미합니다.

헤더, 페이로드에 패딩 추가

전송되는 데이터에 임의의 패딩을 추가하여 같은 요청에 대해서도 약간씩의 다른 응답 크기를 전송하는 방식으로 보입니다. 해당 방법은 HTB로 대체되었습니다.

HTB(Heal-the-BREACH)

이미 압축된 데이터에 임의의 패딩을 추가하는 방법입니다. 최소한의 cpu와 대역폭을 사용하면서도 효과적인 방법이라고 합니다.

원본 secret 값의 주기적 교체

csrf 토큰의 본래 의도처럼, secret을 매번 다른 값으로 교체한다면 BREACH 공격을 제대로 사용할 수 없습니다.
다만 공격자의 공격으로 인한 secret 값의 변화로 실제 사용자가 지녔던 secret 값이 무용지물화 될 수 있다는 점이 아쉽습니다.

Spring Security의 방어법, xorCsrf

스프링 시큐리티는 이를 xorCsrf 토큰으로 해결했습니다.

xorCsrf 토큰이란?

임의의 값 A와 B를 XOR 연산했을 때 실제 csrfToken을 도출하는 방법이며, Spring Security에서는 여기에 더해 base64 인코딩까지 사용합니다.

즉, csrf 토큰값을 숨겨 전달하기 위해 클라이언트에서는

csrfToken으로 A, B 생성
A와 B 문자열을 합해 randomString 생성
secret = base64.encode(randomString)

그 후 secret을 서버에 전송합니다.

서버는 해당 값을 base64로 decode하고, 반을 잘라 A와 B로 나눈 후 xor 연산을 통해 본래의 csrf 토큰을 확인합니다(자세한 방법에 대해서는 이전 글을 참고해 주세요).

해당 방법은 요청 시마다 secret 값이 달라지면서도 원래의 csrfToken은 변화시키지 않는, 아주 좋은 해결법이라 생각합니다.

실제 SockJS로 연결 시 payload를 확인하면, xorCsrf 토큰이 body에 담기는 모습을 확인할 수 있습니다.
만약 일반적인 csrf 토큰이었다면, BREACH 공격에 의해 충분히 노출될 수 있는 상황으로 보여집니다.

base64는 왜 사용한 걸까

'매번 secret 값이 달라져야 한다'라는 것만 본다면, A와 B를 합친 randomString까지만 보내도 되지 않았을까 라는 생각이 들었습니다. base64를 사용한다는 건 전송해야 하는 데이터가 30% 가량 길어지고, 인코딩 및 디코딩 과정도 포함해야 하기에 오히려 성능 이슈가 생길 수 있었습니다.

제 생각에는 아마 randomString을 통해 도출된 결과를 숫자와 문자열, 그리고 조금의 특수기호로 제한하여 결과를 받기 위함이 아니었을까 싶습니다.

randomString은 xor 비트 연산이 기반이기 때문에 도출되는 결과를 종잡을 수 없고, 이를 서버와 주고받는 과정 또는 서버에 도달한 문자열을 처리하는 과정에서 문제가 발생(깨진 문자열이나 해석할 수 없는 문자열, 오류를 일으키는 문자열 등)하지 않을까 합니다.

이를 방지하기 위해 base64로 해당 비트들을 정해진 규격 내에서 처리하여 서버로 보내게끔 의도한 것이 아닌가 싶습니다.

Reference

https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html#csrf-token-request-handler-breach
https://en.wikipedia.org/wiki/BREACH
https://ieeexplore.ieee.org/document/9754554

profile
나누며 타오르는 프로그래머, 타프입니다.

0개의 댓글

관련 채용 정보