브라우저 확장 프로그램 Pentesting 방법론

Reading time: 25 minutes

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

Content Scripts

각 content script는 단일 웹 페이지의 DOM에 직접 접근할 수 있으므로 잠재적으로 악의적인 입력에 노출됩니다. 그러나 content script는 확장 프로그램 코어에 메시지를 보낼 수 있는 기능 외에는 권한을 가지지 않습니다.

Extension Core

extension core는 확장 프로그램의 대부분 권한/접근을 포함하지만, extension core는 웹 컨텐츠와 상호작용할 때 XMLHttpRequest와 content scripts를 통해서만 가능합니다. 또한 extension core는 호스트 시스템에 대한 직접적인 접근 권한이 없습니다.

Native Binary

확장 프로그램은 사용자의 전체 권한으로 호스트 시스템에 접근할 수 있는 native binary를 허용할 수 있습니다. native binary는 Flash 및 기타 브라우저 플러그인에서 사용되는 표준 Netscape Plugin Application Programming Interface (NPAPI)를 통해 extension core와 상호작용합니다.

Boundaries

caution

사용자의 전체 권한을 얻기 위해, 공격자는 content script에서 extension의 core로 악성 입력을 전달하고 extension의 core에서 native binary로 전달하도록 확장 프로그램을 속여야 합니다.

확장 프로그램의 각 구성 요소는 서로 강력한 보호 경계로 분리되어 있습니다. 각 구성 요소는 별도의 운영 체제 프로세스에서 실행됩니다. Content scripts와 extension cores는 대부분의 운영 체제 서비스에서 접근할 수 없는 sandbox 프로세스에서 실행됩니다.

또한 content scripts는 별도의 JavaScript 힙에서 실행됨으로써 관련 웹 페이지와 분리됩니다. content script와 웹 페이지는 같은 기본 DOM에 접근할 수 있지만, 두 객체는 JavaScript 포인터를 절대 교환하지 않으므로 JavaScript 기능의 leak를 방지합니다.

manifest.json

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

Example:

json
{
"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)에 임의로 접근할 수 있습니다.

json
"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 값을 가져올 때 페이지에 explain 버튼을 추가하는 예시 content script입니다.

js
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 같은 몇 가지 예외만 직접 접근할 수 있습니다. 이러한 예외를 벗어난 기능은 확장 프로그램 페이지로 메시지를 보내고, 콘텐츠 스크립트가 해당 페이지와 통신합니다.

warning

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

Chrome에서 콘텐츠 스크립트를 보고 디버깅하려면 Chrome 개발자 도구 메뉴를 Options > More tools > Developer tools에서 열거나 Ctrl + Shift + I를 누르세요.

개발자 도구가 표시되면 Source tab을 클릭한 다음 Content Scripts 탭을 선택합니다. 이렇게 하면 다양한 확장 프로그램에서 실행 중인 콘텐츠 스크립트를 관찰하고 실행 흐름을 추적하기 위해 브레이크포인트를 설정할 수 있습니다.

Injected content scripts

tip

Note that Content Scripts aren't mandatory — 웹 페이지에 스크립트를 동적으로(inject) 주입하거나 프로그래밍 방식으로 주입하는 것도 가능하며, **tabs.executeScript**를 통해 수행할 수 있습니다. 이는 보다 세밀한 제어를 제공합니다.

콘텐츠 스크립트를 프로그래밍 방식으로 주입하려면, 스크립트를 주입할 페이지에 대한 [host permissions]가 확장 프로그램에 있어야 합니다. 이러한 권한은 확장 프로그램의 매니페스트에서 요청(requesting them) 하거나 일시적으로 activeTab를 통해 확보할 수 있습니다.

activeTab 기반 확장 예제

manifest.json
{
"name": "My extension",
...
"permissions": [
"activeTab",
"scripting"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_title": "Action Button"
}
}
  • 클릭 시 JS 파일을 주입:
javascript
// 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"],
})
})
  • 클릭 시 함수 주입:
javascript
//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,
})
})

스크립팅 권한이 있는 예제

javascript
// 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" })

In order to include or exclude more URLs it's also possible to use include_globs and exclude_globs.

콘텐츠 스크립트 run_at

The run_at field controls 언제 JavaScript 파일이 웹 페이지에 주입되는지. The preferred and default value is "document_idle".

The possible values are:

  • document_idle: 가능한 경우마다
  • document_start: css의 파일들이 적용된 후이지만 다른 DOM이 구성되거나 다른 스크립트가 실행되기 전에.
  • document_end: DOM이 완료된 직후이지만 이미지나 프레임 같은 하위 리소스가 로드되기 전.

Via manifest.json

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

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

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

background

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

핵심 포인트:

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

tip

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

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

