Research

XHR(XMLHttpRequest)에서의 CSRF? CORS?

Vardy 2022. 11. 7. 23:32

특정 중요 로직에 대해서 CSRF 공격 가능 여부를 판단 하던 중, 특이하게 양호했던(?) 케이스이다.

 

우선 해당 케이스를 살펴보자. 

 

현재 5건의 데이터가 있으며 그중 한 건을 삭제 시도해보자.

요청되는 패킷을 분석해보면, CSRF 검증을 하는 듯 하나 검증에 쓰일 것 같은 데이터를 삭제하고 Origin, Referer 등의 정보도 임의의 값으로 바꾸더라도 기능이 정상 실행됨을 알 수 있다.

 

따라서, CSRF 공격이 성공 할 수 있을 것이라 판단했고, 간단한 시나리오를 구축해서 테스트해봤다. 내용은 아래와 같다.

1. 공격자 개인 서버에 form 등으로 해당 로직을 요청하도록 하는 페이지 구현

2. 해당 기능 실행 권한을 가진 사용자(피해자) 에게 공격자 서버 접근 유도

3. 접근 시 공격자 권한으로 해당 기능이 요청되어 수행됨.

 

아래와 같이 간단한 form을 구현 후 공격자에게 접근을 유도했다고 가정해보면,

기대와는 다르게, 500에러가 발생하면서 기능이 수행되지 않는다.

원인 파악을 위해,

피해자가 CSRF 공격에 당했을 때 요청되는 패킷을 확인하여 Origin, Referer, Sec-Fetch-Site, Sec-Fetch-Mode, Sec-Fetch-Dest 헤더와 같이 CSRF 공격을 검증할때 쓰일 것 같은 환경을

동일하게 정상 사용자가 해당 기능을 요청했을때의 패킷에 적용시켜 요청시켜보았는데 그 때는 정상 수행 되었다.

CSRF 공격 중 요청되는 패킷 - 500에러 발생
정상 요청에서 CSRF 관련 값 삭제 및 헤더 수정 - 수행 됨(200)

 

눈치 챈 분들도 계시겠지만, 해당 현상의 원인은 커스텀 헤더 중 하나인 X-Requested-With: XMLHttpRequest 에 있었다.

별도로 테스트 결과,

정상 요청에서 해당 헤더 값을 제거 하면 500 에러가 발생하였고, CSRF 공격을 통해 변조된 요청에서 해당 헤더 값을 추가해주면 기능이 정상 실행되었다.

정상적인 요청에서 X-Requested-With 헤더 삭제 - 500에러 발생
CSRF 공격을 통해 요청되는 패킷에 X-Requested-With 헤더 추가 - 기능 정상 수행(200)

 

즉, 해당 서비스에서는 CSRF의 알려진 방어 기법인 CSRF Token이나 Referer, Origin 헤더 검증은 이루어지지 않고 있고, 

X-Requested-With 헤더를 통해 검증이 이루어지고 있다는 뜻이다.

 

그렇다면, 해당 헤더를 포함시켜 CSRF 공격을 수행 하면 어떨까?

form 태그에서는 커스텀 헤더를 추가 할 수 없는 것으로 알고 있어서(있다면 공유 부탁드립니다.)

XHR을 통해 구현해보았다.

결과는 Invalid CORS request 라는 메시지와 함께 실패하게 된다.

조금 더 자세히 살펴보면, 

위와같이 정상적으로 리다이렉트를 시키는 듯 하나,

직후에 강제로 OPTIONS 메소드로 확인 작업이 추가되며, CORS 조건에 부합하지 않음을 판단하여 CSRF 공격을 제한한다.

 

해당 내용에 대해 공부 한 것을 간단히 정리하면,

 

일반적으로, 스크립트 내에서 실행되는 요청에 대해서는 SOP(Same Origin Policy) 정책에 의해 다른 Origin으로 요청을 보낼 수 없도록 브라우저에서 금지하고 있다.

