브라우저 확장 기능 Pentesting Methodology

Tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기

기본 정보

브라우저 확장 기능은 JavaScript로 작성되며 브라우저에서 백그라운드로 로드됩니다. 확장 기능은 자체 DOM을 가지지만 다른 사이트의 DOM과 상호작용할 수 있습니다. 이는 다른 사이트들의 기밀성(confidentiality), 무결성(integrity), 가용성(availability) (CIA)을 침해할 수 있음을 의미합니다.

주요 구성 요소

확장 구조는 시각화했을 때 가장 잘 이해되며 세 가지 구성 요소로 이루어져 있습니다. 각 구성 요소를 자세히 살펴봅시다.

http://webblaze.cs.berkeley.edu/papers/Extensions.pdf

콘텐츠 스크립트

각 콘텐츠 스크립트는 단일 웹 페이지의 DOM에 직접 접근할 수 있으므로 잠재적으로 악성인 입력에 노출됩니다. 그러나 콘텐츠 스크립트는 확장 코어에 메시지를 보내는 기능 외에는 권한을 가지지 않습니다.

확장 코어

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

네이티브 바이너리

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

Boundaries

Caution

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

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

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

manifest.json

A Chrome extension is just a ZIP folder with a .crx file extension. The extension’s core is the manifest.json file at the root of the folder, which specifies layout, permissions, and other configuration options.

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

콘텐츠 스크립트는 사용자가 일치하는 페이지로 이동할 때마다 로드됩니다. 우리 예에서는 https://example.com/* 표현과 일치하고 *://*/*/business* regex와 일치하지 않는 모든 페이지입니다. 이들은 페이지 자체의 스크립트처럼 실행되며 페이지의 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**를 사용할 수도 있습니다.

다음은 the storage API를 사용하여 확장 프로그램의 저장소에서 message 값을 가져올 때 페이지에 설명 버튼을 추가하는 예제 content script입니다.

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)
})

A message is sent to the extension pages by the content script when this button is clicked, through the utilization of the runtime.sendMessage() API. This is due to the content script’s limitation in direct access to APIs, with storage being among the few exceptions. For functionalities beyond these exceptions, messages are sent to extension pages which content scripts can communicate with.

Warning

Depending on the browser, the capabilities of the content script may vary slightly. For Chromium-based browsers, the capabilities list is available in the Chrome Developers documentation, and for Firefox, the MDN serves as the primary source.
It is also noteworthy that content scripts have the ability to communicate with background scripts, enabling them to perform actions and relay responses back.

For viewing and debugging content scripts in Chrome, the Chrome developer tools menu can be accessed from Options > More tools > Developer tools OR by pressing Ctrl + Shift + I.

Upon the developer tools being displayed, the Source tab is to be clicked, followed by the Content Scripts tab. This allows for the observation of running content scripts from various extensions and the setting of breakpoints to track the execution flow.

Injected content scripts

Tip

Note that Content Scripts aren’t mandatory as it’s also possible to dynamically inject scripts and to programatically inject them in web pages via tabs.executeScript. This actually provides more granular controls.

For the programmatic injection of a content script, the extension is required to have host permissions for the page into which the scripts are to be injected. These permissions may be secured either by requesting them within the manifest of the extension or on a temporary basis through activeTab.

activeTab 기반 확장 예시