js
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, 로 이동하면 개발자 도구가 백그라운드 스크립트와 함께 열립니다:

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.
  • 확장 프로그램이 새 탭에서 로드할 페이지.
  • Option Pages: 이 페이지는 클릭하면 확장 위에 표시됩니다. 이전 manifest에서 제 경우 이 페이지에 chrome://extensions/?options=fadlhnelkbeojnebcbkacjilhnbjfjca로 접근할 수 있었습니다 또는 클릭하면:

이러한 페이지들은 필요에 따라 동적으로 콘텐츠를 로드하기 때문에 백그라운드 페이지처럼 항상 지속되지 않는다는 점에 유의하세요. 그럼에도 불구하고, 이들은 백그라운드 페이지와 몇 가지 기능을 공유합니다:

  • Content Scripts와의 통신: 백그라운드 페이지와 유사하게, 이러한 페이지들은 content scripts로부터 메시지를 받을 수 있어 확장 내 상호작용을 촉진합니다.
  • 확장 전용 API에 대한 접근: 이러한 페이지들은 확장에 정의된 권한에 따라 확장 전용 API에 광범위하게 접근할 수 있습니다.

permissions & host_permissions

**permissions**와 **host_permissions**는 manifest.json의 항목으로, 브라우저 확장 프로그램이 (storage, location...) 어떤 권한을 갖고 있는지와 어떤 웹 페이지에서 동작하는지를 나타냅니다.

브라우저 확장 프로그램은 매우 권한이 강력할 수 있기 때문에, 악성 확장이나 침해된 확장은 공격자에게 사용자로부터 민감한 정보를 훔치고 감시할 수 있는 다양한 수단을 제공할 수 있습니다.

Check how these settings work and how they could get abused in:

BrowExt - permissions & host_permissions

content_security_policy

A content security policy can be declared also inside the manifest.json. If there is one defined, it could be vulnerable.

The default setting for browser extension pages is rather restrictive:

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

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

Content Security Policy (CSP) Bypass

web_accessible_resources

웹페이지가 Browser Extension의 페이지(예: .html 페이지)에 접근하려면, 해당 페이지는 manifest.jsonweb_accessible_resources 필드에 명시되어 있어야 합니다.
예시:

javascript
{
...
"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.

Being allowed to access these pages make these pages potentially vulnerable ClickJacking:

BrowExt - ClickJacking

tip

Allowing these pages to be loaded only by the extension and not by random URLs could prevent ClickJacking attacks.

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:
json
"matches": [
"https://*.google.com/*",
"*://*.chromium.org/*",
  • If it's specified as empty: "externally_connectable": {}, no app or web will be able to connect.

여기에서 지정된 extensions 및 URLs가 적을수록 공격 표면(attack surface) 이 작아집니다.

caution

If a web page vulnerable to XSS or takeover is indicated in externally_connectable, an attacker will be able to send messages directly to the background script, completely bypassing the Content Script and its CSP.

Therefore, this is a very powerful bypass.

Moreover, if the client installs a rouge extension, even if it isn't allowed to communicate with the vulnerable extension, it could inject XSS data in an allowed web page or abuse WebRequest or DeclarativeNetRequest APIs to manipulate requests on a targeted domain altering a page's request for a JavaScript file. (Note that CSP on the targeted page could prevent these attacks). This idea comes from this writeup.

통신 요약

Extension <--> WebApp

content script와 웹 페이지 간 통신에는 보통 post messages가 사용됩니다. 따라서 웹 애플리케이션에서는 보통 window.postMessage 호출을, content script 쪽에서는 window.addEventListener 같은 리스너를 볼 수 있습니다. 다만 extension이 웹 애플리케이션에 Post Message를 보내 통신할 수도(따라서 웹 쪽에서 이를 대비해야 함) 있고, 단순히 웹이 새로운 스크립트를 로드하게 만들 수도 있습니다.

확장 내부

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

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

chrome.runtime.connect() example
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
})

또한 background script에서 특정 탭에 위치한 content script로 메시지를 보내기 위해 **chrome.tabs.sendMessage**를 호출할 수 있으며, 이때 메시지를 보낼 탭의 ID를 지정해야 합니다.

허용된 externally_connectable에서 확장 프로그램으로

허용된 Web apps 및 외부 브라우저 확장 프로그램externally_connectable 구성에서 다음을 사용하여 요청을 보낼 수 있습니다 :

javascript
chrome.runtime.sendMessage(extensionId, ...

필요한 곳에서는 extension ID를 언급하세요.

Native Messaging

백그라운드 스크립트가 시스템 내부의 바이너리와 통신할 수 있으며, 이 통신이 적절히 보호되지 않으면 RCEs와 같은 치명적인 취약점에 취약할 수 있습니다. More on this later.

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

↔︎ Content Script Communication

content scripts가 동작하는 환경과 호스트 페이지가 존재하는 환경은 서로 분리되어 있어 격리가 보장됩니다. 이러한 격리에도 불구하고, 양측은 공용 자원인 페이지의 **Document Object Model (DOM)**과 상호작용할 수 있습니다. 호스트 페이지가 content script와 통신하거나 content script를 통해 extension과 간접적으로 통신하려면, 양측에서 접근 가능한 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
)

