keyword : 보안, 콘텐츠 보안 정책, Content Security Policy, CSP
최근 크롬 확장 프로그램의 구조를 분석할 일이 있었습니다.
그러다 리퀘스트 / 리스폰스 요청 후킹 및 콘텐츠 스크립트 인젝션과 관련된
콘텐츠 보안 정책에 대해 깊게 공부할 필요성을 느끼게 되어 정리합니다.
크롬 확장 프로그램과 워커는 일련의 서버 역할을 하므로, 효과적인 활용을 위해선 서버 측 지식의 공부도 필요합니다.
아래 글의 번역입니다.
요약
- 출처 허용 목록(allowlists)을 사용하여 허용되는 항목과 허용되지 않는 항목을 클라이언트에 알립니다.
- 허용 목록에 대해 어떤 지침;지시문(directives)을 사용할 수 있는지 알아보세요.
- 지시문 키워드를 배우세요
- ex) script-src
- 인라인 코드와 eval()은 기본적으로 유해하다 간주합니다.
- 정책을 적용하기 전에, 발생한 문제를 서버로 보고합니다. (보고 전용 모드)
출처 허용 목록(Source allowlists)
오리진과, 구글 api에서 오는 스크립트만 허용하는 정책
Content-Security-Policy: script-src 'self' https://apis.google.com
다양한 리소스에 대한 정책
- base-uri : 페이지의 <base> 요소에 나타날 수 있는 URL을 제한합니다.
- 참고(base-url) : https://www.w3schools.com/tags/tag_base.asp
- child-src : worker 및 embedded 프레임 콘텐츠에 대한 URL을 나열합니다.
- child-src : https://youtube.com 는 유투부에서 온 동영상을 포함할 수 있으나, 다른 출처의 리소스는 사용할 수 없습니다.
- connect-src : (XHR, WebSockets 및 EventSource를 통해) 연결할 수 있는 출처를 제한합니다.
- font-src : 웹 폰트를 제공할 수 있는 출처를 지정합니다.
- Google의 웹 글꼴은 font-src https://themes.googleusercontent.com을 통해 활성화할 수 있습니다.
- Google의 웹 글꼴은 font-src https://themes.googleusercontent.com을 통해 활성화할 수 있습니다.
- form-action : <form> 태그의 유효한 양식 제출 타겟을 나열합니다.
- frame-ancestors : 현재 페이지를 포함할 수 있는 소스를 지정합니다.
- 이 지시문(directive)은 <frame>, <iframe>, <embed> 및 <applet> 태그에 적용됩니다.
- 이 지시문은 <meta> 태그에서 사용할 수 없으며 HTML이 아닌 리소스에만 적용됩니다.
- frame-src : 레벨 2에서 더 이상 사용되지 않지만 레벨 3에서 복원됩니다.
- child-src와 동일합니다.
- child-src와 동일합니다.
- img-src : 이미지를 로드할 수 있는 출처를 정의합니다.
- media-src : 비디오 및 오디오를 제공할 수 있는 출처를 제한합니다.
- object-src : Flash 및 기타 플러그인을 제어할 수 있습니다.
- plugin-types : 페이지가 호출할 수 있는 플러그인의 종류를 제한합니다.
- report-uri : 콘텐츠 보안 정책을 위반한 경우 브라우저가 보고서를 보낼 URL을 지정합니다.
- 이 지시문은 <meta> 태그에서 사용할 수 없습니다.
- 이 지시문은 <meta> 태그에서 사용할 수 없습니다.
- style-src : 스타일시의 script-src의 역할을 합니다.
- upgrade-insecure-requests :
- 사용자 에이전트에게 HTTP를 HTTPS로 변경하여 URL 체계를 다시 작성하도록 지시합니다.
- 이 지시문은 재 작성해야 하는 이전 URL이 많은 웹사이트를 위한 것입니다.
- worker-src :
- 작업자, 공유 작업자 또는 서비스 작업자로 로드될 수 있는 URL을 제한하는 CSP 레벨 3 지시문 입니다.
- 2017년 7월 현재 이 지시문은 제한적으로 구현되어 있습니다.(limited implementations)
- base-uri
- form-action
- frame-ancestors
- plugin-types
- report-uri
- sandbox
script-src https://host1.com https://host2.com
Content-Security-Policy: default-src https://cdn.example.net; child-src 'none'; object-src 'none'
구현 세부사항
웹의 다양한 자습서에서 X-WebKit-CSP 및 X-Content-Security-Policy 헤더를 볼 수 있습니다.
앞으로는 이러한 접두사 헤더를 무시해야 합니다.
- 스킴(data:, https:)별로 출처를 지정할 수 있습니다.
- 호스트 이름(example.com, 해당 호스트의 모든 출처: 모든 스킴, 모든 포트와 일치)부터 정규화된 URI(https:/ /example.com:443(HTTPS만 일치, example.com만 일치, 포트 443만 일치)까지 구체적으로 지시 가능합니다.
- 와일드카드는 스키마, 포트 또는 호스트 이름의 가장 왼쪽 위치에서만 허용됩니다.
- *://*.example.com:*은 example.com의 모든 포트, 모든 스키마, 모든 하위 도메인과 일치하지만, example.com 자체와는 일치하지 않습니다.
출처 목록에는 다음 네 가지 키워드도 허용됩니다.
- 'none' : 예상할 수 있듯이 아무 것도 일치하지 않습니다.
- 'self' : 현재 출처와 일치하지만 하위 도메인은 일치하지 않습니다.
- 'unsafe-inline' : 인라인 JavaScript 및 CSS를 허용합니다.
- 'unsafe-eval'은 eval과 같은 텍스트 to 자바스크립트 메커니즘을 허용합니다.
- 예를 들어 script-src 'self'(따옴표 포함)는 현재 호스트에서 JavaScript 실행을 승인합니다.
- script-src self(따옴표 없음)는 "self"라는 이름의 서버(현재 호스트가 아닌)의 JavaScript를 허용합니다.
- 이는 분명 당신이 원하는 것이 아닐 것입니다.
샌드박싱
메타 태그
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에는 사용할 수 없습니다.
인라인 코드는 유해한 것으로 간주됩니다
<script>
function doAmazingThings() {
alert('YOU AM 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 AM AMAZING!');
}
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('amazing')
.addEventListener('click', doAmazingThings);
});
- CSP 사용에 관계없이 이미 모범 사례입니다.
- 인라인 JavaScript는 데이터(마크업) 구조와 동작(기능)을 혼합해서는 안 되는 방식으로 혼합합니다.
- 외부 리소스는 브라우저가 캐싱하기 쉽고 개발자가 이해하기 쉬우며 컴파일 및 축소에 도움이 됩니다.
- 코드를 외부 리소스로 옮기는 작업을 수행하면 더 나은 코드를 작성할 수 있습니다(모듈화).
인라인 스타일 또한 같은 방식으로 처리되어야 합니다.
- 스타일 속성과 스타일 태그는 모두 외부 스타일시트로 통합되어, CSS가 가능하게 하는 놀랍도록 영리한 다양한 데이터 유출 방법(surprisingly clever)으로부터 보호해야 합니다.
인라인 스크립를 꼭 사용해야 한다면
CSP 레벨 2는 암호화 nonce(한 번 사용되는 숫자) 또는 해시를 사용해
allowlist에 특정 인라인 스크립트를 추가할 수 있도록 하여 인라인 스크립트에 대한 이전 버전과의 호환성을 제공합니다.
nonce 사용 방법
스크립트 태그에 nonce 속성을 지정합니다.
해당 값은 신뢰할 수 있는 소스 목록의 값과 일치해야 합니다.
예를 들어 다음과 같습니다.
<script nonce="EDNnf03nceIOfn39fn3e9h3sdfa">
// Some inline code I can't remove yet, but need to asap.
</script>
이제 nonce- 키워드에 추가된 script-src 지시문에 nonce를 추가합니다.
Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'
nonce는 모든 페이지 요청에 대해 다시 생성되어야 하며 추측할 수 없어야 합니다.
hash 사용 방법
해시는 거의 같은 방식으로 작동합니다.
스크립트 태그에 코드를 추가하는 대신 스크립트 자체의 SHA 해시를 만들어 script-src 지시문에 추가합니다.
예를 들어 페이지에 다음이 포함되어 있다고 가정해 보겠습니다.
<script>alert('Hello, world.');</script>
귀하의 정책에는 다음이 포함됩니다.
Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng='
- sha*- 접두사는 해시를 생성하는 알고리즘을 지정합니다.
- 위의 예에서는 sha256-이 사용됩니다. CSP는 sha384- 및 sha512-도 지원합니다.
- 해시를 생성할 때 <script> 태그를 포함하지 마세요.
- 또한 선행 또는 후행 공백을 포함하여 대문자 및 공백이 중요합니다.
Eval도 마찬가지 입니다
- 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 지시문(AngularJS's ng-csp directive)이 이에 대한 좋은 예입니다.
보고하기
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"
}
}
보고부터 시작하기
Content-Security-Policy-Report-Only: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;
실제 예제로 배우기
사용 사레 #1 : 소셜 미디어 위젯
Facebook의 좋아요 버튼(Like button)에는 다양한 구현 옵션이 있습니다.
사이트의 다른 부분에서 안전하게 샌드박스 처리되므로 <iframe> 을 사용하는 것을 추천합니다.
Twitter의 트윗 버튼(Tweet button)은 스크립트 및 프레임에 둘 다 의존합니다.
이 둘은 동일하게 https://platform.twitter.com 호스팅됩니다.
Twitter도 마찬가지로 디폴트로 상대 URL을 제공합니다.
만약 당신의 자바스크립트 파일이
Twitter가 제공하는 JavaScript 스니펫을 참조하고 있다면,
다음과 같이 로컬에서 복사/붙여넣기할 때 HTTPS를 지정하도록 코드를 편집하세요.
script-src https://platform.twitter.com; child-src https://platform.twitter.com
다른 플랫폼도 유사한 요구 사항을 갖고 있으며 유사하게 처리할 수 있습니다.
script-src https://apis.google.com https://platform.twitter.com; child-src https://plusone.google.com https://facebook.com https://platform.twitter.com
사용 사레 #2 : 내 리소스만 허용하기
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'
사용 사레 #3 : SSL만 허용하기
Content-Security-Policy: default-src https:; script-src https: 'unsafe-inline'; style-src https: 'unsafe-inline'
https:가 default-src에 지정되어 있어도 스크립트 및 스타일 지시문은 해당 출처를 자동으로 상속하지 않습니다.
각 지시문은 특정 타입의 리소스에 대한 기본값을 완전히 덮어씁니다.
CSP의 미래
content security policy level2는 권고 사항(Recommendation)입니다.
현재 Content Security Policy Level 3 에 대한 작업이 진행 중입니다.
참고
https://web.dev/sandboxed-iframes/
https://ko.javascript.info/server-sent-events
'FrontEnd' 카테고리의 다른 글
자바스크립트 스킬 티어 리스트 (0) | 2023.01.22 |
---|---|
Rollup.js 알아보기 1편 : Rollup.js 튜토리얼 (0) | 2023.01.21 |
Vue3과 서버사이드 렌더링(SSR) (0) | 2023.01.19 |
ECMAScript(ESM)의 module resolution(모듈 해석)에 대해 알아보자 (0) | 2023.01.16 |
Node.JS 앱은 어떻게 종료되는가? (0) | 2023.01.15 |