{
"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**를 사용할 수도 있습니다.

Content Scripts 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

content scripts가 보낸 메시지는 백그라운드 페이지에서 수신되며, 확장 기능의 구성 요소들을 조정하는 중심 역할을 합니다. 특히 백그라운드 페이지는 확장의 수명 동안 지속되며, 사용자와 직접적인 상호작용 없이 은밀히 동작합니다. 자체 문서 객체 모델(DOM)을 가지고 있어 복잡한 상호작용과 상태 관리를 가능하게 합니다.

핵심 포인트:

  • 백그라운드 페이지 역할: 확장의 신경 중추로 작동하여 확장의 여러 부분 간 통신과 조정을 보장합니다.
  • 지속성: 사용자는 볼 수 없지만 확장 기능의 작동에 필수적인, 항상 존재하는 구성요소입니다.
  • 자동 생성: 명시적으로 정의하지 않으면 브라우저가 자동으로 백그라운드 페이지를 생성합니다. 이 자동 생성된 페이지에는 확장의 manifest에 지정된 모든 백그라운드 스크립트가 포함되어 확장의 백그라운드 작업이 원활히 작동하도록 합니다.

Tip

브라우저가 백그라운드 페이지를 자동으로 생성해 주는(명시적으로 선언되지 않았을 때) 편의성은 필요한 모든 백그라운드 스크립트가 통합되어 작동하도록 보장하여 확장 설정 과정을 간소화합니다.

예시 백그라운드 스크립트:

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.

백그라운드 스크립트를 디버그하려면 **extension details and inspect the service worker,**로 이동하면 백그라운드 스크립트가 열린 상태로 developer tools가 표시됩니다:

Options pages and other

Browser extensions can contain various kinds of pages:

  • Action pages are displayed in a drop-down when the extension icon is clicked.
  • 확장 프로그램이 load in a new tab 페이지를 엽니다.
  • Option Pages: 이 페이지는 클릭하면 확장 프로그램 위에 표시됩니다. 이전 manifest에서 제 경우에는 chrome://extensions/?options=fadlhnelkbeojnebcbkacjilhnbjfjca로 접근하거나 클릭하여 열 수 있었습니다:

이 페이지들은 필요에 따라 동적으로 콘텐츠를 로드하므로 백그라운드 페이지처럼 지속적으로 존재하지 않습니다. 그럼에도 불구하고 백그라운드 페이지와 몇 가지 기능을 공유합니다:

  • Communication with Content Scripts: 백그라운드 페이지와 마찬가지로, 이 페이지들은 content scripts로부터 메시지를 받을 수 있어 확장 내 상호작용을 용이하게 합니다.
  • Access to Extension-Specific APIs: 이 페이지들은 확장에 정의된 권한에 따라 확장 전용 API에 대한 광범위한 접근 권한을 가집니다.

permissions & host_permissions

permissions 및 **host_permissions**는 manifest.json의 항목으로, 브라우저 확장 프로그램이 어떤 권한(예: storage, location…)을 가지는지와 어떤 웹 페이지들에서 적용되는지를 나타냅니다.

브라우저 확장 프로그램은 매우 권한이 강력할 수 있으므로, 악의적이거나 침해된 확장은 공격자에게 민감한 정보를 탈취하고 사용자를 감시할 수 있는 다양한 수단을 제공할 수 있습니다.

이 설정들이 어떻게 동작하고 어떻게 악용될 수 있는지 확인하세요:

BrowExt - permissions & host_permissions

content_security_policy

manifest.json 내에서도 content security policy를 선언할 수 있습니다. 만약 정의되어 있다면, 이는 취약할 수 있습니다.

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

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

In public extensions the extension-id is accesible:

Although, if the manifest.json parameter use_dynamic_url is used, this id can be dynamic.

Tip

Note that even if a page is mentioned here, it might be protected against ClickJacking thanks to the Content Security Policy. So you also need to check it (frame-ancestors section) before confirming a ClickJacking attack is possible.

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

BrowExt - ClickJacking

Tip

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

Caution

Note that the pages from web_accessible_resources and other pages of the extension are also capable of contacting background scripts. So if one of these pages is vulnerable to XSS it could open a bigger vulnerability.

Moreover, note that you can only open pages indicated in web_accessible_resources inside iframes, but from a new tab it’s possible to access any page in the extension knowing the extension ID. Therefore, if an XSS is found abusing same parameters, it could be abused even if the page isn’t configured in web_accessible_resources.

externally_connectable

A per the docs, The "externally_connectable" manifest property declares which extensions and web pages can connect to your extension via runtime.connect and runtime.sendMessage.

  • If the externally_connectable key is not declared in your extension’s manifest or it’s declared as "ids": ["*"], all extensions can connect, but no web pages can connect.
  • If specific IDs are specified, like in "ids": ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"], only those applications can connect.
  • If matches are specified, those web apps will be able to connect:
"matches": [
"https://*.google.com/*",
"*://*.chromium.org/*",
  • 만약 비어있다고 지정되어 있다면: "externally_connectable": {}, 어떤 앱이나 웹도 연결할 수 없습니다.

여기에 명시된 확장과 URL이 적을수록 공격 표면은 작아집니다.

Caution

만약 웹 페이지가 vulnerable to XSS or takeover로 **externally_connectable**에 표시되어 있다면, 공격자는 send messages directly to the background script할 수 있어 Content Script와 그 CSP를 완전히 우회할 수 있습니다.

따라서, 이는 very powerful bypass입니다.

또한, 클라이언트가 rouge extension을 설치하면, 설령 그것이 취약한 extension과 통신할 수 없더라도, 허용된 웹 페이지에 XSS data in an allowed web page를 주입하거나 WebRequest 또는 DeclarativeNetRequest API를 악용해 대상 도메인의 요청을 조작하여 페이지의 JavaScript file 요청을 변경할 수 있습니다. (대상 페이지의 CSP가 이러한 공격을 방지할 수 있습니다). This idea comes from this writeup.

통신 요약

확장 <–> WebApp

content script와 웹 페이지 간 통신에는 보통 post messages가 사용됩니다. 따라서 웹 애플리케이션에서는 보통 함수 호출 **window.postMessage**를, content script 쪽에서는 window.addEventListener 같은 리스너를 확인할 수 있습니다. 다만, 확장이 웹 애플리케이션에 Post Message를 보내며 통신할 수도(따라서 웹은 이를 예상해야 함) 있고 단순히 웹이 새로운 스크립트를 로드하도록 만들 수도 있다는 점에 유의하세요.

확장 내부

보통 확장 내부에서 메시지를 보내기 위해 함수 **chrome.runtime.sendMessage**가 사용됩니다(보통 background script가 처리). 이를 수신하고 처리하기 위해 **chrome.runtime.onMessage.addListener**를 호출하는 리스너가 선언됩니다.

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

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, ...

필요한 경우 extension ID를 언급해야 합니다.

Native Messaging

background scripts가 시스템 내부의 바이너리와 통신할 수 있으며, 이 통신이 적절히 보호되지 않으면 RCEs와 같은 치명적인 취약점에 노출될 수 있습니다. More on this later.

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

↔︎ 콘텐츠 스크립트 통신

content scripts가 동작하는 환경과 호스트 페이지가 존재하는 환경은 서로 분리되어 있어 격리됩니다. 이 격리에도 불구하고 양쪽은 공용 자원인 페이지의 **Document Object Model (DOM)**과 상호작용할 수 있습니다. 호스트 페이지가 content script와 통신하거나, 콘텐츠 스크립트를 통해 간접적으로 확장 프로그램과 통신하려면 양쪽에서 접근 가능한 DOM을 통신 채널로 사용해야 합니다.

Post Messages

// 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
)
document.getElementById("theButton").addEventListener(
"click",
() => {
window.postMessage(
{ type: "FROM_PAGE", text: "Hello from the webpage!" },
"*"
)
},
false
)

보안적인 Post Message 통신은 수신된 메시지의 진위를 확인해야 하며, 이는 다음을 확인함으로써 수행할 수 있다:

  • event.isTrusted: 이 값은 이벤트가 사용자의 동작에 의해 트리거된 경우에만 True다
  • content script는 사용자가 어떤 동작을 수행할 때만 메시지를 기대하도록 설계될 수 있다
  • origin domain: 도메인 허용 목록에서만 메시지를 수락하도록 설정될 수 있다
  • regex를 사용하는 경우 매우 주의하라
  • Source: received_message.source !== window는 메시지가 Content Script가 수신 중인 동일한 window로부터 왔는지 확인하는 데 사용할 수 있다.

위의 검사들은 수행되더라도 취약할 수 있으므로, 다음 페이지에서 potential Post Message bypasses를 확인하라:

PostMessage Vulnerabilities

Iframe

다른 가능한 통신 방식은 Iframe URLs를 통한 것이며, 예시는 다음에서 볼 수 있다:

BrowExt - XSS Example

DOM

이것은 “정확히” 통신 방식은 아니지만, web and the content script will have access to the web DOM. 따라서 content script가 해당 DOM에서 정보를 읽고 trusting the web DOM할 경우, 웹은 modify this data(웹을 신뢰하면 안 되거나 웹이 XSS에 취약하기 때문에)를 통해 compromise the Content Script할 수 있다.

예시로 브라우저 확장 기능을 탈취하기 위한 DOM based XSS 예시는 다음에서 찾을 수 있다:

BrowExt - XSS Example

Content Script ↔︎ Background Script 통신

A Content Script can use the functions runtime.sendMessage() or tabs.sendMessage() to send a one-time JSON-serializable message.

To handle the response, use the returned Promise. Although, for backward compatibility, you can still pass a callback as the last argument.

Sending a request from a content script looks like this:

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

extension (보통 background script)에서 요청을 전송하기.
선택된 tab의 content script에 메시지를 보내는 예시:

// 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)
})()

