Browser Extension Pentesting Methodology

Support HackTricks

Basic Information

브라우저 확장 프로그램은 JavaScript로 작성되며 브라우저에 의해 백그라운드에서 로드됩니다. 각 확장 프로그램은 DOM을 가지고 있지만 다른 사이트의 DOM과 상호작용할 수 있습니다. 이는 다른 사이트의 기밀성, 무결성 및 가용성(CIA)을 위협할 수 있음을 의미합니다.

Main Components

확장 프로그램 레이아웃은 시각화할 때 가장 잘 보이며 세 가지 구성 요소로 구성됩니다. 각 구성 요소를 자세히 살펴보겠습니다.

Content Scripts

각 콘텐츠 스크립트는 단일 웹 페이지의 DOM에 직접 접근할 수 있으며, 따라서 잠재적으로 악의적인 입력에 노출됩니다. 그러나 콘텐츠 스크립트는 확장 프로그램 코어에 메시지를 전송할 수 있는 능력 외에는 권한이 없습니다.

Extension Core

확장 프로그램 코어는 대부분의 확장 프로그램 권한/접근을 포함하지만, 확장 프로그램 코어는 XMLHttpRequest 및 콘텐츠 스크립트를 통해서만 웹 콘텐츠와 상호작용할 수 있습니다. 또한, 확장 프로그램 코어는 호스트 머신에 직접 접근할 수 없습니다.

Native Binary

확장 프로그램은 사용자의 전체 권한으로 호스트 머신에 접근할 수 있는 네이티브 바이너리를 허용합니다. 네이티브 바이너리는 Flash 및 기타 브라우저 플러그인에서 사용되는 표준 Netscape Plugin Application Programming Interface (NPAPI)를 통해 확장 프로그램 코어와 상호작용합니다.

Boundaries

사용자의 전체 권한을 얻으려면 공격자는 확장 프로그램이 콘텐츠 스크립트에서 확장 프로그램의 코어로, 그리고 확장 프로그램의 코어에서 네이티브 바이너리로 악의적인 입력을 전달하도록 설득해야 합니다.

확장 프로그램의 각 구성 요소는 강력한 보호 경계로 서로 분리되어 있습니다. 각 구성 요소는 별도의 운영 체제 프로세스에서 실행됩니다. 콘텐츠 스크립트와 확장 프로그램 코어는 대부분의 운영 체제 서비스에서 사용할 수 없는 샌드박스 프로세스에서 실행됩니다.

또한, 콘텐츠 스크립트는 별도의 JavaScript 힙에서 실행되어 관련 웹 페이지와 분리됩니다. 콘텐츠 스크립트와 웹 페이지는 같은 기본 DOM에 접근할 수 있지만, 두 개는 JavaScript 포인터를 교환하지 않아 JavaScript 기능의 유출을 방지합니다.

manifest.json

Chrome 확장 프로그램은 단순히 .crx 파일 확장자를 가진 ZIP 폴더입니다. 확장 프로그램의 코어는 폴더의 루트에 있는 manifest.json 파일로, 레이아웃, 권한 및 기타 구성 옵션을 지정합니다.

Example:

{
"manifest_version": 2,
"name": "My extension",
"version": "1.0",
"permissions": [
"storage"
],
"content_scripts": [
{
"js": [
"script.js"
],
"matches": [
"https://example.com/*",
"https://www.example.com/*"
],
"exclude_matches": ["*://*/*business*"],
}
],
"background": {
"scripts": [
"background.js"
]
},
"options_ui": {
"page": "options.html"
}
}

content_scripts

Content scripts는 사용자가 일치하는 페이지로 이동할 때마다 로드됩니다. 이 경우 https://example.com/* 표현과 일치하는 모든 페이지이지만 *://*/*/business* 정규 표현식과는 일치하지 않습니다. 이들은 페이지의 자체 스크립트처럼 실행되며 페이지의 Document Object Model (DOM)에 임의로 접근할 수 있습니다.

"content_scripts": [
{
"js": [
"script.js"
],
"matches": [
"https://example.com/*",
"https://www.example.com/*"
],
"exclude_matches": ["*://*/*business*"],
}
],

더 많은 URL을 포함하거나 제외하기 위해 include_globs 및 **exclude_globs**를 사용할 수도 있습니다.

이것은 저장소 API를 사용하여 확장 프로그램의 저장소에서 message 값을 검색할 때 페이지에 설명 버튼을 추가하는 예제 콘텐츠 스크립트입니다.

chrome.storage.local.get("message", result =>
{
let div = document.createElement("div");
div.innerHTML = result.message + " <button>Explain</button>";
div.querySelector("button").addEventListener("click", () =>
{
chrome.runtime.sendMessage("explain");
});
document.body.appendChild(div);
});