다만, 시스템의 규모가 커지는 등 Cross-Site HTTP Requests 가 필요한 상황이 늘어나면서 CORS(Cross-Origin Resource Sharing) 정책을 만족하는 경우 SOP에서 예외를 두어 허용하여 사용 할 수 있게 되었다. 

또한, AJAX의 사용이 늘어나면서 스크립트 내에서 XHR을 통한 요청에 대해서도 관련 내용이 필요하게 되었다.

 

해당 케이스와 관련된 내용을 살펴 보면

우선 CORS 요청은 Simple/Preflight, Credential/Non-Credential의 종류로 구분 할 수 있다. 브라우저가 요청 내용을 분석하여 위 4가지 방식 중 해당하는 방식으로 서버에 요청하게 된다.

 

Simple Request의 경우

아래의 3가지 조건을 모두 만족하면 Simple Request 라고 판단하여 서버에 1번 요청하고, 서버도 1번 회신하는 것으로 처리가 종료된다.
1. GET, HEAD, POST 중의 한 가지 방식을 사용해야함.
2. POST 방식일 경우 Content-type이 아래 셋 중의 하나여야함.
application/x-www-form-urlencoded
multipart/form-data
text/plain
3. 커스텀 헤더를 전송하지 말아야함.

 

이번 케이스에서는 X-Requested-With 라는 커스텀 헤더가 필수적으로 요구되므로 Simple Request 조건을 만족 하지 않는다.

 

Simple Request 조건에 해당하지 않으면 브라우저는 Preflight Request 방식으로 요청하도록 되어 있다.

Preflight Request는 예비 요청과 본 요청으로 나뉘어 전송된다. 작동 방식은 아래와 같다.

(1) 서버에 예비 요청(Preflight Request)를 보냄 -->

(2) 서버는 예비 요청에 대해 응답 -->

(3) 올바른 응답일 경우 본 요청(Actual Request)을 서버에 보냄 -->

(4) 서버는 본 요청에 응답

 

우리의 예시에서 자동으로 OPTIONS 요청되었던 것이 Preflight Request 인 것이고 (2) 의 과정에서 올바르지 않은 요청이라는 것이 확인되어 CSRF 공격에 실패 하게 된 것이다.

 

결론적으로,

나는 X-Requested-With: XMLHttpRequest 는 해당 요청의 종류가 AJAX라는 것을 의미한다고만 알고 있었는데, 해당 헤더를 서버에서 검증하는 것만으로 브라우저에서의 CORS 규칙에 의해  CSRF 공격에 대한 방어가 가능했다.

해당 서비스 개발자가 의도했는지는 잘 모르겠지만, CSRF 토큰 값이 존재하지만 실제로 사용되고 있지 않았고 Origin이나 Refer 등을 검증하지 않았음에도 요청이 AJAX인건지 검사하는 것 만으로도 해당 장치(브라우저단에서의 방어)를 통해 CSRF 공격을 방어하고 있었던 것이다.

 

참고 자료 :

https://it-eldorado.tistory.com/163

 

[Web] CORS (Cross Origin Resource Sharing) 이해하기

이번 포스팅에서 다룰 내용은 바로 CORS(Cross Origin Resource Sharing)이다. 웹 개발자라면 한 번쯤은 CORS와 관련하여 콘솔에 뜨는 빨간 글씨의 에러 때문에 짜증 났던 적이 있을 것이다. 하지만 CORS 정책

it-eldorado.tistory.com

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers

 

Access-Control-Request-Headers - HTTP | MDN

The Access-Control-Request-Headers request header is used by browsers when issuing a preflight request to let the server know which HTTP headers the client might send when the actual request is made (such as with setRequestHeader()). The complementary serv

developer.mozilla.org

https://brownbears.tistory.com/336

 

CORS

개요HTTP 요청은 기본적으로 Cross-Site HTTP Requests가 가능합니다. 다시 말하면, 태그로 다른 도메인의 이미지 파일을 가져오거나, 태그로 다른 도메인의 CSS를 가져오거나, 로 둘러싸여 있는 스크립

brownbears.tistory.com

 

반응형