수신 측에서는 메시지를 처리하기 위해 runtime.onMessage event listener를 설정해야 합니다. 이는 content script 또는 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()**는 동기 방식으로 실행되었습니다. onMessage 이벤트 핸들러를 sendResponse()의 비동기 실행으로 수정하려면 return true;를 반드시 포함해야 합니다.

중요한 점은 여러 페이지가 onMessage 이벤트를 받도록 설정된 경우, 특정 이벤트에 대해 가장 먼저 sendResponse()를 실행한 페이지만 응답을 실제로 전달할 수 있다는 것입니다. 동일한 이벤트에 대한 이후의 응답들은 고려되지 않습니다.

새 확장 프로그램을 작성할 때는 callbacks보다 promises를 사용하는 것이 권장됩니다. callbacks 사용과 관련해서는, sendResponse() 함수는 동기 컨텍스트 내에서 직접 실행되거나 이벤트 핸들러가 true를 반환하여 비동기 작업을 표시하는 경우에만 유효합니다. 어느 핸들러도 true를 반환하지 않거나 sendResponse() 함수가 메모리에서 제거(garbage-collected)되면, sendMessage()에 연결된 callback이 기본적으로 호출됩니다.

Native Messaging

Browser extensions 또한 binaries in the system via stdin와 통신할 수 있습니다. 애플리케이션은 이를 나타내는 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은 브라우저 확장 프로그램의 백그라운드 스크립트에서 application과 통신하기 위해 runtime.connectNative() 또는 runtime.sendNativeMessage()에 전달되는 문자열입니다. path는 바이너리의 경로이며, 유효한 type은 stdio( stdin과 stdout 사용 ) 하나뿐이고 allowed_origins는 접근할 수 있는 확장(extensions)을 지정하며 와일드카드를 사용할 수 없습니다.

