CORS (PortSwigger Academy) - CORS trust relationships 악용과 방어 방법 (Part 2)
CORS trust 관계 악용, insecure protocol 문제, intranet 환경의 CORS 공격, 그리고 방어 방법 정리
CORS - 신뢰 관계 악용과 방어 방법
Part 1에서는 CORS의 기본 개념과 함께
origin reflection, whitelist 구현 실수, null origin 문제를 다뤘다.
이번에는 조금 더 실전적인 케이스들만 정리한다.
핵심은 신뢰한 origin 자체가 위험한 경우와
프로토콜이나 내부망 환경 때문에 생기는 문제다.
CORS trust 관계와 XSS
CORS 설정만 보면 멀쩡해 보이는데 실제로는 위험한 경우가 있다.
이유는 간단하다. CORS는 결국 origin 간의 “신뢰 관계”를 만들어주는 구조다.
예를 들어 이런 요청이 있다고 보자.
1
2
3
4
GET /api/requestApiKey HTTP/1.1
Host: vulnerable-website.com
Origin: https://subdomain.vulnerable-website.com
Cookie: sessionid=...
서버가 아래처럼 응답한다면
1
2
3
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://subdomain.vulnerable-website.com
Access-Control-Allow-Credentials: true
겉으로 보면 아무 문제 없어 보인다.
허용된 서브도메인만 정확히 열어준 상태다.
그런데 이 서브도메인에 XSS가 하나라도 있으면 상황이 완전히 달라진다.
공격자는 그 취약점을 이용해서 피해자 브라우저에서 스크립트를 실행시키고,
그 상태에서 CORS trust를 그대로 활용해 메인 도메인의 데이터를 읽어올 수 있다.
결국 문제는 CORS 설정 자체가 아니라
신뢰하고 있는 대상이 안전하냐에 있다.
insecure protocol을 신뢰하는 문제
CORS는 host만 보는 게 아니라 scheme까지 포함해서 origin을 판단한다.
이 부분을 놓치면 생각보다 쉽게 공격 경로가 열린다.
예시 요청:
1
2
3
4
GET /api/requestApiKey HTTP/1.1
Host: vulnerable-website.com
Origin: http://trusted-subdomain.vulnerable-website.com
Cookie: sessionid=...
예시 응답:
1
2
3
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://trusted-subdomain.vulnerable-website.com
Access-Control-Allow-Credentials: true
trusted subdomain만 허용했으니까 괜찮아 보일 수 있다.
하지만 자세히 보면 이건 HTTPS가 아니라 HTTP로 설정되어 있는걸 확인할 수 있다.
HTTP는 중간에서 요청이나 응답을 건드릴 수 있다.
그래서 공격자가 네트워크 상에 위치할 수 있다면
trusted origin 자체를 공격자가 원하는 대로 바꿔버릴 수 있다.
흐름을 보면 그렇게 복잡하지 않다.
1
2
3
4
5
6
피해자가 HTTP 요청을 보냄
→ 중간에서 redirect나 응답 조작
→ 공격자가 만든 페이지 로드
→ 그 페이지에서 CORS 요청 발생
→ 서버는 trusted origin이라 판단
→ 데이터 그대로 노출
여기서 중요한 건 이거다.
메인 서비스가 HTTPS를 쓰고 있어도 의미 없다.
신뢰한 대상이 HTTP라면 그 지점이 그대로 공격 경로가 된다.
BurpSuite 예제
Lab: CORS vulnerability with trusted insecure protocols
Intranet 환경과 credential 없는 CORS
보통 CORS에서 위험하다고 하는 건 Access-Control-Allow-Credentials: true가 붙은 경우다.
그래야 쿠키까지 포함된 요청을 보낼 수 있기 때문이다.
그래서 이 헤더가 없으면 크게 문제 없다고 생각하기 쉽다.
외부에서 인증 없이 읽을 수 있는 정보 정도만 노출된다고 보기 때문이다.
그런데 내부망에서는 이야기가 조금 달라진다.
예시 응답:
1
2
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
이런 식으로 열려 있는 내부 서비스가 있다고 가정해보자.
외부에서는 직접 접근이 안 되지만, 내부 사용자의 브라우저는 접근할 수 있다.
이때 공격자는 직접 내부망에 들어갈 필요가 없다.
그냥 사용자의 브라우저를 대신 쓰면 된다.
1
2
3
4
5
사용자가 외부 사이트 접속
→ 그 안에 있는 스크립트 실행
→ 내부망 서버로 요청
→ 브라우저가 응답 받아옴
→ 다시 외부로 전달
이건 인증을 깨는 공격이라기보다는
접근 위치를 우회하는 방식에 가깝다.
CORS는 CSRF 방어가 아니다
CORS를 보안 기능처럼 생각하는 경우가 있는데
이건 개념이 조금 다른 얘기다.
CORS는 요청을 막는 기능이 아니다.
단지 브라우저가 응답을 읽을 수 있는지 결정할 뿐이다.
그래서 CORS가 잘 설정되어 있어도
인증이나 권한 검증이 약하면 그대로 뚫린다.
즉 CORS는 서버 보안을 대신해주지 않는다.
브라우저 동작을 제한하는 규칙일 뿐이다.
CORS 기반 공격 방어 방법
CORS 취약점은 대부분 복잡한 로직 때문에 생기지 않는다.
대충 열어둔 설정에서 터지는 경우가 훨씬 많다.
그래서 접근 방식도 단순하다.
“어디까지 신뢰할지”를 최대한 좁게 잡는 쪽으로 가야 한다.
origin은 필요한 것만 정확히 지정하고,
요청에서 넘어온 값을 그대로 반사하는 방식은 피하는 게 좋다.
이건 거의 바로 악용된다고 보면 된다.
null origin도 마찬가지다.
특수한 상황에서 쉽게 만들어지기 때문에 의도치 않게 열릴 수 있다.
내부망이라고 해서 *를 허용하는 것도 위험하다.
사용자 브라우저는 외부 인터넷과 내부망을 동시에 사용하기 때문에
외부 사이트가 내부 자원에 접근하는 통로가 될 수 있다.
그리고 무엇보다 중요한 건
신뢰한 대상 자체가 안전한지 확인하는 것이다.
서브도메인에 XSS가 있는지,
HTTP를 허용하고 있는지,
외부 도메인을 무분별하게 포함하고 있지는 않은지
이런 부분까지 같이 봐야 한다.
마지막으로, 서버 쪽 보안은 그대로 유지해야 한다.
CORS는 어디까지나 브라우저 정책이기 때문에
인증이나 권한 검증을 대신해주지 않는다.
정리
CORS는 편의를 위한 기능이지만
설정이 느슨하면 그대로 공격 경로가 된다.
결국 문제는 무엇을 어디까지 신뢰할 것인가 하나로 정리된다.
이 기준이 흐려지면 CORS는 보안 기능이 아니라 취약점이 된다.