이 버튼이 클릭되면 콘텐츠 스크립트에 의해 확장 페이지로 메시지가 전송됩니다. 이는 runtime.sendMessage() API를 활용하여 이루어집니다. 콘텐츠 스크립트는 API에 직접 접근하는 데 제한이 있으며, storage가 몇 가지 예외 중 하나입니다. 이러한 예외를 넘어서는 기능을 위해서는 콘텐츠 스크립트가 통신할 수 있는 확장 페이지로 메시지가 전송됩니다.

브라우저에 따라 콘텐츠 스크립트의 기능이 약간 다를 수 있습니다. Chromium 기반 브라우저의 경우 기능 목록은 Chrome Developers documentation에서 확인할 수 있으며, Firefox의 경우 MDN이 주요 출처입니다. 또한 콘텐츠 스크립트는 백그라운드 스크립트와 통신할 수 있는 능력이 있어, 이를 통해 작업을 수행하고 응답을 전달할 수 있다는 점도 주목할 만합니다.

Chrome에서 콘텐츠 스크립트를 보기 및 디버깅하려면 Chrome 개발자 도구 메뉴에 접근할 수 있습니다: 옵션 > 추가 도구 > 개발자 도구 또는 Ctrl + Shift + I를 눌러서 접근할 수 있습니다.

개발자 도구가 표시되면 소스 탭을 클릭한 후 콘텐츠 스크립트 탭을 클릭합니다. 이를 통해 다양한 확장에서 실행 중인 콘텐츠 스크립트를 관찰하고 실행 흐름을 추적하기 위해 중단점을 설정할 수 있습니다.

주입된 콘텐츠 스크립트

콘텐츠 스크립트는 필수적이지 않으며, 동적으로 주입하거나 **tabs.executeScript**를 통해 웹 페이지에 프로그래밍 방식으로 주입할 수도 있습니다. 이는 실제로 더 세밀한 제어를 제공합니다.

콘텐츠 스크립트를 프로그래밍 방식으로 주입하기 위해서는 확장이 스크립트를 주입할 페이지에 대한 호스트 권한을 가져야 합니다. 이러한 권한은 확장의 매니페스트 내에서 요청하거나 activeTab을 통해 임시로 확보할 수 있습니다.

예시 activeTab 기반 확장

manifest.json
{
"name": "My extension",
...
"permissions": [
"activeTab",
"scripting"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_title": "Action Button"
}
}
  • 클릭 시 JS 파일 주입:

// content-script.js
document.body.style.backgroundColor = "orange";

//service-worker.js - Inject the JS file
chrome.action.onClicked.addListener((tab) => {
chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ["content-script.js"]
});
});
  • 클릭 시 함수 주입:

//service-worker.js - Inject a function
function injectedFunction() {
document.body.style.backgroundColor = "orange";
}

chrome.action.onClicked.addListener((tab) => {
chrome.scripting.executeScript({
target : {tabId : tab.id},
func : injectedFunction,
});
});

스크립팅 권한이 있는 예제

// service-workser.js
chrome.scripting.registerContentScripts([{
id : "test",
matches : [ "https://*.example.com/*" ],
excludeMatches : [ "*://*/*business*" ],
js : [ "contentScript.js" ],
}]);

// Another example
chrome.tabs.executeScript(tabId, { file: "content_script.js" });

더 많은 URL을 포함하거나 제외하려면 include_globs 및 **exclude_globs**를 사용할 수도 있습니다.

콘텐츠 스크립트 run_at

run_at 필드는 JavaScript 파일이 웹 페이지에 주입되는 시점을 제어합니다. 선호되는 기본 값은 "document_idle"입니다.

가능한 값은 다음과 같습니다:

  • document_idle: 가능한 경우 언제든지

  • document_start: css의 파일이 로드된 후, 그러나 다른 DOM이 구성되거나 다른 스크립트가 실행되기 전에.

  • document_end: DOM이 완료된 직후, 그러나 이미지 및 프레임과 같은 하위 리소스가 로드되기 전에.

manifest.json을 통해