Chrome/Chromium은 이 json을 Windows 레지스트리의 일부 위치와 macOS 및 Linux의 몇몇 경로에서 검색합니다 (자세한 내용은 docs 참조).

Tip

브라우저 확장 프로그램도 이 통신을 사용하려면 nativeMessaing 권한이 선언되어 있어야 합니다.

아래는 백그라운드 스크립트 코드가 native application으로 메시지를 보내는 예시입니다:

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

In this blog post, a vulnerable pattern abusing native messages is proposed:

  1. content script에 대해 Browser extension에 와일드카드 패턴이 있다.
  2. Content script가 postMessage 메시지를 sendMessage를 사용해 background script로 전달한다.
  3. Background script가 메시지를 sendNativeMessage를 사용해 native application으로 전달한다.
  4. Native application이 메시지를 위험하게 처리하여 code execution으로 이어진다.

그리고 그 안에서 어떤 페이지에서든 브라우저 확장 프로그램을 악용해 RCE에 이르는 예제가 설명되어 있다.

Sensitive Information in Memory/Code/Clipboard

Browser Extension이 민감한 정보를 메모리에 저장하는 경우, 특히 Windows 머신에서는 이를 덤프하고 해당 정보를 검색할 수 있다.

따라서 Browser Extension의 메모리는 안전하다고 간주해서는 안 되며, 자격증명(credentials)이나 니모닉 문구(mnemonic phrases) 같은 민감한 정보는 저장해서는 안 된다.

