콘텐츠 보안 정책은 최신 브라우저에서 교차 사이트 스크립팅 공격의 위험과 영향을 현저히 줄일 수 있습니다.
웹의 보안 모델은 동일 출처 정책을 기반으로 합니다. 예를 들어 https://mybank.com
의 코드는 https://mybank.com
의 데이터에만 액세스할 수 있어야 하며 https://evil.example.com
에는 액세스 권한을 허용해서는 안 됩니다.
각 출처는 이론적으로 나머지 웹과 격리되므로 개발자는 빌드할 수 있는 안전한 샌드박스를 갖게 됩니다. 그러나 실제로는 공격자가 시스템을 손상시키는 여러 가지 방법을 찾아냈습니다.
예를 들어 교차 사이트 스크립팅(XSS) 공격은 사이트를 속여 의도된 콘텐츠와 함께 악성 코드를 전송하도록 하여 동일 출처 정책을 우회합니다. 이는 브라우저가 페이지에 표시되는 모든 코드를 해당 페이지의 합법적인 보안 출처의 일부로 신뢰하므로 심각한 문제를 낳습니다. XSS 요약본은 오래되었지만 공격자가 악성 코드를 삽입하여 이 신뢰를 위반하는 데 사용할 수 있는 방법의 대표적인 섹션입니다. 공격자가 어떤 코드라도 삽입하면 사용자 세션이 손상되고 비공개 정보에 대한 액세스 권한이 획득됩니다.
이 페이지에서는 최신 브라우저에서 XSS 공격의 위험과 영향을 줄이기 위한 전략으로 콘텐츠 보안 정책(CSP)을 설명합니다.
CSP의 구성요소
효과적인 CSP를 구현하려면 다음 단계를 따르세요.
- 허용 목록을 사용하여 클라이언트에게 허용되는 항목과 허용되지 않는 항목을 알립니다.
- 어떤 지시문을 사용할 수 있는지 알아보세요.
- 지시문에서 취하는 키워드를 알아보세요.
- 인라인 코드 및
eval()
의 사용을 제한합니다. - 정책 위반 사항을 적용하기 전에 서버에 신고합니다.
소스 허용 목록
XSS 공격은 브라우저가 애플리케이션에 속한 스크립트와 서드 파티가 악의적으로 주입한 스크립트를 구분하지 못한다는 점을 악용합니다. 예를 들어 이 페이지 하단에 있는 Google +1 버튼은 이 페이지의 출처라는 맥락에서 https://apis.google.com/js/plusone.js
에서 코드를 로드하고 실행합니다.
우리는 그 코드를 신뢰하지만 브라우저가 apis.google.com
의 코드는 실행해도 안전한 반면 apis.evil.example.com
의 코드는 그렇지 않을 것이라고 스스로 알아내리라 기대할 수는 없습니다. 브라우저는 소스에 관계없이 페이지에서 요청하는 코드라면 무엇이든 다운로드하여 실행합니다.
CSP의 Content-Security-Policy
HTTP 헤더를 사용하면 신뢰할 수 있는 콘텐츠 소스의 허용 목록을 만들 수 있으며 이러한 소스의 리소스만 실행하거나 렌더링하도록 브라우저에 지시할 수 있습니다. 공격자가 스크립트를 삽입할 구멍을 찾을 수 있더라도 스크립트가 허용 목록과 일치하지 않으므로 실행되지 않습니다.
Google은 apis.google.com
가 유효한 코드를 제공할 것이라고 신뢰하며, Google도 역시 그렇게 할 것이라고 신뢰합니다. 다음은 스크립트가 두 소스 중 하나에서 가져온 경우에만 실행되도록 허용하는 정책의 예입니다.
Content-Security-Policy: script-src 'self' https://apis.google.com
script-src
는 페이지의 스크립트 관련 권한 집합을 제어하는 지시어입니다. 이 헤더는 'self'
을 스크립트의 한 가지 유효한 소스로 지정하고 https://apis.google.com
를 다른 소스로 지정합니다. 이제 브라우저가 현재 페이지의 출처뿐만 아니라 HTTPS를 통해 apis.google.com
에서 자바스크립트를 다운로드하고 실행할 수 있지만 다른 출처에서는 다운로드할 수 없습니다. 공격자가 사이트에 코드를 삽입하면 브라우저에서 오류가 발생하고 삽입된 스크립트를 실행하지 않습니다.
다양한 리소스에 적용되는 정책
CSP는 이전 예의 script-src
를 비롯하여 페이지가 로드하도록 허용되는 리소스를 세분화하여 제어할 수 있는 정책 지시문을 제공합니다.
다음 목록에는 2단계의 나머지 리소스 지시문이 요약되어 있습니다. 레벨 3 사양 초안이 작성되었지만 주요 브라우저에서는 대부분 구현되지 않았습니다.
base-uri
- 페이지의
<base>
요소에 표시될 수 있는 URL을 제한합니다. child-src
- 작업자 및 삽입된 프레임 콘텐츠의 URL을 나열합니다. 예를 들어
child-src https://youtube.com
는 YouTube에서 가져온 동영상을 삽입할 수 있지만 다른 출처에서 가져온 동영상은 삽입할 수 없습니다. connect-src
- XHR, WebSockets, EventSource를 사용하여 연결할 수 있는 출처를 제한합니다.
font-src
- 웹 글꼴을 제공할 수 있는 출처를 지정합니다. 예를 들어
font-src https://themes.googleusercontent.com
를 사용하여 Google의 웹 글꼴을 허용할 수 있습니다. form-action
<form>
태그에서 제출할 수 있는 유효한 엔드포인트를 나열합니다.frame-ancestors
- 현재 페이지를 삽입할 수 있는 소스를 지정합니다. 이 지시어는
<frame>
,<iframe>
,<embed>
,<applet>
태그에 적용됩니다.<meta>
태그 또는 HTML 리소스에 사용할 수 없습니다. frame-src
- 이 지시문은 수준 2에서 지원 중단되었지만 수준 3에서 복원되었습니다. 존재하지 않으면 브라우저는
child-src
로 대체됩니다. img-src
- 이미지를 로드할 수 있는 출처를 정의합니다.
media-src
- 동영상 및 오디오를 전송할 수 있는 출처를 제한합니다.
object-src
- 플래시 및 기타 플러그인을 제어할 수 있습니다.
plugin-types
- 페이지에서 호출할 수 있는 플러그인의 종류를 제한합니다.
report-uri
- 콘텐츠 보안 정책을 위반하면 브라우저에서 보고서를 전송하는 URL을 지정합니다. 이 지시어는
<meta>
태그에서는 사용할 수 없습니다. style-src
- 페이지에서 스타일시트를 사용할 수 있는 출처를 제한합니다.
upgrade-insecure-requests
- 사용자 에이전트에 HTTP를 HTTPS로 변경하여 URL 스키마를 다시 작성하도록 지시합니다. 이 지시문은 다시 작성해야 할 이전 URL이 많은 웹사이트를 위한 것입니다.
worker-src
- 워커, 공유 워커 또는 서비스 워커로 로드할 수 있는 URL을 제한하는 CSP 수준 3 디렉티브입니다. 2017년 7월부터 이 지시어는 제한된 구현을 갖습니다.
기본적으로 브라우저는 특정 지시어로 정책을 설정하지 않는 한 제한 없이 모든 출처에서 연결된 리소스를 로드합니다. 기본값을 재정의하려면 default-src
지시문을 지정합니다. 이 지시문은 -src
로 끝나는 지정되지 않은 지시문에 대한 기본값을 정의합니다. 예를 들어 default-src
를 https://example.com
로 설정하고 font-src
지시문을 지정하지 않으면 https://example.com
에서만 글꼴을 로드할 수 있습니다.
다음 지시문은 default-src
를 대안으로 사용하지 않습니다. 이를 설정하지 않으면 무엇이든 허용한다는 점에 유의하세요.
base-uri
form-action
frame-ancestors
plugin-types
report-uri
sandbox
기본 CSP 문법
CSP 지시문을 사용하려면 지시문을 콜론으로 구분하여 HTTP 헤더에 나열하세요. 다음과 같이 특정 유형의 필수 리소스를 모두 단일 지시문에 나열해야 합니다.
script-src https://host1.com https://host2.com
다음은 여러 디렉티브의 예입니다. 이 경우 https://cdn.example.net
의 콘텐츠 전송 네트워크에서 모든 리소스를 로드하고 프레임이 지정된 콘텐츠나 플러그인을 사용하지 않는 웹 앱의 예입니다.
Content-Security-Policy: default-src https://cdn.example.net; child-src 'none'; object-src 'none'
구현 세부정보
최신 브라우저는 접두사가 없는 Content-Security-Policy
헤더를 지원합니다.
권장되는 헤더입니다. 온라인 튜토리얼에서 볼 수 있는 X-WebKit-CSP
및 X-Content-Security-Policy
헤더는 지원 중단되었습니다.
CSP는 페이지별로 정의됩니다. 보호하려는 모든 응답과 함께 HTTP 헤더를 전송해야 합니다. 이를 통해 구체적인 필요에 따라 특정 페이지에 대한 정책을 미세 조정할 수 있습니다. 예를 들어 사이트의 한 페이지 세트에는 +1 버튼이 있는 반면 다른 페이지 세트에는 없는 경우 필요할 때만 버튼 코드가 로드되도록 허용할 수 있습니다.
각 지시어의 소스 목록은 유연하게 작성할 수 있습니다. 구성표(data:
, https:
)를 기준으로 소스를 지정하거나, 호스트 이름만 사용하는 것부터(해당 호스트에서 임의의 출처와 일치하는 example.com
: 임의의 구성표, 임의의 포트) 정규화된 URI를 사용하는 것까지(HTTPS만, example.com
만, 포트 443만 서로 일치하는 https://example.com:443
) 범위를 특정하여 소스를 지정할 수 있습니다. 와일드 카드는 스키마, 포트 또는 호스트 이름의 가장 왼쪽 위치에서만 허용됩니다. *://*.example.com:*
는 모든 포트에서 스키마를 사용하여 example.com
의 모든 하위 도메인 (example.com
자체는 아님)과 일치합니다.
소스 목록에는 다음과 같은 4개의 키워드도 허용됩니다.
'none'
는 아무것도 일치시키지 않습니다.'self'
는 현재 출처와 일치하지만 하위 도메인은 일치하지 않습니다.'unsafe-inline'
은 인라인 JavaScript 및 CSS를 허용합니다. 자세한 내용은 인라인 코드 피하기를 참고하세요.'unsafe-eval'
는eval
와 같은 텍스트-JavaScript 메커니즘을 허용합니다. 자세한 내용은eval()
사용 지양을 참고하세요.
이러한 키워드에는 작은따옴표가 필요합니다. 예를 들어 script-src 'self'
(따옴표 포함)는 현재 호스트에서 자바스크립트를 실행할 권한을 부여합니다. script-src self
(따옴표 없음)는 'self
'라는 이름의 서버에서 자바스크립트를 허용하며(현재 호스트에서는 허용되지 않음) 이는 의도한 바가 아닐 수 있습니다.
페이지 샌드박스 처리
설명하고 넘어가야 할 지시문이 하나 더 있는데, 그건 바로 sandbox
입니다. 이 지시문은 페이지가 로드할 수 있는 리소스가 아니라 페이지가 취할 수 있는 작업에 제한을 두므로 지금까지 살펴본 다른 지시문과는 약간 다릅니다. sandbox
지시문이 있는 경우 페이지는 sandbox
속성을 가진 <iframe>
내부에서 로드된 것처럼 취급됩니다. 이는 페이지에 광범위한 효과를 미칠 수 있는데, 무엇보다도 페이지를 고유한 출처로 강제 적용하고 양식 제출을 방지한다는 점입니다. 이 페이지의 범위를 약간 벗어나는 내용이긴 하지만, HTML5 사양의 '샌드박싱' 섹션에서 유효한 샌드박싱 속성에 대한 전체 세부정보를 확인할 수 있습니다.
메타 태그
CSP에서 기본 설정된 전송 메커니즘은 HTTP 헤더입니다. 하지만 마크업에서 페이지에 대한 정책을 직접 설정하는 데 유용할 수 있습니다. http-equiv
속성이 있는 <meta>
태그를 사용하면 됩니다.
<meta http-equiv="Content-Security-Policy" content="default-src https://cdn.example.net; child-src 'none'; object-src 'none'">
frame-ancestors
, report-uri
또는 sandbox
에는 사용할 수 없습니다.
인라인 코드 피하기
CSP 지시어에 사용되는 출처 기반 허용 목록만큼 강력하지만 XSS 공격으로 인한 가장 큰 위협인 인라인 스크립트 삽입은 해결할 수 없습니다.
공격자가 일부 악성 페이로드(예: <script>sendMyDataToEvilDotCom()</script>
)를 직접 포함하는 스크립트 태그를 삽입할 수 있는 경우 브라우저는 이를 합법적인 인라인 스크립트 태그와 구분할 방법이 없습니다. CSP는 인라인 스크립트를 완전히 금지하여 이 문제를 해결합니다.
이러한 금지에는 script
태그에 직접 삽입된 스크립트뿐 아니라 인라인 이벤트 핸들러와 javascript:
URL도 포함됩니다. script
태그의 콘텐츠를 외부 파일로 이동하고 javascript:
URL과 <a ...
onclick="[JAVASCRIPT]">
를 적절한 addEventListener()
호출로 바꾸어야 합니다. 예를 들어 다음과 같은 양식을
<script>
function doAmazingThings() {
alert('YOU ARE AMAZING!');
}
</script>
<button onclick='doAmazingThings();'>Am I amazing?</button>
다음과 같이 변경해야 합니다.
<!-- amazing.html -->
<script src='amazing.js'></script>
<button id='amazing'>Am I amazing?</button>
// amazing.js
function doAmazingThings() {
alert('YOU ARE AMAZING!');
}
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('amazing')
.addEventListener('click', doAmazingThings);
});
다시 작성된 코드는 CSP와 호환될 뿐만 아니라 웹 디자인 권장사항에도 부합합니다. 인라인 JavaScript는 코드를 혼란스럽게 만드는 방식으로 구조와 동작을 혼합합니다. 또한 캐시하고 컴파일하는 것이 더 복잡합니다. 코드를 외부 리소스로 이동하면 페이지 실적이 개선됩니다.
CSS 기반 데이터 무단 반출 공격으로부터 사이트를 보호하려면 인라인 style
태그와 속성을 외부 스타일시트로 이동하는 것이 좋습니다.
인라인 스크립트 및 스타일을 일시적으로 허용하는 방법
script-src
또는 style-src
지시문에 'unsafe-inline'
를 허용된 소스로 추가하여 인라인 스크립트와 스타일을 사용 설정할 수 있습니다. CSP 수준 2에서는 다음과 같이 암호화 nonce (한 번 사용되는 숫자) 또는 해시를 사용하여 특정 인라인 스크립트를 허용 목록에 추가할 수도 있습니다.
난스를 사용하려면 스크립트 태그에 난스 속성을 부여하세요. 이 값은 신뢰할 수 있는 소스 목록에 있는 값과 일치해야 합니다. 예를 들면 다음과 같습니다.
<script nonce="EDNnf03nceIOfn39fn3e9h3sdfa">
// Some inline code I can't remove yet, but need to as soon as possible.
</script>
nonce-
키워드 뒤의 script-src
지시문에 난스를 추가합니다.
Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'
난스는 모든 페이지 요청에 대해 다시 생성해야 하며 추측할 수 없어야 합니다.
해시도 대체로 같은 방식으로 작동합니다. 스크립트 태그에 코드를 추가하는 대신 스크립트 자체의 SHA 해시를 생성하여 script-src
지시문에 추가합니다.
예를 들어 페이지에 다음이 포함되어 있다고 가정해 보겠습니다.
<script>alert('Hello, world.');</script>
정책에는 다음이 포함되어야 합니다.
Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng='
sha*-
접두사는 해시를 생성하는 알고리즘을 지정합니다. 이전 예에서는 sha256-
를 사용하지만 CSP는 sha384-
및 sha512-
도 지원합니다. 해시를 생성할 때는 <script>
태그를 생략합니다. 선행 및 후행 공백을 포함한 대문자와 공백입니다.
SHA 해시를 생성하는 솔루션은 여러 언어로 제공됩니다. Chrome 40 이상을 사용할 때는 DevTools를 연 다음 페이지를 새로고침할 수 있습니다. Console 탭에는 각 인라인 스크립트에 대해 올바른 SHA-256 해시가 있는 오류 메시지가 표시됩니다.
eval()
사용 자제
공격자가 스크립트를 직접 삽입할 수 없는 경우에도 애플리케이션을 속여 입력 텍스트를 실행 가능한 JavaScript로 변환하고 대신 실행하도록 할 수 있습니다. eval()
, new Function()
, setTimeout([string], …)
, setInterval([string], ...)
는 모두 공격자가 삽입된 텍스트를 통해 악성 코드를 실행하는 데 사용할 수 있는 벡터입니다. 이 위험에 대한 CSP의 기본 응답은 이러한 벡터를 전부 완전히 차단하는 것입니다.
이는 애플리케이션을 빌드하는 방식에 다음과 같은 영향을 미칩니다.
eval
에 의존하는 대신 기본 제공JSON.parse
를 사용하여 JSON을 파싱해야 합니다. 안전한 JSON 작업은 IE8 이후의 모든 브라우저에서 사용할 수 있습니다.문자열 대신 인라인 함수를 사용하여 실행하는 모든
setTimeout
또는setInterval
호출을 다시 작성해야 합니다. 예를 들어 페이지에 다음이 포함되어 있다고 가정해 보겠습니다.setTimeout("document.querySelector('a').style.display = 'none';", 10);
다음과 같이 다시 작성하세요.
setTimeout(function () { document.querySelector('a').style.display = 'none'; }, 10); ```
런타임에서 인라인 템플릿을 사용하지 마세요. 많은 템플릿 라이브러리는 런타임에 템플릿 생성 속도를 높이기 위해
new Function()
를 자주 사용합니다. 이로 인해 악성 텍스트를 평가할 수 있습니다. 일부 프레임워크는 CSP를 즉시 지원하여eval
가 없으면 강력한 파서로 대체합니다. AngularJS의 ng-csp 지시어가 이에 대한 좋은 예입니다. 대신 Handlebars와 같이 사전 컴파일을 제공하는 템플릿 언어를 사용하는 것이 좋습니다. 템플릿을 사전 컴파일하면 사용자 환경을 가장 빠른 런타임 구현 환경보다도 훨씬 더 빠르게 할 수 있을 뿐만 아니라 사이트의 보안도 강화할 수 있습니다.
eval()
또는 기타 텍스트-자바스크립트 함수가 애플리케이션에 필수적인 경우 script-src
지시어에 'unsafe-eval'
를 허용된 소스로 추가하여 사용할 수 있습니다. 코드 삽입에 따른 위험이 있으므로 이 방법은 사용하지 않는 것이 좋습니다.
정책 위반사항 신고
악의적인 삽입을 허용할 수 있는 버그를 서버에 알리려면 브라우저에 report-uri
지시문에 지정된 위치에 JSON 형식의 위반 보고서를 POST
하도록 지시할 수 있습니다.
Content-Security-Policy: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;
이러한 보고서는 다음과 같이 표시됩니다.
{
"csp-report": {
"document-uri": "http://example.org/page.html",
"referrer": "http://evil.example.com/",
"blocked-uri": "http://evil.example.com/evil.js",
"violated-directive": "script-src 'self' https://apis.google.com",
"original-policy": "script-src 'self' https://apis.google.com; report-uri http://example.org/my_amazing_csp_report_parser"
}
}
이 보고서에는 정책 위반의 원인을 찾는 데 도움이 되는 정보(예: 정책 위반이 발생한 페이지(document-uri
), 해당 페이지의 referrer
, 페이지의 정책을 위반한 리소스(blocked-uri
), 위반한 구체적인 지침(violated-directive
), 페이지의 전체 정책(original-policy
))가 포함되어 있습니다.
보고서 전용
CSP를 처음 사용하는 경우 정책을 변경하기 전에 보고서 전용 모드를 사용하여 앱 상태를 평가하는 것이 좋습니다. 이렇게 하려면 Content-Security-Policy
헤더를 보내는 대신 Content-Security-Policy-Report-Only
헤더를 전송합니다.
Content-Security-Policy-Report-Only: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;
보고서 전용 모드에 지정된 정책은 한정된 리소스를 차단하지는 않지만 지정된 위치로 위반 보고서를 보냅니다. 한 정책을 적용하는 한편으로 다른 정책은 모니터링하면서 두 헤더를 모두 보낼 수도 있습니다. 이는 현재 정책을 시행하면서 CSP의 변경사항을 테스트할 수 있는 좋은 방법입니다. 새 정책에 대한 보고를 사용 설정하고, 위반 신고를 모니터링하고 버그를 수정한 다음, 새 정책에 만족하면 시행을 시작해 보세요.
실제 사용
앱에 적용할 정책을 만드는 첫 번째 단계는 로드하는 리소스를 평가하는 것입니다. 앱의 구조를 파악한 후 요구사항에 따라 정책을 만듭니다. 다음 섹션에서는 몇 가지 일반적인 사용 사례와 CSP 가이드라인에 따라 이를 지원하기 위한 결정 프로세스를 살펴봅니다.
소셜 미디어 위젯
- Facebook의 좋아요 버튼에는 여러 가지 구현 옵션이 있습니다.
<iframe>
버전을 사용하여 사이트의 나머지 부분과 샌드박스 처리하는 것이 좋습니다. 제대로 작동하려면child-src https://facebook.com
지시어가 필요합니다. - X의 트윗 버튼은 스크립트에 대한 액세스 권한을 사용합니다.
제공하는 스크립트를 외부 JavaScript 파일로 이동하고
script-src https://platform.twitter.com; child-src https://platform.twitter.com
디렉티브를 사용합니다. - 다른 플랫폼에도 비슷한 요구사항이 있으며, 역시 비슷한 방법으로 해결할 수 있습니다.
이러한 리소스를 테스트하려면
default-src
를'none'
로 설정하고 콘솔을 살펴보면서 사용 설정해야 하는 리소스를 확인하는 것이 좋습니다.
여러 위젯을 사용하려면 다음과 같이 지시어를 결합하세요.
script-src https://apis.google.com https://platform.twitter.com; child-src https://plusone.google.com https://facebook.com https://platform.twitter.com
잠금
일부 웹사이트의 경우 로컬 리소스만 로드될 수 있도록 해야 합니다. 다음 예에서는 모든 것을 차단하는 기본 정책(default-src 'none'
)으로 시작하여 은행 사이트의 CSP를 개발합니다.
사이트는 https://cdn.mybank.net
의 CDN에서 모든 이미지, 스타일, 스크립트를 로드하고 XHR을 사용하여 https://api.mybank.com/
에 연결하여 데이터를 검색합니다. 프레임을 사용하지만 사이트에 대해 로컬인 페이지에만 사용합니다(서드 파티 출처 없음). 이 사이트에는 플래시도, 글꼴도, 추가 기능도 없습니다. 전송할 수 있는 가장 제한적인 CSP 헤더는 다음과 같습니다.
Content-Security-Policy: default-src 'none'; script-src https://cdn.mybank.net; style-src https://cdn.mybank.net; img-src https://cdn.mybank.net; connect-src https://api.mybank.com; child-src 'self'
SSL만
다음은 포럼의 모든 리소스가 보안 채널을 통해서만 로드되도록 하려는 포럼 관리자를 위한 CSP의 예입니다. 이 관리자는 코딩 경험이 없고 인라인 스크립트와 스타일로 가득 찬 서드 파티 포럼 소프트웨어를 다시 작성할 리소스가 없습니다.
Content-Security-Policy: default-src https:; script-src https: 'unsafe-inline'; style-src https: 'unsafe-inline'
https:
가 default-src
에 지정되어 있지만 스크립트 및 스타일 지시문은 해당 소스를 자동으로 상속하지 않습니다. 각 지시문은 특정 리소스 유형의 기본값을 덮어씁니다.
CSP 표준 개발
Content Security Policy Level 2는 W3C 권장 표준입니다. W3C의 Web Application Security Working Group은 사양의 다음 반복 버전인 Content Security Policy Level 3을 개발하고 있습니다.
이러한 출시 예정 기능에 대한 논의를 보려면 public-webappsec@ 메일링 리스트 자료실을 참조하세요.