Chrome의 확장 프로그램 시스템은 상당히 엄격한 기본 콘텐츠 보안 정책 (CSP)을 적용합니다.
정책 제한은 간단합니다. 스크립트가 다른 자바스크립트 파일로 이동되어야 하고, 인라인 이벤트 핸들러는 addEventListener
을 사용하도록 변환되어야 하며, eval()
는 사용 중지됩니다.
그러나 다양한 라이브러리가 성능 최적화와 표현의 용이성을 위해 eval()
및 eval
와 유사한 구성(예: new Function()
)을 사용한다는 것을 알고 있습니다. 템플릿 라이브러리는 특히 이러한 스타일의 구현에 취약합니다. 일부 (예: Angular.js)는 기본적으로 CSP를 지원하지만, 많은 인기 있는 프레임워크는 아직 확장 프로그램의 eval
가 없는 환경과 호환되는 메커니즘으로 업데이트되지 않았습니다. 따라서 이 기능의 지원을 중단하는 것은 개발자에게 예상보다 더 큰 문제가 되는 것으로 입증되었습니다.
이 문서에서는 보안을 손상하지 않고 프로젝트에 이러한 라이브러리를 포함하는 안전한 메커니즘으로 샌드박스를 소개합니다.
샌드박스를 사용해야 하는 이유
eval
은 확장 프로그램 내에서 위험합니다. 실행되는 코드가 확장 프로그램의 높은 권한 환경에 있는 모든 항목에 액세스할 수 있기 때문입니다. 사용자의 보안 및 개인 정보 보호에 심각한 영향을 미칠 수 있는 강력한 chrome.*
API가 다양하게 마련되어 있습니다. 단순한 데이터 무단 반출이 가장 우려됩니다.
제공되는 솔루션은 eval
이 확장 프로그램의 데이터 또는 확장 프로그램의 가치가 높은 API에 액세스하지 않고도 코드를 실행할 수 있는 샌드박스입니다. 데이터, API, 문제 없음
이를 위해 확장 프로그램 패키지 내의 특정 HTML 파일을 샌드박스 처리된 것으로 나열합니다.
샌드박스 처리된 페이지는 로드될 때마다 고유한 원본으로 이동하며 chrome.*
API에 대한 액세스가 거부됩니다. iframe
를 통해 샌드박스 처리된 페이지를 확장 프로그램에 로드하면 이 페이지에 메시지를 전달하고, 이러한 메시지에 대해 어떤 방식으로든 조치를 취하고, 결과가 반환될 때까지 기다릴 수 있습니다. 이 간단한 메시지 메커니즘을 통해 확장 프로그램의 워크플로에 eval
기반 코드를 안전하게 포함하는 데 필요한 모든 것을 얻을 수 있습니다.
샌드박스 만들기 및 사용
코드를 바로 살펴보고 싶다면 샌드박스 샘플 확장 프로그램을 가져와 실행합니다. Handlebars 템플릿 라이브러리를 기반으로 빌드된 작은 메시지 API의 실제 예이며 시작하는 데 필요한 모든 것을 제공합니다. 좀 더 자세한 설명을 원하는 분들을 위해 여기에 있는 샘플을 함께 살펴보겠습니다.
매니페스트에 파일 나열
샌드박스 내에서 실행해야 하는 각 파일은 sandbox
속성을 추가하여 확장 프로그램 매니페스트에 나열해야 합니다. 이 단계는 중요한 단계이며 잊어버리기 쉬우므로 샌드박스 처리된 파일이 매니페스트에 나열되어 있는지 다시 한번 확인하세요. 이 샘플에서는 'sandbox.html'이라는 멋진 파일을 샌드박스로 만듭니다. 매니페스트 항목은 다음과 같습니다.
{
...,
"sandbox": {
"pages": ["sandbox.html"]
},
...
}
샌드박스 처리된 파일 로드
샌드박스 처리된 파일에서 흥미로운 작업을 하려면 확장 프로그램의 코드로 처리할 수 있는 컨텍스트에서 파일을 로드해야 합니다. 여기서 sandbox.html이 iframe
를 통해 확장 프로그램 페이지에 로드되었습니다. 페이지의 JavaScript 파일에는 브라우저 작업을 클릭할 때마다 페이지에서 iframe
를 찾고 contentWindow
에서 postMessage()
를 호출하여 샌드박스로 메시지를 전송하는 코드가 포함되어 있습니다. 이 메시지는 context
, templateName
, command
의 세 가지 속성을 포함하는 객체입니다. 곧 context
및 command
에 대해 자세히 알아보겠습니다.
service-worker.js:
chrome.action.onClicked.addListener(() => {
chrome.tabs.create({
url: 'mainpage.html'
});
console.log('Opened a tab with a sandboxed page!');
});
extension-page.js:
let counter = 0;
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('reset').addEventListener('click', function () {
counter = 0;
document.querySelector('#result').innerHTML = '';
});
document.getElementById('sendMessage').addEventListener('click', function () {
counter++;
let message = {
command: 'render',
templateName: 'sample-template-' + counter,
context: { counter: counter }
};
document.getElementById('theFrame').contentWindow.postMessage(message, '*');
});
위험한 행동
sandbox.html
가 로드되면 Handlebars 라이브러리가 로드되고 Handlebars에서 제안하는 방식으로 인라인 템플릿을 만들고 컴파일합니다.
extension-page.html:
<!DOCTYPE html>
<html>
<head>
<script src="mainpage.js"></script>
<link href="styles/main.css" rel="stylesheet" />
</head>
<body>
<div id="buttons">
<button id="sendMessage">Click me</button>
<button id="reset">Reset counter</button>
</div>
<div id="result"></div>
<iframe id="theFrame" src="sandbox.html" style="display: none"></iframe>
</body>
</html>
sandbox.html:
<script id="sample-template-1" type="text/x-handlebars-template">
<div class='entry'>
<h1>Hello</h1>
<p>This is a Handlebar template compiled inside a hidden sandboxed
iframe.</p>
<p>The counter parameter from postMessage() (outer frame) is:
</p>
</div>
</script>
<script id="sample-template-2" type="text/x-handlebars-template">
<div class='entry'>
<h1>Welcome back</h1>
<p>This is another Handlebar template compiled inside a hidden sandboxed
iframe.</p>
<p>The counter parameter from postMessage() (outer frame) is:
</p>
</div>
</script>
이것은 실패하지 않습니다. Handlebars.compile
가 new Function
를 사용하게 되더라도 작업은 정확히 예상대로 작동하고 templates['hello']
에 컴파일된 템플릿이 생성됩니다.
결과 전달
확장 프로그램 페이지에서 명령어를 수락하는 메시지 리스너를 설정하여 이 템플릿을 사용할 수 있도록 합니다. 전달된 command
를 사용하여 해야 할 작업을 결정합니다. 단순한 렌더링 이상의 작업을 상상할 수도 있습니다. 템플릿을 만들거나 어떤 방식으로든 관리하나요?) context
는 렌더링을 위해 템플릿에 직접 전달됩니다. 렌더링된 HTML은 확장 프로그램 페이지로 다시 전달되므로 확장 프로그램에서 나중에 유용한 작업을 할 수 있습니다.
<script>
const templatesElements = document.querySelectorAll(
"script[type='text/x-handlebars-template']"
);
let templates = {},
source,
name;
// precompile all templates in this page
for (let i = 0; i < templatesElements.length; i++) {
source = templatesElements[i].innerHTML;
name = templatesElements[i].id;
templates[name] = Handlebars.compile(source);
}
// Set up message event handler:
window.addEventListener('message', function (event) {
const command = event.data.command;
const template = templates[event.data.templateName];
let result = 'invalid request';
// if we don't know the templateName requested, return an error message
if (template) {
switch (command) {
case 'render':
result = template(event.data.context);
break;
// you could even do dynamic compilation, by accepting a command
// to compile a new template instead of using static ones, for example:
// case 'new':
// template = Handlebars.compile(event.data.templateSource);
// result = template(event.data.context);
// break;
}
} else {
result = 'Unknown template: ' + event.data.templateName;
}
event.source.postMessage({ result: result }, event.origin);
});
</script>
확장 프로그램 페이지로 돌아가면 이 메시지를 받게 되고 전달된 html
데이터로 흥미로운 작업을 수행할 수 있습니다. 이 경우에는 알림을 통해 에코를 표시하지만 이 HTML을 확장 프로그램 UI의 일부로 안전하게 사용할 수 있습니다. 샌드박스 내에서 렌더링된 콘텐츠를 신뢰하므로 innerHTML
를 통해 삽입해도 심각한 보안 위험이 없습니다.
이 메커니즘으로 템플릿을 쉽게 만들 수 있지만 물론 템플릿만으로 제한되지는 않습니다. 엄격한 콘텐츠 보안 정책에 따라 즉시 작동하지 않는 코드는 샌드박스 처리될 수 있습니다. 실제로 올바르게 실행되는 데 필요한 최소한의 권한으로 프로그램의 각 부분을 제한하기 위해 올바르게 실행되는 확장 프로그램의 샌드박스 구성요소를 사용하는 것이 유용한 경우가 많습니다. Google I/O 2012의 보안 웹 앱 및 Chrome 확장 프로그램 작성 프레젠테이션은 이러한 기술이 실제로 작동하는 예를 보여주며, 56분만 시간을 할애해 주시기 바랍니다.