물론, 민감한 정보를 코드에 넣지 마라, 코드에 들어간 정보는 공개적이 된다.

브라우저에서 메모리를 덤프하려면 프로세스 메모리를 덤프하거나 브라우저 확장 프로그램의 설정으로 가서 Inspect pop-up -> Memory 섹션 -> Take a snaphost -> CTRL+F 로 스냅샷 내부를 검색해 민감한 정보를 찾을 수 있다.

또한, 니모닉 키나 비밀번호처럼 매우 민감한 정보는 클립보드에 복사되도록 허용해서는 안 되며(적어도 몇 초 후에 클립보드에서 제거해야 한다), 그렇지 않으면 클립보드를 모니터링하는 프로세스가 해당 정보를 얻을 수 있다.

Loading an Extension in the Browser

  1. 브라우저 확장 프로그램을 다운로드하고 압축을 푼다.
  2. **chrome://extensions/**로 이동하여 Developer Mode활성화한다.
  3. Load unpacked 버튼을 클릭한다.

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

Getting the source code from the store

Chrome 확장 프로그램의 소스 코드는 여러 방법으로 얻을 수 있다. 아래에는 각 옵션에 대한 자세한 설명과 지침이 있다.

Download Extension as ZIP via Command Line

Chrome 확장 프로그램의 소스 코드는 커맨드 라인을 통해 ZIP 파일로 다운로드할 수 있다. 이는 특정 URL에서 curl을 사용해 ZIP 파일을 가져오고, ZIP 파일의 내용을 디렉터리에 압축 해제하는 과정을 포함한다. 절차는 다음과 같다:

  1. 실제 확장 프로그램 ID로 “extension_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 Viewer 웹사이트 사용

https://robwu.nl/crxviewer/

CRX Viewer 확장 프로그램 사용

다른 편리한 방법은 Chrome Extension Source Viewer를 사용하는 것입니다. 이 프로젝트는 오픈소스이며 Chrome Web Store에서 설치할 수 있습니다. 뷰어의 소스 코드는 해당 GitHub repository에 있습니다.

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

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

  1. chrome://version/을 방문하여 “Profile Path” 필드를 찾아 Chrome 로컬 프로필 디렉터리에 접근합니다.
  2. 프로필 디렉터리 내의 Extensions/ 하위 폴더로 이동합니다.
  3. 이 폴더에는 설치된 모든 확장 프로그램이 들어 있으며, 일반적으로 소스 코드가 읽을 수 있는 형태로 포함되어 있습니다.

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

  • about:extensions 페이지에서 “Developer Mode“를 활성화하면 각 확장 프로그램의 ID를 볼 수 있습니다.
  • 각 확장 프로그램 폴더의 manifest.json 파일에는 식별에 도움이 되는 읽을 수 있는 name 필드가 포함되어 있습니다.

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

Chrome Web Store에 가서 확장 프로그램을 다운로드하세요. 파일은 .crx 확장자를 갖습니다. 파일 확장자를 .crx에서 .zip으로 변경하세요. WinRAR, 7-Zip 등과 같은 파일 압축 프로그램을 사용해 ZIP 파일의 내용을 추출하면 됩니다.

Chrome에서 Developer Mode 사용

Chrome을 열고 chrome://extensions/로 이동하세요. 우측 상단에서 “Developer mode“를 활성화합니다. “Load unpacked extension…“을 클릭하고 확장 프로그램의 디렉터리로 이동합니다. 이 방법은 소스 코드를 다운로드하지는 않지만 이미 다운로드되었거나 개발 중인 확장 프로그램의 코드를 보고 수정하는 데 유용합니다.

Chrome extension manifest dataset

취약한 브라우저 확장 프로그램을 찾아보려면 https://github.com/palant/chrome-extension-manifests-dataset를 사용해 manifest 파일에서 잠재적으로 취약한 징후를 확인할 수 있습니다. 예를 들어 사용자 수가 25000명이 넘고, content_scripts와 권한 nativeMessaing을 가진 확장 프로그램을 확인하려면:

# 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')"

Post-exploitation: Forced extension load & persistence (Windows)

Chromium에 사용자별 Preferences를 직접 편집하고 유효한 HMACs를 위조하여 백도어를 심는 은밀한 기법으로, 브라우저가 프롬프트나 플래그 없이 임의의 unpacked extension을 수락하고 활성화하게 만든다.

Forced Extension Load Preferences Mac Forgery Windows

Detecting Malicious Extension Updates (Static Version Diffing)

공급망 침해는 종종 이전에 정상적이던 확장 프로그램에 대한 malicious updates 형태로 발생합니다. 실용적이고 저소음인 접근법은 새 확장 패키지를 정적 분석을 통해 마지막 알려진 정상 버전과 비교하는 것입니다(예: Assemblyline). 목표는 모든 변경에 경보를 울리는 것이 아니라 신호가 높은 델타에 대해 경보하는 것입니다.

Workflow

  • 두 버전 모두 제출(old + new)을 동일한 static-analysis 프로필에 제출.
  • 새로 추가되거나 업데이트된 background/service worker 스크립트 표시(persistence + privileged logic).
  • 새로 추가되거나 업데이트된 content scripts 표시(DOM 접근 및 데이터 수집).
  • manifest.json에서 추가된 permissions/host_permissions 표시.
  • 코드에서 추출된 새 도메인 표시(잠재적 C2/유출 엔드포인트).
  • 새 static-analysis 탐지 표시(예: base64 decode, cookie harvesting, network-request builders, obfuscation patterns).
  • 변경된 스크립트에서 급격한 엔트로피 상승이나 이상치 z-score 같은 통계적 이상치 표시.

Detecting script changes accurately

  • 새 스크립트 추가됨manifest.json diff로 탐지.
  • 기존 스크립트 수정됨(manifest는 변경 없음) → 추출된 파일 트리의 파일별 해시 비교(e.g., Assemblyline Extract 출력). 이는 기존 worker나 content script에 대한 은밀한 업데이트를 잡아낸다.

Pre-disclosure detections

이미 알려진 IOC에 기반한 “쉬운 방식” 탐지를 피하기 위해, threat-intel-fed 서비스들을 비활성화하고 도메인, 휴리스틱 서명, 스크립트 델타, 엔트로피 이상치 같은 내재적 신호에 의존하십시오. 이렇게 하면 공개 리포트 이전에 malicious updates를 발견할 가능성이 높아집니다.

Example high-confidence alert logic

  • Low-noise combo: 새 도메인 + 새 static-analysis 탐지 + 업데이트된 background/service worker + 업데이트되었거나 추가된 content scripts.
  • Broader catch: 새 도메인 + 새 또는 업데이트된 background/service worker (더 높은 재현율, 더 많은 노이즈).

이 워크플로우에 유용한 Assemblyline 서비스:

  • Extract: 확장팩을 풀고 파일별 해시를 생성.
  • Characterize: 파일 특성(예: 엔트로피) 계산.
  • JsJAWS / FrankenStrings / URLCreator: JS 휴리스틱, 문자열, 도메인을 표면화하여 버전 간 diff에 사용.

Security Audit Checklist

비록 브라우저 확장 프로그램(Browser Extensions)은 제한된 공격 표면을 가지지만, 일부는 취약점 또는 강화가 필요한 부분을 포함할 수 있습니다. 다음 항목들이 가장 흔합니다:

  • 가능한 한 요청되는 **permissions**을 제한할 것
  • 가능한 한 **host_permissions**을 제한할 것
  • 강력한 content_security_policy 사용
  • 가능한 한 **externally_connectable**을 제한; 필요 없고 가능하면 기본값으로 비워두고 {} 명시
  • URL이 XSS 또는 takeover에 취약하게 언급되어 있으면, 공격자가 background scripts에 직접 메시지를 보낼 수 있음. 매우 강력한 우회.
  • 가능한 한 **web_accessible_resources**을 제한(가능하면 비워두기).
  • **web_accessible_resources**가 none이 아니면, ClickJacking 확인
  • 확장 프로그램에서 웹 페이지로 통신이 발생하면, 통신 과정에서 유발된 XSS 취약점인지 확인
  • Post Messages를 사용하면, Post Message vulnerabilities를 확인.
  • Content Script가 DOM에 접근하면, 웹에 의해 수정될 경우 XSS를 도입하지 않는지 확인
  • 특히 Content Script -> Background script 통신에 이 통신이 관여하는 경우 특별히 강조할 것
  • background script가 native messaging으로 통신하면, 통신이 안전하고 입력이 정제되는지 확인
  • 민감한 정보는 Browser Extension 코드 안에 저장하면 안 됨
  • 민감한 정보는 Browser Extension 메모리 안에 저장하면 안 됨
  • 민감한 정보는 보호되지 않은 파일 시스템에 저장하면 안 됨

