708 lines
44 KiB
Markdown

# 브라우저 확장 펜테스팅 방법론
{{#include ../../banners/hacktricks-training.md}}
## 기본 정보
브라우저 확장은 JavaScript로 작성되며 브라우저에 의해 백그라운드에서 로드됩니다. 자체 [DOM](https://www.w3schools.com/js/js_htmldom.asp)을 가지고 있지만 다른 사이트의 DOM과 상호작용할 수 있습니다. 이는 다른 사이트의 기밀성, 무결성 및 가용성(CIA)을 위협할 수 있음을 의미합니다.
## 주요 구성 요소
확장 레이아웃은 시각화할 때 가장 잘 보이며 세 가지 구성 요소로 구성됩니다. 각 구성 요소를 자세히 살펴보겠습니다.
<figure><img src="../../images/image (16) (1) (1).png" alt=""><figcaption><p><a href="http://webblaze.cs.berkeley.edu/papers/Extensions.pdf">http://webblaze.cs.berkeley.edu/papers/Extensions.pdf</a></p></figcaption></figure>
### **콘텐츠 스크립트**
각 콘텐츠 스크립트는 **단일 웹 페이지**의 DOM에 직접 접근할 수 있으며, 따라서 **잠재적으로 악의적인 입력**에 노출됩니다. 그러나 콘텐츠 스크립트는 확장 코어에 메시지를 전송할 수 있는 능력 외에는 권한이 없습니다.
### **확장 코어**
확장 코어는 대부분의 확장 권한/접근을 포함하지만, 확장 코어는 [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) 및 콘텐츠 스크립트를 통해서만 웹 콘텐츠와 상호작용할 수 있습니다. 또한, 확장 코어는 호스트 머신에 직접 접근할 수 없습니다.
### **네이티브 바이너리**
확장은 **사용자의 전체 권한으로 호스트 머신에 접근할 수 있는 네이티브 바이너리**를 허용합니다. 네이티브 바이너리는 Flash 및 기타 브라우저 플러그인에서 사용되는 표준 Netscape Plugin Application Programming Interface ([NPAPI](https://en.wikipedia.org/wiki/NPAPI))를 통해 확장 코어와 상호작용합니다.
### 경계
> [!CAUTION]
> 사용자의 전체 권한을 얻으려면 공격자는 확장에 콘텐츠 스크립트에서 확장 코어로, 그리고 확장 코어에서 네이티브 바이너리로 악의적인 입력을 전달하도록 설득해야 합니다.
확장의 각 구성 요소는 **강력한 보호 경계**로 서로 분리되어 있습니다. 각 구성 요소는 **별도의 운영 체제 프로세스**에서 실행됩니다. 콘텐츠 스크립트와 확장 코어는 대부분의 운영 체제 서비스에서 사용할 수 없는 **샌드박스 프로세스**에서 실행됩니다.
또한, 콘텐츠 스크립트는 **별도의 JavaScript 힙**에서 실행되어 관련 웹 페이지와 분리됩니다. 콘텐츠 스크립트와 웹 페이지는 **같은 기본 DOM에 접근할 수** 있지만, 두 개는 **JavaScript 포인터를 교환하지 않**아 JavaScript 기능의 유출을 방지합니다.
## **`manifest.json`**
Chrome 확장은 단순히 [.crx 파일 확장자](https://www.lifewire.com/crx-file-2620391)를 가진 ZIP 폴더입니다. 확장의 코어는 폴더의 루트에 있는 **`manifest.json`** 파일로, 레이아웃, 권한 및 기타 구성 옵션을 지정합니다.
예:
```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)](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)에 임의로 접근할 수 있습니다.
```json
"content_scripts": [
{
"js": [
"script.js"
],
"matches": [
"https://example.com/*",
"https://www.example.com/*"
],
"exclude_matches": ["*://*/*business*"],
}
],
```
더 많은 URL을 포함하거나 제외하려면 **`include_globs`** 및 **`exclude_globs`**를 사용할 수도 있습니다.
이것은 페이지에 설명 버튼을 추가하는 예제 콘텐츠 스크립트로, [저장소 API](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage)를 사용하여 확장 프로그램의 저장소에서 `message` 값을 검색합니다.
```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)
})
```
<figure><img src="../../images/image (23).png" alt=""><figcaption></figcaption></figure>
이 버튼이 클릭되면 콘텐츠 스크립트를 통해 확장 페이지로 메시지가 전송됩니다. 이는 [**runtime.sendMessage() API**](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/sendMessage)를 활용한 것입니다. 콘텐츠 스크립트는 API에 직접 접근하는 데 제한이 있으며, `storage`가 몇 가지 예외 중 하나입니다. 이러한 예외를 넘어서는 기능을 위해 메시지가 확장 페이지로 전송되며, 콘텐츠 스크립트는 이와 통신할 수 있습니다.
> [!WARNING]
> 브라우저에 따라 콘텐츠 스크립트의 기능이 약간 다를 수 있습니다. Chromium 기반 브라우저의 경우 기능 목록은 [Chrome Developers documentation](https://developer.chrome.com/docs/extensions/mv3/content_scripts/#capabilities)에서 확인할 수 있으며, Firefox의 경우 [MDN](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts#webextension_apis)이 주요 출처입니다.\
> 또한 콘텐츠 스크립트는 백그라운드 스크립트와 통신할 수 있는 능력이 있어, 작업을 수행하고 응답을 전달할 수 있다는 점도 주목할 만합니다.
Chrome에서 콘텐츠 스크립트를 보기 및 디버깅하려면 Chrome 개발자 도구 메뉴에 접근할 수 있습니다: 옵션 > 추가 도구 > 개발자 도구 또는 Ctrl + Shift + I를 눌러서 접근할 수 있습니다.
개발자 도구가 표시되면 **소스 탭**을 클릭한 후 **콘텐츠 스크립트** 탭을 클릭합니다. 이를 통해 다양한 확장에서 실행 중인 콘텐츠 스크립트를 관찰하고 실행 흐름을 추적하기 위해 중단점을 설정할 수 있습니다.
### 주입된 콘텐츠 스크립트
> [!TIP]
> **콘텐츠 스크립트는 필수적이지 않습니다.** 웹 페이지에 **동적으로** **주입**하거나 **프로그래밍 방식으로 주입**할 수 있는 스크립트를 **`tabs.executeScript`**를 통해 주입할 수 있습니다. 이는 실제로 더 **세밀한 제어**를 제공합니다.
콘텐츠 스크립트를 프로그래밍 방식으로 주입하려면 확장이 스크립트를 주입할 페이지에 대한 [호스트 권한](https://developer.chrome.com/docs/extensions/reference/permissions)을 가져야 합니다. 이러한 권한은 확장의 매니페스트 내에서 **요청**하거나 [**activeTab**](https://developer.chrome.com/docs/extensions/reference/manifest/activeTab)을 통해 임시로 확보할 수 있습니다.
#### 예시 activeTab 기반 확장
```json: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" })
```
더 많은 URL을 포함하거나 제외하려면 **`include_globs`** 및 **`exclude_globs`**를 사용할 수 있습니다.
### 콘텐츠 스크립트 `run_at`
`run_at` 필드는 **JavaScript 파일이 웹 페이지에 주입되는 시점**을 제어합니다. 선호되는 기본 값은 `"document_idle"`입니다.
가능한 값은 다음과 같습니다:
- **`document_idle`**: 가능한 경우 언제든지
- **`document_start`**: `css`의 파일이 로드된 후, 그러나 다른 DOM이 구성되거나 다른 스크립트가 실행되기 전에.
- **`document_end`**: DOM이 완료된 직후, 그러나 이미지 및 프레임과 같은 하위 리소스가 로드되기 전에.
#### `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`
콘텐츠 스크립트가 전송한 메시지는 **background page**에 의해 수신되며, 이는 확장 구성 요소를 조정하는 중앙 역할을 합니다. 특히, background page는 확장의 수명 동안 지속되며, 사용자와의 직접적인 상호작용 없이 조용히 작동합니다. 자체 Document Object Model (DOM)을 가지고 있어 복잡한 상호작용 및 상태 관리를 가능하게 합니다.
**주요 사항**:
- **Background Page 역할:** 확장의 신경 센터 역할을 하여 확장의 다양한 부분 간의 통신 및 조정을 보장합니다.
- **지속성:** 사용자에게는 보이지 않지만 확장의 기능에 필수적인 항상 존재하는 개체입니다.
- **자동 생성:** 명시적으로 정의되지 않은 경우, 브라우저는 자동으로 background page를 생성합니다. 이 자동 생성된 페이지는 확장 매니페스트에 지정된 모든 background 스크립트를 포함하여 확장의 백그라운드 작업이 원활하게 작동하도록 보장합니다.
> [!TIP]
> 명시적으로 선언되지 않은 경우 브라우저가 background page를 자동으로 생성하는 편리함은 모든 필요한 background 스크립트가 통합되고 작동하도록 보장하여 확장의 설정 프로세스를 간소화합니다.
Example background script:
```js
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request == "explain") {
chrome.tabs.create({ url: "https://example.net/explanation" })
}
})
```
메시지를 수신하기 위해 [runtime.onMessage API](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage)를 사용합니다. `"explain"` 메시지를 수신하면 [tabs API](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs)를 사용하여 새 탭에서 페이지를 엽니다.
백그라운드 스크립트를 디버깅하려면 **확장 세부정보로 이동하여 서비스 워커를 검사**할 수 있으며, 이렇게 하면 백그라운드 스크립트와 함께 개발자 도구가 열립니다:
<figure><img src="https://github.com/carlospolop/hacktricks/blob/master/pentesting-web/browser-extension-pentesting-methodology/broken-reference" alt=""><figcaption></figcaption></figure>
### 옵션 페이지 및 기타
브라우저 확장 프로그램은 다양한 종류의 페이지를 포함할 수 있습니다:
- **작업 페이지**는 **확장 아이콘**을 클릭할 때 드롭다운에 표시됩니다.
- 확장이 **새 탭에서 로드할** 페이지.
- **옵션 페이지**: 이 페이지는 클릭할 때 확장 위에 표시됩니다. 이전 매니페스트에서는 `chrome://extensions/?options=fadlhnelkbeojnebcbkacjilhnbjfjca`에서 이 페이지에 접근할 수 있었거나 다음을 클릭하여 접근할 수 있었습니다:
<figure><img src="../../images/image (24).png" alt="" width="375"><figcaption></figcaption></figure>
이 페이지는 필요에 따라 동적으로 콘텐츠를 로드하므로 백그라운드 페이지처럼 지속적이지 않다는 점에 유의하십시오. 그럼에도 불구하고 이들은 백그라운드 페이지와 특정 기능을 공유합니다:
- **콘텐츠 스크립트와의 통신:** 백그라운드 페이지와 유사하게, 이 페이지는 콘텐츠 스크립트로부터 메시지를 수신할 수 있어 확장 내 상호작용을 촉진합니다.
- **확장 전용 API에 대한 접근:** 이 페이지는 확장에 대해 정의된 권한에 따라 확장 전용 API에 대한 포괄적인 접근을 누립니다.
### `permissions` & `host_permissions`
**`permissions`** 및 **`host_permissions`**는 `manifest.json`의 항목으로, **브라우저 확장이 어떤 권한**(저장소, 위치 등)을 가지고 있는지와 **어떤 웹 페이지에서** 이를 사용할 수 있는지를 나타냅니다.
브라우저 확장이 매우 **특권적**일 수 있으므로, 악의적인 확장이나 손상된 확장은 공격자가 **민감한 정보를 훔치고 사용자에 대해 스파이할 수 있는 다양한 수단을 허용할 수 있습니다**.
이 설정이 어떻게 작동하는지 및 어떻게 남용될 수 있는지 확인하십시오:
{{#ref}}
browext-permissions-and-host_permissions.md
{{#endref}}
### `content_security_policy`
**콘텐츠 보안 정책**은 `manifest.json` 내에서도 선언할 수 있습니다. 정의된 것이 있다면, 이는 **취약할 수 있습니다**.
브라우저 확장 페이지의 기본 설정은 다소 제한적입니다:
```bash
script-src 'self'; object-src 'self';
```
CSP 및 잠재적 우회에 대한 자세한 내용은 다음을 확인하세요:
{{#ref}}
../content-security-policy-csp-bypass/
{{#endref}}
### `web_accessible_resources`
웹 페이지가 브라우저 확장의 페이지에 접근하기 위해서는, 예를 들어 `.html` 페이지, 이 페이지는 `manifest.json`의 **`web_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
```
공개 확장 프로그램에서는 **extension-id에 접근할 수 있습니다**:
<figure><img src="../../images/image (1194).png" alt="" width="375"><figcaption></figcaption></figure>
하지만, `manifest.json` 매개변수 **`use_dynamic_url`**이 사용되면, 이 **id는 동적일 수 있습니다**.
> [!TIP]
> 여기에서 페이지가 언급되더라도, **Content Security Policy** 덕분에 **ClickJacking**에 대해 **보호될 수 있습니다**. 따라서 ClickJacking 공격이 가능한지 확인하기 전에 이를 (frame-ancestors 섹션) 확인해야 합니다.
이 페이지에 접근할 수 있는 것은 이 페이지들이 **잠재적으로 ClickJacking에 취약할 수 있음을 의미합니다**:
{{#ref}}
browext-clickjacking.md
{{#endref}}
> [!TIP]
> 이러한 페이지가 확장 프로그램에 의해서만 로드되고 무작위 URL에 의해 로드되지 않도록 허용하면 ClickJacking 공격을 방지할 수 있습니다.
> [!CAUTION]
> **`web_accessible_resources`**의 페이지와 확장 프로그램의 다른 페이지도 **백그라운드 스크립트와 연락할 수 있습니다**. 따라서 이러한 페이지 중 하나가 **XSS**에 취약하다면 더 큰 취약점을 열 수 있습니다.
>
> 또한, **`web_accessible_resources`**에 표시된 페이지는 iframe 내에서만 열 수 있지만, 새 탭에서 확장 프로그램 ID를 알고 있으면 확장 프로그램의 모든 페이지에 접근할 수 있습니다. 따라서 동일한 매개변수를 악용하는 XSS가 발견되면, 페이지가 **`web_accessible_resources`**에 구성되어 있지 않더라도 악용될 수 있습니다.
### `externally_connectable`
[**docs**](https://developer.chrome.com/docs/extensions/reference/manifest/externally-connectable)에 따르면, `"externally_connectable"` 매니페스트 속성은 **어떤 확장 프로그램과 웹 페이지가** [runtime.connect](https://developer.chrome.com/docs/extensions/reference/runtime#method-connect) 및 [runtime.sendMessage](https://developer.chrome.com/docs/extensions/reference/runtime#method-sendMessage)를 통해 귀하의 확장 프로그램에 연결할 수 있는지를 선언합니다.
- **`externally_connectable`** 키가 귀하의 확장 프로그램의 매니페스트에 **선언되지 않았거나** **`"ids": ["*"]`**로 선언된 경우, **모든 확장 프로그램이 연결할 수 있지만 웹 페이지는 연결할 수 없습니다**.
- **특정 ID가 지정된 경우**, 예를 들어 `"ids": ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]`와 같이, **오직 해당 애플리케이션만** 연결할 수 있습니다.
- **matches**가 지정된 경우, 해당 웹 앱은 연결할 수 있습니다:
```json
"matches": [
"https://*.google.com/*",
"*://*.chromium.org/*",
```
- 비어 있는 것으로 지정된 경우: **`"externally_connectable": {}`**, 어떤 앱이나 웹도 연결할 수 없습니다.
여기에서 **확장 프로그램과 URL**이 적을수록, **공격 표면**이 더 작아집니다.
> [!CAUTION]
> 만약 **`externally_connectable`**에 **XSS 또는 탈취에 취약한** 웹 페이지가 표시되면, 공격자는 **배경 스크립트에 직접 메시지를 보낼 수** 있어, Content Script와 그 CSP를 완전히 우회할 수 있습니다.
>
> 따라서, 이는 **매우 강력한 우회**입니다.
>
> 게다가, 클라이언트가 악성 확장 프로그램을 설치하면, 취약한 확장 프로그램과 통신할 수 없더라도, **허용된 웹 페이지에 XSS 데이터를 주입**하거나 **`WebRequest`** 또는 **`DeclarativeNetRequest`** API를 악용하여 특정 도메인에서 요청을 조작하고 **JavaScript 파일**에 대한 페이지 요청을 변경할 수 있습니다. (대상 페이지의 CSP가 이러한 공격을 방지할 수 있습니다). 이 아이디어는 [**이 글**](https://www.darkrelay.com/post/opera-zero-day-rce-vulnerability)에서 나왔습니다.
## 통신 요약
### 확장 프로그램 <--> 웹앱
콘텐츠 스크립트와 웹 페이지 간의 통신을 위해 일반적으로 포스트 메시지가 사용됩니다. 따라서 웹 애플리케이션에서는 일반적으로 **`window.postMessage`** 함수 호출을 찾을 수 있으며, 콘텐츠 스크립트에서는 **`window.addEventListener`**와 같은 리스너를 찾을 수 있습니다. 그러나 확장 프로그램이 **Post Message를 보내 웹 애플리케이션과 통신**할 수도 있으므로 웹은 이를 예상해야 하며, 단순히 웹이 새로운 스크립트를 로드하게 할 수도 있습니다.
### 확장 프로그램 내부
일반적으로 **`chrome.runtime.sendMessage`** 함수가 확장 프로그램 내에서 메시지를 보내는 데 사용되며(일반적으로 `background` 스크립트에서 처리됨), 이를 수신하고 처리하기 위해 **`chrome.runtime.onMessage.addListener`**를 호출하는 리스너가 선언됩니다.
단일 메시지를 보내는 대신 지속적인 연결을 위해 **`chrome.runtime.connect()`**를 사용할 수도 있으며, 다음 예제와 같이 **메시지를 보내고** **받는** 데 사용할 수 있습니다:
<details>
<summary><code>chrome.runtime.connect()</code> 예제</summary>
```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와 같은 치명적인 취약점에 노출될 수 있습니다**. [자세한 내용은 나중에](./#native-messaging) 확인하세요.
```javascript
chrome.runtime.sendNativeMessage(
"com.my_company.my_application",
{ text: "Hello" },
function (response) {
console.log("Received " + response)
}
)
```
## 웹 **↔︎** 콘텐츠 스크립트 통신
**콘텐츠 스크립트**가 작동하는 환경과 호스트 페이지가 존재하는 환경은 **분리**되어 있어 **격리**를 보장합니다. 이러한 격리에도 불구하고, 두 환경 모두 페이지의 **문서 객체 모델(DOM)**과 상호작용할 수 있는 능력을 가지고 있는 공유 자원입니다. 호스트 페이지가 **콘텐츠 스크립트**와 통신하거나 콘텐츠 스크립트를 통해 확장과 간접적으로 통신하기 위해서는, 두 당사자가 접근할 수 있는 **DOM**을 통신 채널로 활용해야 합니다.
### 포스트 메시지
```javascript: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
)
```
```javascript: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 우회**를 확인하십시오:
{{#ref}}
../postmessage-vulnerabilities/
{{#endref}}
### Iframe
또 다른 가능한 통신 방법은 **Iframe URLs**를 통해 이루어질 수 있으며, 예시는 다음에서 찾을 수 있습니다:
{{#ref}}
browext-xss-example.md
{{#endref}}
### DOM
이것은 "정확히" 통신 방법은 아니지만, **웹과 콘텐츠 스크립트는 웹 DOM에 접근할 수 있습니다**. 따라서 **콘텐츠 스크립트**가 그로부터 정보를 읽고 **웹 DOM을 신뢰하는 경우**, 웹은 이 데이터를 **수정할 수 있습니다** (웹을 신뢰해서는 안 되거나, 웹이 XSS에 취약하기 때문에) 그리고 **콘텐츠 스크립트를 손상시킬 수 있습니다**.
**브라우저 확장을 손상시키기 위한 DOM 기반 XSS의 예시**를 다음에서 찾을 수 있습니다:
{{#ref}}
browext-xss-example.md
{{#endref}}
## 콘텐츠 스크립트 **↔︎** 백그라운드 스크립트 통신
콘텐츠 스크립트는 [**runtime.sendMessage()**](https://developer.chrome.com/docs/extensions/reference/runtime#method-sendMessage) **또는** [**tabs.sendMessage()**](https://developer.chrome.com/docs/extensions/reference/tabs#method-sendMessage) 함수를 사용하여 **일회성 JSON 직렬화 가능** 메시지를 보낼 수 있습니다.
**응답**을 처리하려면 반환된 **Promise**를 사용하십시오. 그러나 이전 호환성을 위해 마지막 인수로 **콜백**을 여전히 전달할 수 있습니다.
**콘텐츠 스크립트**에서 요청을 보내는 모습은 다음과 같습니다:
```javascript
;(async () => {
const response = await chrome.runtime.sendMessage({ greeting: "hello" })
// do something with response here, not outside the function
console.log(response)
})()
```
**확장**에서 요청 보내기 (보통 **백그라운드 스크립트**). 선택한 탭의 콘텐츠 스크립트에 메시지를 보내는 방법의 예:
```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**](https://developer.chrome.com/docs/extensions/reference/runtime#event-onMessage) **이벤트 리스너**를 설정해야 합니다. 이는 콘텐츠 스크립트나 확장 페이지에서 동일하게 보입니다.
```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()`를 실행하는 첫 번째 페이지**만이 응답을 효과적으로 전달할 수 있다는 것입니다. 동일한 이벤트에 대한 후속 응답은 고려되지 않습니다.
새로운 확장을 만들 때는 콜백보다 프로미스를 선호해야 합니다. 콜백 사용과 관련하여, `sendResponse()` 함수는 동기 컨텍스트 내에서 직접 실행되거나 이벤트 핸들러가 `true`를 반환하여 비동기 작업을 나타내는 경우에만 유효하다고 간주됩니다. 핸들러 중 어느 것도 `true`를 반환하지 않거나 `sendResponse()` 함수가 메모리에서 제거(가비지 컬렉션)되면, `sendMessage()` 함수와 연결된 콜백이 기본적으로 트리거됩니다.
## Native Messaging
브라우저 확장은 **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()`](https://developer.chrome.com/docs/extensions/reference/api/runtime#method-connectNative) 또는 [`runtime.sendNativeMessage()`](https://developer.chrome.com/docs/extensions/reference/api/runtime#method-sendNativeMessage)에 전달되는 문자열로, 브라우저 확장의 백그라운드 스크립트에서 애플리케이션과 통신하는 데 사용됩니다. `path`는 바이너리의 경로이며, 유효한 `type`은 stdio(표준 입력 및 표준 출력 사용) 하나뿐이며, `allowed_origins`는 접근할 수 있는 확장을 나타냅니다(와일드카드를 사용할 수 없음).
Chrome/Chromium은 이 json을 일부 Windows 레지스트리와 macOS 및 Linux의 일부 경로에서 검색합니다(자세한 정보는 [**docs**](https://developer.chrome.com/docs/extensions/develop/concepts/native-messaging)에서 확인할 수 있습니다).
> [!TIP]
> 브라우저 확장은 이 통신을 사용하기 위해 `nativeMessaing` 권한이 선언되어야 합니다.
다음은 네이티브 애플리케이션에 메시지를 보내는 일부 백그라운드 스크립트 코드의 모습입니다:
```javascript
chrome.runtime.sendNativeMessage(
"com.my_company.my_application",
{ text: "Hello" },
function (response) {
console.log("Received " + response)
}
)
```
[**이 블로그 게시물**](https://spaceraccoon.dev/universal-code-execution-browser-extensions/)에서는 네이티브 메시지를 악용하는 취약한 패턴이 제안됩니다:
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. 다음 명령을 실행합니다:
```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 뷰어 웹사이트 사용
[https://robwu.nl/crxviewer/](https://robwu.nl/crxviewer/)
### CRX 뷰어 확장 프로그램 사용
또 다른 편리한 방법은 오픈 소스 프로젝트인 Chrome Extension Source Viewer를 사용하는 것입니다. [Chrome 웹 스토어](https://chrome.google.com/webstore/detail/chrome-extension-source-v/jifpbeccnghkjeaalbbjmodiffmgedin?hl=en)에서 설치할 수 있습니다. 뷰어의 소스 코드는 [GitHub 저장소](https://github.com/Rob--W/crxviewer)에서 확인할 수 있습니다.
### 로컬에 설치된 확장 프로그램의 소스 보기
로컬에 설치된 Chrome 확장 프로그램도 검사할 수 있습니다. 방법은 다음과 같습니다:
1. `chrome://version/`를 방문하여 "Profile Path" 필드를 찾아 Chrome 로컬 프로필 디렉토리에 접근합니다.
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](https://github.com/palant/chrome-extension-manifests-dataset)를 사용하고 그들의 매니페스트 파일에서 잠재적으로 취약한 징후를 확인할 수 있습니다. 예를 들어, 25000명 이상의 사용자가 있는 확장 프로그램, `content_scripts` 및 권한 `nativeMessaging`을 확인하려면:
```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')"
```
## 보안 감사 체크리스트
브라우저 확장 프로그램은 **제한된 공격 표면**을 가지고 있지만, 일부는 **취약점**이나 **잠재적인 강화 개선**을 포함할 수 있습니다. 다음은 가장 일반적인 항목입니다:
- [ ] 요청된 **`permissions`**를 가능한 한 많이 **제한**합니다.
- [ ] **`host_permissions`**를 가능한 한 많이 **제한**합니다.
- [ ] **강력한** **`content_security_policy`**를 사용합니다.
- [ ] 필요하지 않다면 **`externally_connectable`**를 가능한 한 많이 **제한**하고, 기본값으로 두지 말고 **`{}`**로 지정합니다.
- [ ] 여기에서 **XSS 또는 인수**에 취약한 **URL**이 언급되면, 공격자는 **백그라운드 스크립트에 직접 메시지를 보낼 수 있습니다**. 매우 강력한 우회입니다.
- [ ] **`web_accessible_resources`**를 가능한 한 많이 **제한**합니다. 가능하다면 비워두세요.
- [ ] **`web_accessible_resources`**가 없지 않다면, [**ClickJacking**](browext-clickjacking.md)을 확인합니다.
- [ ] **확장 프로그램**에서 **웹 페이지**로 **통신**이 발생하면, **통신에서 발생한 XSS** [**취약점**](browext-xss-example.md)을 **확인합니다**.
- [ ] Post Messages가 사용된다면, [**Post Message 취약점**](../postmessage-vulnerabilities/)**을 확인합니다.**
- [ ] **Content Script가 DOM 세부정보에 접근**하는 경우, 웹에 의해 **수정**될 때 **XSS를 도입하지 않는지** 확인합니다.
- [ ] 이 통신이 **Content Script -> Background script communication**에 관련되어 있다면 특별히 강조합니다.
- [ ] 백그라운드 스크립트가 **native messaging**을 통해 통신하는 경우, 통신이 안전하고 정제되었는지 확인합니다.
- [ ] **민감한 정보는** 브라우저 확장 프로그램 **코드** 내에 저장되어서는 안 됩니다.
- [ ] **민감한 정보는** 브라우저 확장 프로그램 **메모리** 내에 저장되어서는 안 됩니다.
- [ ] **민감한 정보는** **파일 시스템에 보호되지 않은 상태로 저장되어서는 안 됩니다.**
## 브라우저 확장 프로그램 위험
- 앱 [https://crxaminer.tech/](https://crxaminer.tech/)는 브라우저 확장 프로그램이 요청하는 권한과 같은 데이터를 분석하여 브라우저 확장 프로그램 사용의 위험 수준을 제공합니다.
## 도구
### [**Tarnish**](https://thehackerblog.com/tarnish/)
- 제공된 Chrome 웹스토어 링크에서 Chrome 확장을 가져옵니다.
- [**manifest.json**](https://developer.chrome.com/extensions/manifest) **뷰어**: 확장의 매니페스트의 JSON 예쁘게 정리된 버전을 간단히 표시합니다.
- **지문 분석**: [web_accessible_resources](https://developer.chrome.com/extensions/manifest/web_accessible_resources)의 감지 및 Chrome 확장 지문 생성 JavaScript의 자동 생성.
- **잠재적 Clickjacking 분석**: [web_accessible_resources](https://developer.chrome.com/extensions/manifest/web_accessible_resources) 지시어가 설정된 확장 HTML 페이지의 감지. 이러한 페이지의 목적에 따라 Clickjacking에 취약할 수 있습니다.
- **권한 경고 뷰어**: 사용자가 확장을 설치하려고 할 때 표시될 모든 Chrome 권한 프롬프트 경고 목록을 보여줍니다.
- **위험한 함수**: 공격자가 잠재적으로 악용할 수 있는 위험한 함수의 위치를 보여줍니다(예: innerHTML, chrome.tabs.executeScript와 같은 함수).
- **진입점**: 확장이 사용자/외부 입력을 받는 위치를 보여줍니다. 이는 확장의 표면적을 이해하고 악의적으로 조작된 데이터를 확장으로 보낼 수 있는 잠재적 지점을 찾는 데 유용합니다.
- 위험한 함수 및 진입점 스캐너는 생성된 경고에 대해 다음을 포함합니다:
- 경고를 유발한 관련 코드 스니펫 및 줄.
- 문제에 대한 설명.
- 코드를 포함하는 전체 소스 파일을 보기 위한 “파일 보기” 버튼.
- 경고된 파일의 경로.
- 경고된 파일의 전체 Chrome 확장 URI.
- 파일의 유형(예: Background Page 스크립트, Content Script, Browser Action 등).
- 취약한 줄이 JavaScript 파일에 있는 경우, 포함된 모든 페이지의 경로와 이 페이지의 유형 및 [web_accessible_resource](https://developer.chrome.com/extensions/manifest/web_accessible_resources) 상태.
- **Content Security Policy (CSP) 분석기 및 우회 검사기**: 확장의 CSP의 약점을 지적하고 화이트리스트된 CDN 등으로 인해 CSP를 우회할 수 있는 잠재적인 방법을 밝혀냅니다.
- **알려진 취약한 라이브러리**: [Retire.js](https://retirejs.github.io/retire.js/)를 사용하여 알려진 취약한 JavaScript 라이브러리의 사용 여부를 확인합니다.
- 확장 프로그램 및 형식화된 버전 다운로드.
- 원본 확장 프로그램 다운로드.
- 확장의 아름답게 정리된 버전 다운로드(자동으로 예쁘게 정리된 HTML 및 JavaScript).
- 스캔 결과의 자동 캐싱, 확장 스캔을 처음 실행할 때는 상당한 시간이 소요됩니다. 그러나 두 번째 실행 시 확장이 업데이트되지 않았다면 결과가 캐시되어 거의 즉시 완료됩니다.
- 링크 가능한 보고서 URL, 다른 사람에게 tarnish에서 생성된 확장 보고서에 쉽게 링크할 수 있습니다.
### [Neto](https://github.com/elevenpaths/neto)
Neto 프로젝트는 Firefox 및 Chrome과 같은 잘 알려진 브라우저의 브라우저 플러그인 및 확장의 숨겨진 기능을 분석하고 풀어내기 위해 고안된 Python 3 패키지입니다. `manifest.json`, 지역화 폴더 또는 JavaScript 및 HTML 소스 파일과 같은 관련 리소스에서 이러한 기능을 추출하기 위해 패키지 파일을 압축 해제하는 프로세스를 자동화합니다.
## 참고 문헌
- **이 방법론에 대한 도움을 주신** [**@naivenom**](https://twitter.com/naivenom) **에게 감사드립니다.**
- [https://www.cobalt.io/blog/introduction-to-chrome-browser-extension-security-testing](https://www.cobalt.io/blog/introduction-to-chrome-browser-extension-security-testing)
- [https://palant.info/2022/08/10/anatomy-of-a-basic-extension/](https://palant.info/2022/08/10/anatomy-of-a-basic-extension/)
- [https://palant.info/2022/08/24/attack-surface-of-extension-pages/](https://palant.info/2022/08/24/attack-surface-of-extension-pages/)
- [https://palant.info/2022/08/31/when-extension-pages-are-web-accessible/](https://palant.info/2022/08/31/when-extension-pages-are-web-accessible/)
- [https://help.passbolt.com/assets/files/PBL-02-report.pdf](https://help.passbolt.com/assets/files/PBL-02-report.pdf)
- [https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts](https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts)
- [https://developer.chrome.com/docs/extensions/mv2/background-pages](https://developer.chrome.com/docs/extensions/mv2/background-pages)
- [https://thehackerblog.com/kicking-the-rims-a-guide-for-securely-writing-and-auditing-chrome-extensions/](https://thehackerblog.com/kicking-the-rims-a-guide-for-securely-writing-and-auditing-chrome-extensions/)
- [https://gist.github.com/LongJohnCoder/9ddf5735df3a4f2e9559665fb864eac0](https://gist.github.com/LongJohnCoder/9ddf5735df3a4f2e9559665fb864eac0)
{{#include ../../banners/hacktricks-training.md}}