A secure Post Message communication should check the authenticity of the received message, this can be done checking:

  • event.isTrusted: 이 값은 이벤트가 사용자의 동작에 의해 트리거된 경우에만 True입니다
  • Content Script는 사용자가 특정 동작을 수행할 때에만 메시지를 기대하도록 설계될 수 있습니다
  • origin domain: 특정 도메인들의 allowlist(허용 목록)에만 메시지를 허용하도록 기대할 수 있습니다.
  • If a regex is used, be very careful
  • Source: received_message.source !== window 를 사용해서 메시지가 Content Script가 리스닝 중인 동일한 창에서 온 것인지 확인할 수 있습니다.

The previous checks, even if performed, could be vulnerable, so check in the following page potential Post Message bypasses:

PostMessage Vulnerabilities

Iframe

Another possible way of communication might be through Iframe URLs, you can find an example in:

BrowExt - XSS Example

DOM

This isn't "exactly" a communication way, but the web and the content script will have access to the web DOM. So, if the content script is reading some information from it, trusting the web DOM, the web could modify this data (because the web shouldn't be trusted, or because the web is vulnerable to XSS) and compromise the Content Script.

You can also find an example of a DOM based XSS to compromise a browser extension in:

BrowExt - XSS Example

Content Script ↔︎ Background Script Communication

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:

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

extension (보통 background script)에서 요청을 보냅니다. 선택된 탭의 content script로 메시지를 보내는 예:

javascript
// 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 이벤트 리스너를 설정해야 합니다. 이 동작은 content script나 extension page에서 동일하게 보입니다.

javascript
// 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()를 실행한 페이지만 응답을 전달할 수 있다는 점에 유의해야 합니다. 같은 이벤트에 대한 이후의 응답은 무시됩니다.

새 확장 기능을 작성할 때는 promises보다 callbacks보다 promises를 사용하는 것이 바람직합니다. callbacks를 사용할 경우 sendResponse()는 동기 컨텍스트 내에서 직접 실행되었거나 이벤트 핸들러가 return true;로 비동기 작업임을 표시한 경우에만 유효합니다. 어떤 핸들러도 true를 반환하지 않거나 sendResponse()가 메모리에서 해제(garbage-collected)되면, sendMessage()에 전달된 콜백은 기본적으로 호출됩니다.

Native Messaging

Browser extensions also allow to communicate with binaries in the system via stdin. The application must install a json indicating so in a json like:

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/"]
}