{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.example.com/*"],
"run_at": "document_idle",
"js": ["contentScript.js"]
}
],
...
}

**service-worker.js**를 통해

chrome.scripting.registerContentScripts([{
id : "test",
matches : [ "https://*.example.com/*" ],
runAt : "document_idle",
js : [ "contentScript.js" ],
}]);

background

내용 스크립트가 전송한 메시지는 배경 페이지에서 수신되며, 이는 확장 구성 요소를 조정하는 중앙 역할을 합니다. 특히, 배경 페이지는 확장의 수명 동안 지속되며, 사용자와의 직접적인 상호작용 없이 조용히 작동합니다. 자체 문서 객체 모델(DOM)을 가지고 있어 복잡한 상호작용 및 상태 관리를 가능하게 합니다.

주요 사항:

  • 배경 페이지 역할: 확장의 신경 센터 역할을 하여 다양한 부분 간의 통신 및 조정을 보장합니다.

  • 지속성: 사용자에게는 보이지 않지만 확장의 기능에 필수적인 항상 존재하는 개체입니다.

  • 자동 생성: 명시적으로 정의되지 않은 경우, 브라우저는 자동으로 배경 페이지를 생성합니다. 이 자동 생성된 페이지는 확장 매니페스트에 지정된 모든 배경 스크립트를 포함하여 확장의 배경 작업이 원활하게 작동하도록 보장합니다.

명시적으로 선언되지 않았을 때 브라우저가 배경 페이지를 자동으로 생성하는 편리함은 모든 필요한 배경 스크립트가 통합되고 작동하도록 보장하여 확장 설정 프로세스를 간소화합니다.

Example background script:

chrome.runtime.onMessage.addListener((request, sender, sendResponse) =>
{
if (request == "explain")
{
chrome.tabs.create({ url: "https://example.net/explanation" });
}
})

It uses runtime.onMessage API to listen to messages. When an "explain" message is received, it uses tabs API to open a page in a new tab.

To debug the background script you could go to the extension details and inspect the service worker, this will open the developer tools with the background script:

옵션 페이지 및 기타

Browser extensions can contain various kinds of pages:

  • 액션 페이지확장 아이콘을 클릭할 때 드롭다운에 표시됩니다.

  • 확장이 새 탭에서 로드할 페이지.

  • 옵션 페이지: 이 페이지는 클릭할 때 확장 위에 표시됩니다. 이전 매니페스트에서 제 경우에는 chrome://extensions/?options=fadlhnelkbeojnebcbkacjilhnbjfjca에서 이 페이지에 접근할 수 있었거나 클릭하여:

Note that these pages aren't persistent like background pages as they load dynamically content on necessity. Despite this, they share certain capabilities with the background page:

  • Content Scripts와의 통신: 배경 페이지와 유사하게, 이러한 페이지는 콘텐츠 스크립트로부터 메시지를 받을 수 있어 확장 내 상호작용을 촉진합니다.

  • 확장 전용 API에 대한 접근: 이러한 페이지는 확장에 대해 정의된 권한에 따라 확장 전용 API에 대한 포괄적인 접근을 누립니다.

permissions & host_permissions

**permissions**와 **host_permissions**는 manifest.json의 항목으로, 어떤 권한(저장소, 위치 등)을 브라우저 확장이 가지고 있는지와 어떤 웹 페이지에서 이를 사용할 수 있는지를 나타냅니다.

브라우저 확장이 매우 특권적일 수 있기 때문에, 악의적인 확장이나 손상된 확장은 공격자가 민감한 정보를 훔치고 사용자에 대해 스파이할 수 있는 다양한 수단을 허용할 수 있습니다.

이 설정이 어떻게 작동하는지 및 어떻게 남용될 수 있는지 확인하십시오:

BrowExt - permissions & host_permissions

content_security_policy

콘텐츠 보안 정책manifest.json 내에서도 선언할 수 있습니다. 정의된 것이 있다면, 취약할 수 있습니다.

브라우저 확장 페이지의 기본 설정은 다소 제한적입니다:

script-src 'self'; object-src 'self';

CSP 및 잠재적인 우회에 대한 자세한 내용은 다음을 확인하세요:

Content Security Policy (CSP) Bypass

web_accessible_resources

웹 페이지가 브라우저 확장의 페이지에 접근하기 위해서는, 예를 들어 .html 페이지, 이 페이지는 manifest.jsonweb_accessible_resources 필드에 언급되어야 합니다. 예를 들어:

{
...
"web_accessible_resources": [
{
"resources": [ "images/*.png" ],
"matches": [ "https://example.com/*" ]
},
{
"resources": [ "fonts/*.woff" ],
"matches": [ "https://example.com/*" ]
}
],
...
}

이 페이지는 다음과 같은 URL에서 접근할 수 있습니다:

chrome-extension://<extension-id>/message.html

공개 확장 프로그램에서는 extension-id에 접근할 수 있습니다:

하지만, manifest.json 매개변수 **use_dynamic_url**이 사용되면, 이 id는 동적일 수 있습니다.

여기에서 페이지가 언급되더라도, Content Security Policy 덕분에 ClickJacking에 대해 보호될 수 있습니다. 따라서 ClickJacking 공격이 가능한지 확인하기 전에 이를 (frame-ancestors 섹션) 확인해야 합니다.

이러한 페이지에 접근할 수 있는 것은 이 페이지들이 잠재적으로 ClickJacking에 취약할 수 있음을 의미합니다:

BrowExt - ClickJacking

이러한 페이지가 확장 프로그램에 의해서만 로드되고 무작위 URL에 의해 로드되지 않도록 허용하면 ClickJacking 공격을 방지할 수 있습니다.

**web_accessible_resources**의 페이지와 확장의 다른 페이지들도 백그라운드 스크립트에 연락할 수 있습니다. 따라서 이러한 페이지 중 하나가 XSS에 취약하다면 더 큰 취약점을 열 수 있습니다.

또한, **web_accessible_resources**에 표시된 페이지만 iframe 내에서 열 수 있지만, 새 탭에서 확장 ID를 알고 있으면 확장의 모든 페이지에 접근할 수 있습니다. 따라서 동일한 매개변수를 악용하는 XSS가 발견되면, 페이지가 **web_accessible_resources**에 구성되어 있지 않더라도 악용될 수 있습니다.

externally_connectable

문서에 따르면, "externally_connectable" 매니페스트 속성은 어떤 확장 프로그램과 웹 페이지가 runtime.connectruntime.sendMessage를 통해 귀하의 확장 프로그램에 연결할 수 있는지를 선언합니다.

  • externally_connectable 키가 귀하의 확장 프로그램의 매니페스트에 선언되지 않았거나 **"ids": ["*"]**로 선언된 경우, 모든 확장 프로그램이 연결할 수 있지만, 웹 페이지는 연결할 수 없습니다.

  • 특정 ID가 지정된 경우, 예를 들어 "ids": ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]와 같이, 오직 해당 애플리케이션만 연결할 수 있습니다.

  • matches가 지정된 경우, 해당 웹 앱은 연결할 수 있습니다:

"matches": [
"https://*.google.com/*",
"*://*.chromium.org/*",
  • 비어 있는 것으로 지정된 경우: "externally_connectable": {}, 어떤 앱이나 웹도 연결할 수 없습니다.

여기에서 적은 확장 및 URL이 표시될수록 공격 표면이 작아집니다.

웹 페이지가 XSS 또는 탈취에 취약한 경우 **externally_connectable**에 표시되면, 공격자는 배경 스크립트에 직접 메시지를 보낼 수 있어, Content Script와 그 CSP를 완전히 우회할 수 있습니다.

따라서, 이는 매우 강력한 우회입니다.

게다가, 클라이언트가 악성 확장을 설치하면, 취약한 확장과 통신할 수 없더라도 허용된 웹 페이지에 XSS 데이터를 주입하거나 WebRequest 또는 DeclarativeNetRequest API를 악용하여 특정 도메인에서 요청을 조작하고 JavaScript 파일에 대한 페이지 요청을 변경할 수 있습니다. (대상 페이지의 CSP가 이러한 공격을 방지할 수 있습니다). 이 아이디어는 이 글에서 나왔습니다.

통신 요약

확장 <--> 웹앱

콘텐츠 스크립트와 웹 페이지 간의 통신에는 일반적으로 포스트 메시지가 사용됩니다. 따라서 웹 애플리케이션에서는 일반적으로 window.postMessage 함수 호출을 찾을 수 있으며, 콘텐츠 스크립트에서는 **window.addEventListener**와 같은 리스너를 찾을 수 있습니다. 그러나 확장이 Post Message를 보내 웹 애플리케이션과 통신할 수도 있으며 (따라서 웹은 이를 예상해야 함) 웹이 새 스크립트를 로드하도록 만들 수도 있습니다.

확장 내부

일반적으로 chrome.runtime.sendMessage 함수가 확장 내에서 메시지를 보내는 데 사용되며 (일반적으로 background 스크립트에서 처리됨), 이를 수신하고 처리하기 위해 **chrome.runtime.onMessage.addListener**를 호출하는 리스너가 선언됩니다.

단일 메시지를 보내는 대신 지속적인 연결을 위해 **chrome.runtime.connect()**를 사용할 수도 있으며, 다음 예제와 같이 메시지를 보내고 받는 데 사용할 수 있습니다:

chrome.runtime.connect() 예제

```javascript var port = chrome.runtime.connect();

// Listen for messages from the web page window.addEventListener("message", (event) => { // Only accept messages from the same window if (event.source !== window) { return; }

// Check if the message type is "FROM_PAGE" if (event.data.type && (event.data.type === "FROM_PAGE")) { console.log("Content script received: " + event.data.text); // Forward the message to the background script port.postMessage({ type: 'FROM_PAGE', text: event.data.text }); } }, false);

// Listen for messages from the background script port.onMessage.addListener(function(msg) { console.log("Content script received message from background script:", msg); // Handle the response message from the background script });

</details>

특정 탭에 위치한 콘텐츠 스크립트로 백그라운드 스크립트에서 메시지를 보내는 것도 가능합니다. 이때 **`chrome.tabs.sendMessage`**를 호출하며, 메시지를 보낼 **탭의 ID**를 지정해야 합니다.

### 허용된 `externally_connectable`에서 확장으로

`externally_connectable` 구성에서 허용된 **웹 앱 및 외부 브라우저 확장**은 다음을 사용하여 요청을 보낼 수 있습니다:
```javascript
chrome.runtime.sendMessage(extensionId, ...

어디에서 확장 ID를 언급해야 하는지.

네이티브 메시징

백그라운드 스크립트가 시스템 내의 바이너리와 통신할 수 있으며, 이 통신이 적절하게 보호되지 않으면 RCE와 같은 치명적인 취약점에 노출될 수 있습니다. 자세한 내용은 나중에.

chrome.runtime.sendNativeMessage(
'com.my_company.my_application',
{text: 'Hello'},
function (response) {
console.log('Received ' + response);
}
);

Web ↔︎ Content Script Communication

콘텐츠 스크립트가 작동하는 환경과 호스트 페이지가 존재하는 환경은 분리되어 있어 격리를 보장합니다. 이러한 격리에도 불구하고 두 환경 모두 페이지의 **문서 객체 모델(DOM)**과 상호작용할 수 있는 능력을 가지고 있습니다. 호스트 페이지가 콘텐츠 스크립트와 통신하거나 콘텐츠 스크립트를 통해 확장과 간접적으로 통신하기 위해서는 두 당사자가 접근할 수 있는 DOM을 통신 채널로 활용해야 합니다.

Post Messages

content-script.js
// This is like "chrome.runtime.sendMessage" but to maintain the connection
var port = chrome.runtime.connect();

window.addEventListener("message", (event) => {
// We only accept messages from ourselves
if (event.source !== window) {
return;
}

if (event.data.type && (event.data.type === "FROM_PAGE")) {
console.log("Content script received: " + event.data.text);
// Forward the message to the background script
port.postMessage(event.data.text);
}
}, false);
example.js
document.getElementById("theButton").addEventListener("click", () => {
window.postMessage(
{type : "FROM_PAGE", text : "Hello from the webpage!"}, "*");
}, false);

안전한 Post Message 통신은 수신된 메시지의 진위를 확인해야 하며, 이는 다음을 확인하여 수행할 수 있습니다:

  • event.isTrusted: 이 값은 이벤트가 사용자 행동에 의해 트리거된 경우에만 True입니다.

  • 콘텐츠 스크립트는 사용자가 특정 행동을 수행할 때만 메시지를 기대할 수 있습니다.

  • origin domain: 메시지를 기대할 수 있는 도메인의 허용 목록만 허용합니다.

  • 정규 표현식을 사용하는 경우, 매우 주의해야 합니다.

  • Source: received_message.source !== window를 사용하여 메시지가 콘텐츠 스크립트가 수신 대기 중인 동일한 창에서 온 것인지 확인할 수 있습니다.

이전의 확인 사항이 수행되었더라도 취약할 수 있으므로 다음 페이지에서 잠재적인 Post Message 우회를 확인하십시오:

Iframe

또 다른 가능한 통신 방법은 Iframe URL을 통한 것입니다. 예시는 다음에서 찾을 수 있습니다:

DOM

이것은 "정확히" 통신 방법은 아니지만, 웹과 콘텐츠 스크립트는 웹 DOM에 접근할 수 있습니다. 따라서 콘텐츠 스크립트가 그로부터 정보를 읽고 웹 DOM을 신뢰하는 경우, 웹은 이 데이터를 수정할 수 있습니다 (웹을 신뢰해서는 안 되거나, 웹이 XSS에 취약하기 때문에) 그리고 콘텐츠 스크립트를 손상시킬 수 있습니다.

브라우저 확장을 손상시키기 위한 DOM 기반 XSS의 예시는 다음에서 찾을 수 있습니다:

콘텐츠 스크립트 ↔︎ 백그라운드 스크립트 통신

콘텐츠 스크립트는 runtime.sendMessage() 또는 tabs.sendMessage() 함수를 사용하여 일회성 JSON 직렬화 가능 메시지를 보낼 수 있습니다.

응답을 처리하려면 반환된 Promise를 사용하십시오. 그러나 이전 호환성을 위해 마지막 인수로 콜백을 여전히 전달할 수 있습니다.

콘텐츠 스크립트에서 요청을 보내는 모습은 다음과 같습니다:

(async () => {
const response = await chrome.runtime.sendMessage({greeting: "hello"});
// do something with response here, not outside the function
console.log(response);
})();

확장에서 요청 보내기 (보통 백그라운드 스크립트). 선택한 탭의 콘텐츠 스크립트에 메시지를 보내는 방법의 예:

// From https://stackoverflow.com/questions/36153999/how-to-send-a-message-between-chrome-extension-popup-and-content-script
(async () => {
const [tab] = await chrome.tabs.query({active: true, lastFocusedWindow: true});
const response = await chrome.tabs.sendMessage(tab.id, {greeting: "hello"});
// do something with response here, not outside the function
console.log(response);
})();

On the receiving end, you need to set up an runtime.onMessage event listener to handle the message. This looks the same from a content script or extension page.

// From https://stackoverflow.com/questions/70406787/javascript-send-message-from-content-js-to-background-js
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log(sender.tab ?
"from a content script:" + sender.tab.url :
"from the extension");
if (request.greeting === "hello")
sendResponse({farewell: "goodbye"});
}
);

예제에서 강조된 바와 같이, **sendResponse()**는 동기 방식으로 실행되었습니다. sendResponse()의 비동기 실행을 위해 onMessage 이벤트 핸들러를 수정하려면 return true;를 포함하는 것이 필수적입니다.

중요한 고려 사항은 여러 페이지가 onMessage 이벤트를 수신하도록 설정된 시나리오에서 특정 이벤트에 대해 sendResponse()를 실행하는 첫 번째 페이지만이 응답을 효과적으로 전달할 수 있다는 것입니다. 동일한 이벤트에 대한 후속 응답은 고려되지 않습니다.

새로운 확장을 만들 때는 콜백보다 프로미스를 선호해야 합니다. 콜백 사용과 관련하여, sendResponse() 함수는 동기 컨텍스트 내에서 직접 실행되거나 이벤트 핸들러가 true를 반환하여 비동기 작업을 나타내는 경우에만 유효하다고 간주됩니다. 만약 어떤 핸들러도 true를 반환하지 않거나 sendResponse() 함수가 메모리에서 제거되면(가비지 수집됨), sendMessage() 함수와 연결된 콜백이 기본적으로 트리거됩니다.

네이티브 메시징

브라우저 확장은 stdin을 통해 시스템의 바이너리와 통신할 수 있습니다. 애플리케이션은 이를 나타내는 json을 설치해야 하며, json은 다음과 같습니다:

{
"name": "com.my_company.my_application",
"description": "My Application",
"path": "C:\\Program Files\\My Application\\chrome_native_messaging_host.exe",
"type": "stdio",
"allowed_origins": ["chrome-extension://knldjmfmopnpolahpmmgbagdohdnhkik/"]
}

name은 브라우저 확장의 백그라운드 스크립트에서 애플리케이션과 통신하기 위해 runtime.connectNative() 또는 runtime.sendNativeMessage()에 전달되는 문자열입니다. path는 바이너리의 경로이며, 유효한 type은 stdio(표준 입력 및 표준 출력 사용) 하나뿐이고, allowed_origins는 이를 접근할 수 있는 확장을 나타냅니다(와일드카드를 사용할 수 없습니다).

Chrome/Chromium은 이 json을 일부 Windows 레지스트리와 macOS 및 Linux의 일부 경로에서 검색합니다(자세한 정보는 docs에서 확인할 수 있습니다).

브라우저 확장은 이 통신을 사용하기 위해 nativeMessaing 권한을 선언해야 합니다.

이것은 네이티브 애플리케이션에 메시지를 보내는 일부 백그라운드 스크립트 코드의 모습입니다:

chrome.runtime.sendNativeMessage(
'com.my_company.my_application',
{text: 'Hello'},
function (response) {
console.log('Received ' + response);
}
);

In 이 블로그 포스트에서는 네이티브 메시지를 악용하는 취약한 패턴이 제안됩니다:

  1. 브라우저 확장 프로그램은 콘텐츠 스크립트에 대한 와일드카드 패턴을 가지고 있습니다.

  2. 콘텐츠 스크립트는 sendMessage를 사용하여 백그라운드 스크립트에 postMessage 메시지를 전달합니다.

  3. 백그라운드 스크립트는 sendNativeMessage를 사용하여 네이티브 애플리케이션에 메시지를 전달합니다.

  4. 네이티브 애플리케이션은 메시지를 위험하게 처리하여 코드 실행으로 이어집니다.

그리고 그 안에서 브라우저 확장을 악용하여 어떤 페이지에서든 RCE로 가는 예시가 설명됩니다.

메모리/코드/클립보드의 민감한 정보

브라우저 확장 프로그램이 민감한 정보를 메모리 안에 저장하는 경우, 이는 덤프될 수 있으며 (특히 Windows 기기에서) 이 정보를 검색할 수 있습니다.

따라서 브라우저 확장 프로그램의 메모리는 안전하다고 간주되어서는 안 되며, 자격 증명이나 니모닉 구문과 같은 민감한 정보는 저장되어서는 안 됩니다.

물론, 코드에 민감한 정보를 넣지 마십시오, 왜냐하면 이는 공개될 것이기 때문입니다.

브라우저에서 메모리를 덤프하려면 프로세스 메모리를 덤프하거나 브라우저 확장의 설정으로 가서 **Inspect pop-up**을 클릭한 후 Memory 섹션에서 **Take a snapshot**을 클릭하고 **CTRL+F**를 사용하여 스냅샷 내에서 민감한 정보를 검색할 수 있습니다.

게다가, 니모닉 키나 비밀번호와 같은 매우 민감한 정보는 클립보드에 복사되는 것을 허용해서는 안 됩니다 (또는 최소한 몇 초 후에 클립보드에서 제거해야 합니다) 왜냐하면 그러면 클립보드를 모니터링하는 프로세스가 이를 얻을 수 있기 때문입니다.

브라우저에 확장 프로그램 로드하기

  1. 브라우저 확장 프로그램을 다운로드하고 압축을 풉니다.

  2. **chrome://extensions/**로 가서 개발자 모드활성화합니다.

  3. Load unpacked 버튼을 클릭합니다.

Firefox에서는 **about:debugging#/runtime/this-firefox**로 가서 Load Temporary Add-on 버튼을 클릭합니다.

스토어에서 소스 코드 가져오기

Chrome 확장의 소스 코드는 다양한 방법으로 얻을 수 있습니다. 아래는 각 옵션에 대한 자세한 설명과 지침입니다.

명령줄을 통해 ZIP으로 확장 프로그램 다운로드

Chrome 확장의 소스 코드는 명령줄을 사용하여 ZIP 파일로 다운로드할 수 있습니다. 이는 curl을 사용하여 특정 URL에서 ZIP 파일을 가져오고, ZIP 파일의 내용을 디렉토리에 추출하는 과정을 포함합니다. 단계는 다음과 같습니다:

  1. "extension_id"를 확장의 실제 ID로 교체합니다.

  2. 다음 명령을 실행합니다:

extension_id=your_extension_id   # Replace with the actual extension ID
curl -L -o "$extension_id.zip" "https://clients2.google.com/service/update2/crx?response=redirect&os=mac&arch=x86-64&nacl_arch=x86-64&prod=chromecrx&prodchannel=stable&prodversion=44.0.2403.130&x=id%3D$extension_id%26uc"
unzip -d "$extension_id-source" "$extension_id.zip"

CRX 뷰어 웹사이트 사용

https://robwu.nl/crxviewer/

CRX 뷰어 확장 프로그램 사용

또 다른 편리한 방법은 오픈 소스 프로젝트인 Chrome Extension Source Viewer를 사용하는 것입니다. Chrome 웹 스토어에서 설치할 수 있습니다. 뷰어의 소스 코드는 GitHub 저장소에서 확인할 수 있습니다.

로컬에 설치된 확장 프로그램의 소스 보기

로컬에 설치된 Chrome 확장 프로그램도 검사할 수 있습니다. 방법은 다음과 같습니다:

  1. chrome://version/를 방문하여 "Profile Path" 필드를 찾습니다.

  2. 프로필 디렉토리 내의 Extensions/ 하위 폴더로 이동합니다.

  3. 이 폴더에는 모든 설치된 확장 프로그램이 포함되어 있으며, 일반적으로 읽기 가능한 형식의 소스 코드가 있습니다.

확장 프로그램을 식별하려면 ID를 이름에 매핑할 수 있습니다:

  • about:extensions 페이지에서 개발자 모드를 활성화하여 각 확장 프로그램의 ID를 확인합니다.

  • 각 확장 프로그램의 폴더 내에서 manifest.json 파일에는 읽기 가능한 name 필드가 포함되어 있어 확장 프로그램을 식별하는 데 도움이 됩니다.

파일 압축 해제기 또는 언팩커 사용

Chrome 웹 스토어로 가서 확장 프로그램을 다운로드합니다. 파일은 .crx 확장자를 가집니다. 파일 확장자를 .crx에서 .zip으로 변경합니다. WinRAR, 7-Zip 등과 같은 파일 압축 해제기를 사용하여 ZIP 파일의 내용을 추출합니다.

Chrome에서 개발자 모드 사용

Chrome을 열고 chrome://extensions/로 이동합니다. 오른쪽 상단에서 "개발자 모드"를 활성화합니다. "압축 해제된 확장 프로그램 로드..."를 클릭합니다. 확장 프로그램의 디렉토리로 이동합니다. 이는 소스 코드를 다운로드하지 않지만, 이미 다운로드되었거나 개발된 확장 프로그램의 코드를 보기 및 수정하는 데 유용합니다.

Chrome 확장 프로그램 매니페스트 데이터셋

취약한 브라우저 확장 프로그램을 찾기 위해 https://github.com/palant/chrome-extension-manifests-dataset를 사용하고 매니페스트 파일에서 잠재적으로 취약한 징후를 확인할 수 있습니다. 예를 들어, 25000명 이상의 사용자가 있는 확장 프로그램, content_scripts 및 권한 nativeMessaging을 확인하려면:

# Query example from https://spaceraccoon.dev/universal-code-execution-browser-extensions/
node query.js -f "metadata.user_count > 250000" "manifest.content_scripts?.length > 0 && manifest.permissions?.includes('nativeMessaging')"

보안 감사 체크리스트

브라우저 확장 프로그램은 제한된 공격 표면을 가지고 있지만, 일부는 취약점이나 강화 개선 사항을 포함할 수 있습니다. 다음은 가장 일반적인 항목입니다:

도구

  • 제공된 Chrome 웹스토어 링크에서 Chrome 확장 프로그램을 가져옵니다.

  • manifest.json 뷰어: 확장 프로그램의 매니페스트를 JSON 형식으로 표시합니다.

  • 지문 분석: web_accessible_resources 감지 및 Chrome 확장 프로그램 지문 생성을 자동으로 수행합니다.

  • 잠재적 Clickjacking 분석: web_accessible_resources 지시어가 설정된 확장 프로그램 HTML 페이지 감지. 페이지의 목적에 따라 Clickjacking에 취약할 수 있습니다.

  • 권한 경고 뷰어: 사용자가 확장 프로그램을 설치하려고 할 때 표시될 Chrome 권한 프롬프트 경고 목록을 보여줍니다.

  • 위험한 함수: 공격자가 악용할 수 있는 위험한 함수의 위치를 보여줍니다 (예: innerHTML, chrome.tabs.executeScript와 같은 함수).

  • 진입점: 확장 프로그램이 사용자/외부 입력을 받는 위치를 보여줍니다. 이는 확장 프로그램의 표면적을 이해하고 악의적으로 조작된 데이터를 확장 프로그램에 보낼 수 있는 잠재적 지점을 찾는 데 유용합니다.

  • 위험한 함수 및 진입점 스캐너는 생성된 경고에 대해 다음을 제공합니다:

  • 경고를 유발한 관련 코드 스니펫 및 줄.

  • 문제에 대한 설명.

  • 코드를 포함하는 전체 소스 파일을 보기 위한 “파일 보기” 버튼.

  • 경고된 파일의 경로.

  • 경고된 파일의 전체 Chrome 확장 프로그램 URI.

  • 파일의 유형 (예: 백그라운드 페이지 스크립트, 콘텐츠 스크립트, 브라우저 액션 등).

  • 취약한 줄이 JavaScript 파일에 있는 경우, 포함된 모든 페이지의 경로와 이 페이지의 유형 및 web_accessible_resource 상태.

  • Content Security Policy (CSP) 분석기 및 우회 검사기: 확장 프로그램의 CSP의 약점을 지적하고 화이트리스트에 있는 CDN 등으로 인해 CSP를 우회할 수 있는 잠재적 방법을 밝혀냅니다.

  • 알려진 취약한 라이브러리: Retire.js를 사용하여 알려진 취약한 JavaScript 라이브러리의 사용 여부를 확인합니다.

  • 확장 프로그램 및 포맷된 버전 다운로드.

  • 원본 확장 프로그램 다운로드.

  • 확장 프로그램의 아름답게 포맷된 버전 다운로드 (자동으로 포맷된 HTML 및 JavaScript).

  • 스캔 결과의 자동 캐싱, 확장 프로그램 스캔을 처음 실행할 때는 시간이 걸리지만, 두 번째 실행 시 확장 프로그램이 업데이트되지 않았다면 거의 즉시 결과가 캐시됩니다.

  • 링크 가능한 보고서 URL, tarnish에 의해 생성된 확장 프로그램 보고서에 다른 사람을 쉽게 링크할 수 있습니다.

Neto 프로젝트는 Firefox 및 Chrome과 같은 잘 알려진 브라우저의 브라우저 플러그인 및 확장 프로그램의 숨겨진 기능을 분석하고 풀어내기 위해 고안된 Python 3 패키지입니다. manifest.json, 지역화 폴더 또는 JavaScript 및 HTML 소스 파일과 같은 관련 리소스에서 이러한 기능을 추출하기 위해 패키지 파일을 압축 해제하는 프로세스를 자동화합니다.

참고 문헌

Last updated