동일 출처 정책은 웹 브라우저에 구현된 가장 기본적인 보안 메커니즘입니다. 이 정책은 한 출처(origin)에서 로드된 문서나 스크립트가 다른 출처의 리소스와 상호작용하는 것을 제한합니다.
여기서 '출처'는 다음 세 가지 요소의 조합으로 정의됩니다.
이 세 가지 요소가 모두 동일해야만 같은 출처로 간주됩니다.
사용자가 은행 웹 사이트에 로그인한 상태에서 악의적인 사이트를 열면, 그 사이트의 JavaScript가 은행 사이트의 DOM에 접근하여 계좌 정보를 읽거나 송금 요청을 할 수 있게 됩니다. 동일 출처 정책은 이러한 위험으로 부터 사용자를 보호합니다.
때로는 다른 출처와의 통신이 필요한 상황이 있습니다. 이를 위한 안전한 방법들은 다음과 같습니다.
서버가 HTTP 헤더를 통해 특정 출처의 접근을 명시적으로 허용할 수 있습니다.
Access-Control-Allow-Origin: https://trusted-site.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type, Authorization
<script> 태그는 동일 출처 정책의 제약을 받지 않는 점을 이용한 기법입니다. 하지만 보안 취약점이 있어 현대 웹에서는 CORS를 사용하는 것이 권장됩니다.
다른 윈도우(iframe, 팝업 등) 간에 안전하게 메시지를 주고 받을 수 있는 메서드입니다.
XSS는 가장 흔하고 위험한 웹 취약점 중 하나입니다. 공격자가 웹 페이지에 악성 클라이언트 측 코드를 삽입하여 사용자의 브라우저에서 실행되게 하는 공격입니다.
악성 스크립트가 서버에 영구적으로 저장되어 해당 페이지를 방문하는 모든 사용자에게 영향을 미칩니다.
<script>악성코드</script>를 포함한 내용 작성악성 스크립트가 URL 파라미터 등을 통해 웹 애플리케이션에 즉시 반사되어 실행됩니다.
https://example.com/search?q=<script>악성코드</script> 같은 URL 생성클라이언트 측 JavaScript가 안전하지 않은 방식으로 DOM을 조작할 때 발생합니다.
document.getElementById("output").innerHTML = location.hash.substring(1);
이 코드는 URL의 해시 부분을 필터링 없이 DOM에 삽입하므로, website.com/page#<script>악성코드</script>와 같은 URL을 통해 공격이 가능합니다.
사용자 입력을 받을 때는 항상 데이터를 검증하고 필터링해야 합니다.
사용자 입력 데이터를 출력할 때는 컨텍스트에 맞게 인코딩해야 합니다.
// 안전한 DOM 조작 예시
document.getElementById("output").textContent = userInput; // textContent 사용
// 또는
document.getElementById("output").innerText = userInput; // innerText 사용
CSP는 웹 페이지에서 로드될 수 있는 리소스를 제한하여 XSS 공격의 영향을 줄입니다.
Content-Security-Policy: script-src 'self' https://trusted-cdn.com;
이 헤더는 스크립트가 오직 같은 출처와 trusted-cdn.com에서만 로드될 수 있도록 제한합니다.
민감한 쿠키(특히 세션 쿠키)는 다음과 같은 플래그를 설정해야 합니다.
Set-Cookie: SessionId=abc123; HttpOnly; Secure; SameSite=Strict
React, Vue, Angular 같은 현대적인 프레임워크는 기본적으로 자동 이스케이프를 제공하여 XSS 위험을 줄여줍니다.
// React 예시 - 기본적으로 안전하게 렌더링됨
function Component({ userInput }) {
return <div>{userInput}</div>; // 자동으로 이스케이프됨
}
CSRF는 인증된 사용자가 의도하지 않은 요청을 실행하도록 속이는 공격입니다. 사용자가 로그인된 상태에서 악의적인 사이트를 방문하면, 그 사이트가 사용자 모르게 다른 사이트에 요청을 보낼 수 있습니다.
<img src="https://bank.com/transfer?to=attacker&amount=1000" style="display:none">
<!-- 또는 -->
<form id="csrf-form" action="https://bank.com/transfer" method="POST" style="display:none">
<input type="hidden" name="to" value="attacker">
<input type="hidden" name="amount" value="1000">
</form>
<script>document.getElementById("csrf-form").submit();</script>
서버가 예측 불가능한 고유 토큰을 생성하여 세션과 연결합니다. 이 토큰은 모든 상태 변경 요청에 포함되어야 합니다.
<!-- 폼에 CSRF 토큰 포함 -->
<form action="/transfer" method="POST">
<input type="hidden" name="csrf_token" value="random_token_123">
<!-- 나머지 폼 필드 -->
</form>
// AJAX 요청에 CSRF 토큰 포함
fetch('/api/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': 'random_token_123'
},
body: JSON.stringify(data)
});
서버는 이 토큰을 검증하여 요청의 출처를 확인합니다.
쿠키에 SameSite 속성을 설정하여 교차 사이트 요청에서 쿠키가 전송되는 것을 제한할 수 있습니다.
Set-Cookie: SessionId=abc123; SameSite=Lax; Secure; HttpOnly
SameSite 값에 따른 동작:
요청의 Referer 또는 Origin 헤더를 검사하여 예상된 출처에서 왔는지 확인합니다.
세션 저장소 없이 CSRF 공격을 방지하는 방법입니다.
AJAX 요청에 커스텀 헤더를 추가하면 단순 폼 제출이나 이미지 로드로는 해당 헤더를 설정할 수 없어 CSRF 공격을 방지할 수 있습니다.