Where the name is the string passed to runtime.connectNative() or runtime.sendNativeMessage() to communicate with the application from the background scripts of the browser extension. The path is the path to the binary, there is only 1 valid type which is stdio (use stdin and stdout) and the allowed_origins indicate the extensions that can access it (and can't have wildcard).

Chrome/Chromium will search for this json in some windows registry and some paths in macOS and Linux (more info in the docs).

tip

The browser extension also needs the nativeMessaing permission declared in order to be able to use this communication.

This is how it looks like some background script code sending messages to a native application:

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

In this blog post, native messages를 악용하는 취약한 패턴이 제안됩니다:

  1. Browser extension has a wildcard pattern for content script.
  2. Content script passes postMessage messages to the background script using sendMessage.
  3. Background script passes the message to native application using sendNativeMessage.
  4. Native application handles the message dangerously, leading to code execution.

그리고 이 글에는 어떤 페이지에서든 browser extension을 악용해 RCE로 이어지는 예제가 설명되어 있습니다.

Sensitive Information in Memory/Code/Clipboard

Browser Extension이 메모리에 민감한 정보를 저장하는 경우, 특히 Windows 환경에서는 해당 메모리가 **덤프(dump)**되어 그 정보를 검색할 수 있습니다.

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

물론, not put sensitive information in the code, 이는 **공개(public)**될 수 있습니다.

브라우저에서 메모리를 덤프하려면 프로세스 메모리를 dump the process memory하거나 browser extension의 settings로 가서 **Inspect pop-up**을 클릭한 뒤 Memory 섹션에서 **Take a snaphost**를 수행하고 **CTRL+F**로 스냅샷 내부를 검색하여 민감한 정보를 찾을 수 있습니다.

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

Loading an Extension in the Browser

  1. Download the Browser Extension & unzipped
  2. Go to chrome://extensions/ and enable the Developer Mode
  3. Click the Load unpacked button

In Firefox you go to about:debugging#/runtime/this-firefox and click Load Temporary Add-on button.

Getting the source code from the store

Chrome extension의 소스 코드는 여러 방법으로 얻을 수 있습니다. 아래에는 각 옵션에 대한 자세한 설명과 지침이 나와 있습니다.

Download Extension as ZIP via Command Line

Chrome extension의 소스 코드는 커맨드 라인을 통해 ZIP 파일로 다운로드할 수 있습니다. 이는 특정 URL에서 curl을 사용해 ZIP 파일을 가져오고 ZIP 파일의 내용을 디렉터리로 추출하는 과정을 포함합니다. 단계는 다음과 같습니다:

  1. "extension_id"를 실제 확장 프로그램의 ID로 교체합니다.
  2. 다음 명령을 실행합니다:
bash
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/을 방문하여 Chrome 로컬 프로필 디렉터리에 접근한 다음 "Profile Path" 필드를 찾습니다.
  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 데이터셋

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

bash
# 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)

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

Forced Extension Load Preferences Mac Forgery Windows

보안 감사 체크리스트

Even though Browser Extensions have a limited attack surface, some of them might contain vulnerabilities or potential hardening improvements. The following ones are the most common ones:

  • 요청된 permissions을 가능한 한 제한하세요.
  • 요청된 **host_permissions**를 가능한 한 제한하세요.
  • 강력한 **content_security_policy**를 사용하세요.
  • **externally_connectable**를 가능한 한 제한하세요. 필요 없고 가능하다면 기본값으로 두지 말고 **{}**로 명시하세요.
  • 여기에서 URL vulnerable to XSS or to takeover가 언급되어 있다면, 공격자는 background scripts에 직접 메시지를 보낼 수 있게 됩니다. 매우 강력한 우회입니다.
  • **web_accessible_resources**를 가능한 한 제한하세요. 가능하면 비워두세요.
  • **web_accessible_resources**가 비어있지 않다면 ClickJacking을 확인하세요.
  • 확장에서 웹 페이지로 어떤 communication이 발생한다면, 통신 과정에서 유발되는 XSS vulnerabilities를 확인하세요.
  • Post Messages가 사용된다면 Post Message vulnerabilities를 확인하세요.
  • Content Script가 DOM 세부 정보를 접근한다면, 웹에 의해 modified될 경우 XSS를 도입하지 않는지 확인하세요.
  • 이 통신이 Content Script -> Background script communication에도 관여되어 있다면 특히 주의하세요.
  • background script가 native messaging으로 통신한다면, 통신이 안전하고 입력이 적절히 정제되어 있는지 확인하세요.
  • Browser Extension의 code 내부에 민감한 정보가 저장되어서는 안 됩니다.
  • Browser Extension의 memory 안에 민감한 정보가 저장되어서는 안 됩니다.
  • file system unprotected에 민감한 정보가 저장되어서는 안 됩니다.

Browser Extension Risks

  • https://crxaminer.tech/은 permissions 같은 데이터를 분석하여 Browser Extension 사용의 위험 수준을 제공합니다.

Tools

Tarnish

  • 제공된 Chrome webstore 링크에서 Chrome extension을 가져옵니다.
  • manifest.json viewer: 확장의 manifest를 JSON 형식으로 보기 좋게 표시합니다.
  • Fingerprint Analysis: web_accessible_resources를 감지하고 자동으로 Chrome extension fingerprinting JavaScript를 생성합니다.
  • Potential Clickjacking Analysis: web_accessible_resources 지시어가 설정된 확장 HTML 페이지를 감지합니다. 이러한 페이지들은 목적에 따라 clickjacking에 취약할 수 있습니다.
  • Permission Warning(s) viewer: 확장 설치 시 사용자에게 표시되는 모든 Chrome 권한 프롬프트 경고 목록을 표시합니다.
  • 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 라이브러리 사용을 검사합니다.
  • 확장 다운로드 및 포매팅된 버전 제공.
  • 원본 확장 다운로드.
  • 예쁘게 정리된(beautified) 확장 버전 다운로드(자동으로 prettified된 HTML 및 JavaScript).
  • 스캔 결과 자동 캐시: 처음 스캔은 시간이 걸리지만, 확장이 업데이트되지 않았다면 두 번째 스캔은 결과가 캐시되어 거의 즉시 완료됩니다.
  • 링크 가능한 리포트 URL: tarnish로 생성된 확장 리포트를 다른 사람에게 쉽게 공유할 수 있습니다.

Neto

Project Neto는 Firefox 및 Chrome과 같은 잘 알려진 브라우저용 플러그인과 확장의 숨겨진 기능을 분석하고 추출하도록 고안된 Python 3 패키지입니다. 패키지된 파일을 자동으로 압축 해제하고 manifest.json, 지역화 폴더(localization folders) 또는 Javascript 및 HTML 소스 파일 같은 관련 리소스에서 이러한 기능을 추출합니다.

참고자료

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 지원하기