Browser Extension Risks

  • https://crxaminer.tech/는 확장 프로그램이 요청하는 permissions 같은 데이터를 분석하여 확장 사용의 위험 수준을 제공한다.

Tools

Tarnish

  • 제공된 Chrome webstore 링크에서 Chrome extension을 가져옴.
  • manifest.json viewer: 확장 manifest의 JSON-prettified 버전을 표시.
  • Fingerprint Analysis: web_accessible_resources 탐지 및 Chrome extension fingerprinting JavaScript 자동 생성.
  • Potential Clickjacking Analysis: web_accessible_resources 지시자가 설정된 확장 HTML 페이지를 탐지. 페이지 목적에 따라 clickjacking에 취약할 수 있음.
  • Permission Warning(s) viewer: 사용자가 확장을 설치할 때 표시되는 모든 Chrome permission prompt 경고 목록을 표시.
  • Dangerous Function(s): innerHTML, chrome.tabs.executeScript 같은 공격자가 악용할 수 있는 위험한 함수의 위치를 표시.
  • Entry Point(s): 확장이 사용자/외부 입력을 수신하는 지점을 표시. 확장의 표면적을 이해하고 악의적으로 조작된 데이터를 보낼 잠재적 지점을 찾는 데 유용.
  • Dangerous Function(s)와 Entry Point(s) 스캐너가 생성하는 경고에는 다음이 포함:
    • 경고를 발생시킨 관련 코드 스니펫과 줄 번호.
    • 문제 설명.
    • 전체 소스 파일을 보는 “View File” 버튼.
    • 경고된 파일의 경로.
    • 경고된 파일의 전체 Chrome extension URI.
    • Background Page script, Content Script, Browser Action 등 파일 유형.
    • 취약한 라인이 JavaScript 파일에 있을 경우 그 파일이 포함된 모든 페이지의 경로와 페이지 유형, web_accessible_resource 상태.
  • Content Security Policy (CSP) analyzer and bypass checker: 확장 CSP의 약점을 지적하고, 화이트리스트된 CDN 등으로 인해 CSP를 우회할 수 있는 잠재적 방법을 밝힘.
  • Known Vulnerable Libraries: Retire.js를 사용하여 알려진 취약 JavaScript 라이브러리 사용 여부 점검.
  • 확장 다운로드 및 형식화된 버전 다운로드.
  • 원본 확장 다운로드.
  • 정리(prettified)된 버전의 확장 다운로드(자동 prettified HTML 및 JavaScript).
  • 스캔 결과 자동 캐싱: 처음 스캔은 시간이 걸리지만, 확장이 업데이트되지 않았다면 두 번째 스캔은 캐시로 인해 거의 즉시 완료됨.
  • 링크 가능한 보고서 URL: Tarnish가 생성한 확장 보고서를 쉽게 공유 가능.

Neto

Project Neto는 Firefox와 Chrome 같은 잘 알려진 브라우저용 브라우저 플러그인과 확장의 숨겨진 특징을 분석하고 파악하도록 고안된 Python 3 패키지입니다. 패키지 파일을 자동으로 풀어 manifest.json, localization 폴더, Javascript 및 HTML 소스 파일 같은 관련 리소스에서 이러한 특징을 추출합니다.

References

Tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기