Metodologia Pentesting rozszerzeń przeglądarki

Tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks

Podstawowe informacje

Rozszerzenia przeglądarki są napisane w JavaScript i ładowane przez przeglądarkę w tle. Mają swój DOM, ale mogą wchodzić w interakcję z DOM innych stron. Oznacza to, że mogą naruszyć poufność, integralność i dostępność innych stron (CIA).

Główne komponenty

Układ rozszerzenia najlepiej przedstawić graficznie i składa się z trzech komponentów. Przyjrzyjmy się każdemu z nich szczegółowo.

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

Content Scripts

Każdy content script ma bezpośredni dostęp do DOM jednej strony internetowej i w związku z tym jest narażony na potencjalnie złośliwe dane wejściowe. Jednak content script nie ma żadnych uprawnień poza możliwością wysyłania wiadomości do extension core.

Extension Core

Extension core zawiera większość uprawnień/dostępu rozszerzenia, ale extension core może wchodzić w interakcję z zawartością webową tylko za pośrednictwem XMLHttpRequest i content scripts. Ponadto extension core nie ma bezpośredniego dostępu do maszyny hosta.

Native Binary

Rozszerzenie może zawierać native binary, który może uzyskać dostęp do maszyny hosta z pełnymi uprawnieniami użytkownika. Native binary komunikuje się z extension core poprzez standardowe Netscape Plugin Application Programming Interface (NPAPI) używane przez Flash i inne wtyczki przeglądarki.

Granice

Caution

Aby uzyskać pełne uprawnienia użytkownika, atakujący musi przekonać rozszerzenie do przekazania złośliwych danych wejściowych z content script do extension core, a następnie z extension core do native binary.

Każdy komponent rozszerzenia jest oddzielony od pozostałych przez silne granice ochronne. Każdy komponent działa w oddzielnym procesie systemu operacyjnego. Content scripts i extension cores uruchamiane są w procesach sandbox niedostępnych dla większości usług systemu operacyjnego.

Ponadto content scripts są oddzielone od powiązanych stron poprzez uruchamianie w oddzielnym JavaScript heap. Content script i strona internetowa mają dostęp do tego samego podstawowego DOM, ale obie strony nigdy nie wymieniają wskaźników JavaScript, co zapobiega leakingowi funkcjonalności JavaScript.

manifest.json

Rozszerzenie Chrome to po prostu folder ZIP z rozszerzeniem pliku .crx file extension. Rdzeniem rozszerzenia jest plik manifest.json w katalogu głównym folderu, który określa układ, uprawnienia i inne opcje konfiguracyjne.

Przykład:

{
"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 są ładowane za każdym razem, gdy użytkownik przechodzi na pasującą stronę, w naszym przypadku każda strona pasująca do wyrażenia https://example.com/* i niepasująca do regexu *://*/*/business*. Wykonują się tak jak skrypty samej strony i mają pełny dostęp do strony Document Object Model (DOM).

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

Aby uwzględnić lub wykluczyć więcej adresów URL, można także użyć include_globs i exclude_globs.

To przykładowy content script, który doda do strony przycisk ‘explain’, używając the storage API do pobrania wartości message z pamięci rozszerzenia.

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

Po kliknięciu tego przycisku content script wysyła wiadomość do stron rozszerzenia przy użyciu runtime.sendMessage() API. Wynika to z ograniczeń content scriptów w bezpośrednim dostępie do API, przy czym storage jest jedną z nielicznych wyjątków. Dla funkcjonalności wykraczających poza te wyjątki, wiadomości są wysyłane do stron rozszerzenia, z którymi content scripty mogą się komunikować.

Warning

W zależności od przeglądarki możliwości content scriptu mogą się nieznacznie różnić. Dla przeglądarek opartych na Chromium lista możliwości jest dostępna w Chrome Developers documentation, a dla Firefox źródłem podstawowym jest MDN.
Warto również zauważyć, że content scripty potrafią komunikować się ze skryptami background, co pozwala im wykonywać akcje i przekazywać odpowiedzi z powrotem.

Aby przeglądać i debugować content scripty w Chrome, menu narzędzi deweloperskich Chrome można otworzyć z Options > More tools > Developer tools LUB skrótem Ctrl + Shift + I.

Po wyświetleniu narzędzi deweloperskich należy kliknąć zakładkę Source, a następnie zakładkę Content Scripts. Umożliwia to obserwowanie uruchomionych content scriptów z różnych rozszerzeń oraz ustawianie breakpointów w celu śledzenia przebiegu wykonania.

Injected content scripts

Tip

Zwróć uwagę, że Content Scripts nie są obowiązkowe, ponieważ możliwe jest również dynamiczne wstrzykiwanie skryptów oraz programatyczne ich wstrzykiwanie na stronach za pomocą tabs.executeScript. Daje to w rzeczywistości bardziej precyzyjną kontrolę.

Do programatycznego wstrzyknięcia content scriptu rozszerzenie musi mieć host permissions dla strony, na którą skrypty mają być wstrzyknięte. Uprawnienia te mogą być uzyskane albo przez zadeklarowanie ich w manifeście rozszerzenia, albo tymczasowo poprzez activeTab.

Przykład rozszerzenia wykorzystującego activeTab-based extension

{
"name": "My extension",
...
"permissions": [
"activeTab",
"scripting"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_title": "Action Button"
}
}
  • Wstrzyknij plik JS po kliknięciu:
// 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"],
})
})
  • Inject a function po kliknięciu:
//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,
})
})

Przykład z uprawnieniami do uruchamiania skryptów

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

Aby uwzględnić lub wykluczyć więcej adresów URL, można także użyć include_globs i exclude_globs.

Content Scripts run_at

Pole run_at kontroluje kiedy pliki JavaScript są wstrzykiwane na stronę. Preferowaną i domyślną wartością jest "document_idle".

Możliwe wartości to:

  • document_idle: Gdy tylko to możliwe
  • document_start: Po wszelkich plikach z css, ale zanim zostanie skonstruowany DOM lub zanim zostanie uruchomiony jakikolwiek inny skrypt.
  • document_end: Bezpośrednio po ukończeniu DOM, ale zanim załadują się subresources takie jak obrazy i ramki.

Za pomocą manifest.json

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

Za pomocą service-worker.js

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

background

Wiadomości wysyłane przez content scripts są odbierane przez background page, która pełni centralną rolę w koordynowaniu komponentów rozszerzenia. Co ważne, background page utrzymuje się przez cały czas działania rozszerzenia, działając dyskretnie bez bezpośredniej interakcji z użytkownikiem. Posiada własny Document Object Model (DOM), co umożliwia złożone interakcje i zarządzanie stanem.

Kluczowe punkty:

  • Background Page Role: Działa jako centrum nerwowe rozszerzenia, zapewniając komunikację i koordynację między różnymi częściami rozszerzenia.
  • Persistence: Jest bytem obecnym przez cały czas działania, niewidocznym dla użytkownika, ale integralnym dla funkcjonalności rozszerzenia.
  • Automatic Generation: Jeśli nie jest jawnie zdefiniowana, przeglądarka automatycznie utworzy background page. Ta auto-wygenerowana strona będzie zawierać wszystkie background scripts określone w manifest, zapewniając płynne działanie zadań w tle rozszerzenia.

Tip

Wygoda zapewniona przez przeglądarkę poprzez automatyczne generowanie background page (gdy nie jest jawnie zadeklarowana) gwarantuje, że wszystkie niezbędne background scripts są zintegrowane i działające, upraszczając proces konfiguracji rozszerzenia.

Example background script:

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

Używa runtime.onMessage API do nasłuchiwania wiadomości. Gdy otrzymana zostanie wiadomość "explain", używa tabs API aby otworzyć stronę w nowej karcie.

Aby debugować skrypt w tle możesz przejść do extension details and inspect the service worker, to otworzy narzędzia deweloperskie z skryptem w tle:

Options pages and other

Rozszerzenia przeglądarki mogą zawierać różne rodzaje stron:

  • Action pages są wyświetlane w drop-down when the extension icon po kliknięciu.
  • Strony, które rozszerzenie będzie load in a new tab.
  • Option Pages: Ta strona wyświetla się nad rozszerzeniem po kliknięciu. W poprzednim manifeście w moim przypadku mogłem uzyskać dostęp do tej strony przez chrome://extensions/?options=fadlhnelkbeojnebcbkacjilhnbjfjca lub klikając:

Zauważ, że te strony nie są trwałe jak background pages, ponieważ ładują dynamicznie zawartość w razie potrzeby. Mimo to dzielą pewne możliwości ze stroną w tle:

  • Communication with Content Scripts: Podobnie jak strona w tle, te strony mogą odbierać wiadomości od content scripts, ułatwiając interakcję w ramach rozszerzenia.
  • Access to Extension-Specific APIs: Te strony mają pełny dostęp do extension-specific APIs, z zastrzeżeniem uprawnień zdefiniowanych dla rozszerzenia.

permissions & host_permissions

permissions i host_permissions to wpisy w manifest.json, które wskażą, jakie uprawnienia ma rozszerzenie przeglądarki (storage, location…) oraz na których stronach.

Ponieważ rozszerzenia przeglądarki mogą być tak privileged, złośliwe lub przejęte rozszerzenie może pozwolić atakującemu na różne sposoby kradzieży wrażliwych informacji i szpiegowanie użytkownika.

Sprawdź, jak te ustawienia działają i jak można je nadużyć w:

BrowExt - permissions & host_permissions

content_security_policy

A content security policy można zadeklarować również w manifest.json. Jeśli jest zdefiniowana, może być vulnerable.

Domyślne ustawienie dla stron rozszerzeń przeglądarki jest raczej restrykcyjne:

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

Aby uzyskać więcej informacji o CSP i możliwych obejściach, zobacz:

Content Security Policy (CSP) Bypass

web_accessible_resources

Aby strona internetowa mogła uzyskać dostęp do strony rozszerzenia przeglądarki, na przykład pliku .html, strona ta musi być wymieniona w polu web_accessible_resources w manifest.json.
Na przykład:

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

Te strony są dostępne pod adresem URL takim jak:

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

W publicznych rozszerzeniach extension-id jest dostępny:

Jednak jeśli parametr manifest.json use_dynamic_url jest użyty, to id może być dynamiczne.

Tip

Zwróć uwagę, że nawet jeśli strona jest tu wymieniona, może być chroniona przed ClickJacking dzięki Content Security Policy. Dlatego musisz ją również sprawdzić (sekcja frame-ancestors), zanim potwierdzisz, że atak ClickJacking jest możliwy.

Pozwolenie na dostęp do tych stron czyni je potencjalnie podatnymi na ClickJacking:

BrowExt - ClickJacking

Tip

Pozwolenie, aby te strony były ładowane tylko przez rozszerzenie, a nie przez losowe URL-e, może zapobiec atakom ClickJacking.

Caution

Zauważ, że strony z web_accessible_resources oraz inne strony rozszerzenia są również zdolne do contacting background scripts. Jeśli któraś z tych stron będzie podatna na XSS, może to otworzyć poważniejszą lukę.

Ponadto pamiętaj, że w iframe można otwierać tylko strony wskazane w web_accessible_resources, ale z nowej karty możliwe jest uzyskanie dostępu do dowolnej strony rozszerzenia znając extension ID. Dlatego, jeśli zostanie znaleziony XSS wykorzystujący te same parametry, może być nadużyty nawet jeśli strona nie jest skonfigurowana w web_accessible_resources.

externally_connectable

Zgodnie z docs, właściwość manifestu "externally_connectable" deklaruje które rozszerzenia i strony WWW mogą łączyć się z twoim rozszerzeniem za pomocą runtime.connect i runtime.sendMessage.

  • Jeśli klucz externally_connectable nie jest zadeklarowany w manifeście twojego rozszerzenia lub zadeklarowano go jako "ids": ["*"], wszystkie rozszerzenia mogą się łączyć, ale żadne strony WWW nie mogą się łączyć.
  • Jeśli określone są konkretne ID, np. w "ids": ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"], tylko te aplikacje mogą się łączyć.
  • Jeśli określone są matches, te aplikacje webowe będą mogły się połączyć:
"matches": [
"https://*.google.com/*",
"*://*.chromium.org/*",
  • If it’s specified as empty: "externally_connectable": {}, no app or web will be able to connect.

The less extensions and URLs indicated here, the smaller the attack surface will be.

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.

Podsumowanie komunikacji

Extension <–> WebApp

To communicate between the content script and the web page post messages are usually used. Therefore, in the web application you will usually find calls to the function window.postMessage and in the content script listeners like window.addEventListener. Note however, that the extension could also communicate with the web application sending a Post Message (and therefore the web should expect it) or just make the web load a new script.

Inside the extension

Usually the function chrome.runtime.sendMessage is used to send a message inside the extension (usually handled by the background script) and in order to receive and handle it a listener is declared calling chrome.runtime.onMessage.addListener.

It’s also possible to use chrome.runtime.connect() to have a persistent connection instead of sending single messages, it’s possible to use it to send and receive messages like in the following example:

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

</details>

Można również wysyłać wiadomości ze skryptu w tle do skryptu zawartości znajdującego się w określonej karcie, wywołując **`chrome.tabs.sendMessage`**, gdzie trzeba podać **ID karty**, do której ma być wysłana wiadomość.

### Z dozwolonych `externally_connectable` do rozszerzenia

**Aplikacje webowe i zewnętrzne rozszerzenia przeglądarki dozwolone** w konfiguracji `externally_connectable` mogą wysyłać żądania używając :
```javascript
chrome.runtime.sendMessage(extensionId, ...

Gdzie trzeba podać extension ID.

Native Messaging

Możliwe jest, że background scripts będą komunikować się z binaries w systemie, co może prowadzić do krytycznych podatności, takich jak RCEs, jeśli ta komunikacja nie jest odpowiednio zabezpieczona. More on this later.

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

Komunikacja Web ↔︎ Content Script Communication

Środowiska, w których działają content scripts, oraz te, w których istnieją strony hosta, są od siebie oddzielone, zapewniając izolację. Pomimo tej izolacji obie strony mają możliwość interakcji z Document Object Model (DOM) strony, wspólnym zasobem. Aby strona hosta mogła prowadzić komunikację z content script, lub pośrednio z rozszerzeniem przez content script, konieczne jest wykorzystanie DOM dostępnego dla obu stron jako kanału komunikacji.

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
)

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

  • event.isTrusted: This is True only if the event was triggered by a users action
  • The content script might expecting a message only if the user performs some action
  • origin domain: might expecting a message only allowlist of domains.
  • If a regex is used, be very careful
  • Source: received_message.source !== window can be used to check if the message was from the same window where the Content Script is listening.

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:

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

Wysyłanie żądania z extension (zwykle z background script). Przykład, jak wysłać wiadomość do content script w wybranym tab:

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

Po stronie odbiorczej musisz zarejestrować runtime.onMessage nasłuch zdarzeń, aby obsłużyć wiadomość. Wygląda to tak samo w content script lub na 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" })
})

W wyróżnionym przykładzie sendResponse() zostało wykonane synchronicznie. Aby zmodyfikować obsługę zdarzenia onMessage tak, by sendResponse() było wykonywane asynchronicznie, konieczne jest dodanie return true;.

Ważne jest, że w scenariuszach, gdzie wiele stron ma otrzymywać zdarzenia onMessage, pierwsza strona, która wykona sendResponse() dla danego zdarzenia będzie jedyną, która faktycznie dostarczy odpowiedź. Jakiekolwiek późniejsze odpowiedzi na to samo zdarzenie nie zostaną uwzględnione.

Podczas tworzenia nowych rozszerzeń należy preferować promises zamiast callbacks. Jeśli chodzi o stosowanie callbacks, funkcja sendResponse() jest uznawana za ważną tylko wtedy, gdy zostanie wykonana bezpośrednio w kontekście synchronicznym, lub gdy handler zdarzenia sygnalizuje operację asynchroniczną przez zwrócenie true. Jeśli żaden z handlerów nie zwróci true lub jeśli funkcja sendResponse() zostanie usunięta z pamięci (garbage-collected), to domyślnie wywołany zostanie callback powiązany z funkcją sendMessage().

Native Messaging

Rozszerzenia przeglądarki umożliwiają także komunikację z binaries in the system via stdin. Aplikacja musi zainstalować plik json wskazujący to, w pliku json podobnym do:

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

Gdzie name jest stringiem przekazywanym do runtime.connectNative() lub runtime.sendNativeMessage() w celu komunikacji z aplikacją z background scripts rozszerzenia przeglądarki. path to ścieżka do binarki, jest tylko 1 poprawny type, którym jest stdio (używa stdin i stdout), a allowed_origins wskazują rozszerzenia, które mogą mieć do niego dostęp (i nie mogą zawierać wildcard).

Chrome/Chromium będzie szukać tego json w niektórych wpisach rejestru Windows i w niektórych ścieżkach na macOS i Linux (więcej informacji w docs).

Tip

Rozszerzenie przeglądarki musi również zadeklarować uprawnienie nativeMessaing, aby móc korzystać z tej komunikacji.

Poniżej przykład kodu background script wysyłającego wiadomości do native application:

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

W this blog post, zaproponowano podatny wzorzec wykorzystujący native messages:

  1. Browser extension ma wzorzec wildcard dla content script.
  2. Content script przekazuje wiadomości postMessage do background script używając sendMessage.
  3. Background script przekazuje wiadomość do native application używając sendNativeMessage.
  4. Native application obsługuje wiadomość w niebezpieczny sposób, prowadząc do code execution.

W nim znajduje się przykład jak z dowolnej strony osiągnąć RCE, wykorzystując browser extension.

Wrażliwe informacje w pamięci/kodzie/schowku

Jeśli Browser Extension przechowuje w swojej pamięci wrażliwe informacje, mogą one zostać zrzucane (szczególnie na maszynach Windows) i przeszukane w celu ich odnalezienia.

Dlatego pamięć Browser Extension nie powinna być uważana za bezpieczną, a wrażliwe informacje takie jak poświadczenia czy frazy mnemoniczne nie powinny być w niej przechowywane.

Oczywiście, nie umieszczaj wrażliwych informacji w kodzie, ponieważ będą one publiczne.

Aby zrzucić pamięć z przeglądarki możesz zrzucić pamięć procesu lub przejść do settings rozszerzenia, kliknąć Inspect pop-up -> w sekcji Memory -> Take a snaphost i użyć CTRL+F aby przeszukać snapshot w poszukiwaniu wrażliwych informacji.

Dodatkowo, wysoce wrażliwe informacje, takie jak klucze mnemoniczne lub hasła, nie powinny być kopiowane do schowka (lub przynajmniej usuń je ze schowka po kilku sekundach), ponieważ procesy monitorujące schowek będą w stanie je przechwycić.

Ładowanie rozszerzenia w przeglądarce

  1. Download the Browser Extension & unzipped
  2. Przejdź do chrome://extensions/ i włącz Developer Mode
  3. Kliknij przycisk Load unpacked

W Firefox przejdź do about:debugging#/runtime/this-firefox i kliknij przycisk Load Temporary Add-on.

Getting the source code from the store

Kod źródłowy rozszerzenia Chrome można uzyskać różnymi metodami. Poniżej znajdują się szczegółowe wyjaśnienia i instrukcje dla każdej opcji.

Download Extension as ZIP via Command Line

Kod źródłowy rozszerzenia Chrome można pobrać jako plik ZIP z użyciem wiersza poleceń. Polega to na użyciu curl do pobrania pliku ZIP z określonego URL, a następnie rozpakowaniu zawartości ZIP do katalogu. Oto kroki:

  1. Zamień "extension_id" na rzeczywiste ID rozszerzenia.
  2. Wykonaj następujące polecenia:
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"

Use the CRX Viewer website

https://robwu.nl/crxviewer/

Use the CRX Viewer extension

Inną wygodną metodą jest użycie Chrome Extension Source Viewer, który jest projektem open-source. Można go zainstalować ze Chrome Web Store. Kod źródłowy viewer’a jest dostępny w jego GitHub repository.

View source of locally installed extension

Chrome extensions zainstalowane lokalnie można również zbadać. Oto jak:

  1. Wejdź do lokalnego katalogu profilu Chrome odwiedzając chrome://version/ i znajdź pole “Profile Path”.
  2. Przejdź do podfolderu Extensions/ w katalogu profilu.
  3. Ten folder zawiera wszystkie zainstalowane rozszerzenia, zwykle z ich kodem źródłowym w czytelnym formacie.

Aby zidentyfikować rozszerzenia, możesz mapować ich ID na nazwy:

  • Włącz Developer Mode na stronie about:extensions, aby zobaczyć ID każdego rozszerzenia.
  • W folderze każdego rozszerzenia plik manifest.json zawiera czytelne pole name, co pomaga zidentyfikować rozszerzenie.

Use a File Archiver or Unpacker

Przejdź do Chrome Web Store i pobierz rozszerzenie. Plik będzie miał rozszerzenie .crx. Zmień rozszerzenie pliku z .crx na .zip. Użyj dowolnego archiwizera (np. WinRAR, 7-Zip itp.), aby rozpakować zawartość pliku ZIP.

Use Developer Mode in Chrome

Otwórz Chrome i przejdź do chrome://extensions/. Włącz “Developer mode” w prawym górnym rogu. Kliknij “Load unpacked extension…”. Przejdź do katalogu z rozszerzeniem. To nie pobiera kodu źródłowego, ale jest przydatne do przeglądania i modyfikowania kodu już pobranego lub rozwijanego rozszerzenia.

Chrome extension manifest dataset

Aby spróbować znaleźć podatne rozszerzenia przeglądarki możesz użyć https://github.com/palant/chrome-extension-manifests-dataset i sprawdzić ich pliki manifestu pod kątem potencjalnych oznak podatności. Na przykład, aby sprawdzić rozszerzenia z więcej niż 25000 użytkowników, content_scripts i uprawnienie 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)

Ukryta technika do backdoorowania Chromium poprzez bezpośrednią edycję per-user Preferences i fałszowanie poprawnych HMACs, powodująca, że przeglądarka akceptuje i aktywuje dowolne unpacked extension bez powiadomień czy flag.

Forced Extension Load Preferences Mac Forgery Windows

Wykrywanie złośliwych aktualizacji rozszerzeń (Static Version Diffing)

Kompromisy łańcucha dostaw często pojawiają się jako złośliwe aktualizacje wcześniej nieszkodliwych rozszerzeń. Praktyczne, niskoszumowe podejście to porównanie nowego pakietu rozszerzenia z ostatnią znaną-dobrą wersją przy użyciu analizy statycznej (np. Assemblyline). Celem jest generowanie alertów dla wysokosygnałowych deltas zamiast dla każdej zmiany.

Workflow

  • Prześlij obie wersje (stara + nowa) do tego samego profilu analizy statycznej.
  • Zaznacz nowe lub zmodyfikowane background/service worker scripts (persistence + privileged logic).
  • Zaznacz nowe lub zmodyfikowane content scripts (dostęp do DOM i zbieranie danych).
  • Zaznacz nowe permissions/host_permissions dodane w manifest.json.
  • Zaznacz nowe domeny wyekstrahowane z kodu (potencjalne C2/exfil endpoints).
  • Zaznacz nowe static-analysis detections (np. base64 decode, cookie harvesting, network-request builders, obfuscation patterns).
  • Zaznacz anomalie statystyczne takie jak ostre skoki entropii lub outlier z-scores w zmienionych skryptach.

Dokładne wykrywanie zmian w skryptach

  • Nowy skrypt dodany → wykryć przez diff manifest.json.
  • Istniejący skrypt zmodyfikowany (manifest bez zmian) → porównaj per-file hashes z wyodrębnionego drzewa plików (np. output Extract z Assemblyline). To wychwytuje ukryte aktualizacje istniejących workers lub content scripts.

Wykrywania przed ujawnieniem

Aby uniknąć wykryć „easy mode” opartych na już znanych IOC, wyłącz threat-intel-fed services i polegaj na sygnałach wewnętrznych (domeny, heurystyczne sygnatury, delty skryptów, anomalie entropii). To zwiększa szanse wykrycia złośliwych aktualizacji przed publicznym zgłoszeniem.

Przykładowa logika alertów o wysokiej pewności

  • Niskoszumowa kombinacja: nowe domeny + nowe static-analysis detections + zaktualizowany background/service worker + zaktualizowane lub dodane content scripts.
  • Szersze wykrycie: new domain + new or updated background/service worker (wyższy recall, więcej fałszywych alarmów).

Kluczowe usługi Assemblyline dla tego workflow:

  • Extract: rozpakowuje rozszerzenie i generuje per-file hashes.
  • Characterize: oblicza cechy plików (np. entropia).
  • JsJAWS / FrankenStrings / URLCreator: ujawniają heurystyki JS, stringi i domeny do porównania między wersjami.

Security Audit Checklist

Mimo że Browser Extensions mają ograniczoną powierzchnię ataku, niektóre z nich mogą zawierać vulnerabilities lub potencjalne hardening improvements. Poniższe pozycje są najczęstsze:

  • Ogranicz na ile to możliwe żądane permissions
  • Ogranicz jak najbardziej host_permissions
  • Użyj silnej content_security_policy
  • Ogranicz maksymalnie externally_connectable; jeśli nie jest potrzebne i to możliwe, nie zostawiaj go domyślnie, określ {}
  • Jeśli URL vulnerable to XSS or to takeover jest tutaj wspomniany, atakujący będzie w stanie wysyłać wiadomości bezpośrednio do background scripts. Bardzo potężny bypass.
  • Ogranicz maksymalnie web_accessible_resources, nawet do pustego jeśli możliwe.
  • Jeśli web_accessible_resources nie jest none, sprawdź ClickJacking
  • Jeśli jakakolwiek communication występuje od extension do web page, sprawdź XSS vulnerabilities powstające w tej komunikacji.
  • Jeśli używane są Post Messages, sprawdź Post Message vulnerabilities.
  • Jeśli Content Script ma dostęp do szczegółów DOM, sprawdź, czy nie wprowadzają XSS jeśli zostaną zmodyfikowane przez stronę.
  • Zwróć szczególną uwagę, jeśli ta komunikacja jest zaangażowana również w Content Script -> Background script communication
  • Jeśli background script komunikuje się przez native messaging, sprawdź czy komunikacja jest bezpieczna i sanityzowana
  • Sensitive information shouldn’t be stored wewnątrz kodu Browser Extension
  • Sensitive information shouldn’t be stored w pamięci Browser Extension
  • Sensitive information shouldn’t be stored na niechronionym file system

Browser Extension Risks

Aplikacja https://crxaminer.tech/ analizuje dane, takie jak permissions, o które prosi rozszerzenie, aby ocenić poziom ryzyka korzystania z rozszerzenia.

Tools

Tarnish

  • Pobiera dowolne Chrome extension z podanego linku do Chrome webstore.
  • manifest.json viewer: po prostu wyświetla JSON-prettified wersję manifestu rozszerzenia.
  • Fingerprint Analysis: wykrywanie web_accessible_resources i automatyczne generowanie JavaScript do fingerprintingu rozszerzeń Chrome.
  • Potential Clickjacking Analysis: wykrywanie stron HTML rozszerzeń z ustawioną dyrektywą web_accessible_resources. Mogą być potencjalnie podatne na clickjacking w zależności od przeznaczenia stron.
  • Permission Warning(s) viewer: pokazuje listę wszystkich ostrzeżeń z promptów uprawnień Chrome, które będą wyświetlane przy próbie instalacji rozszerzenia.
  • Dangerous Function(s): pokazuje lokalizację niebezpiecznych funkcji, które mogłyby być potencjalnie wykorzystane przez atakującego (np. innerHTML, chrome.tabs.executeScript).
  • Entry Point(s): pokazuje, gdzie rozszerzenie przyjmuje input użytkownika/zewnętrzny. To przydatne do zrozumienia powierzchni ataku rozszerzenia i wyszukania potencjalnych punktów, do których wysłać złośliwie spreparowane dane.
  • Zarówno Dangerous Function(s), jak i Entry Point(s) skanery dla wygenerowanych alertów udostępniają:
    • Odpowiedni fragment kodu i linię, która spowodowała alert.
    • Opis problemu.
    • Przycisk “View File” do wyświetlenia pełnego pliku źródłowego zawierającego kod.
    • Ścieżkę pliku, który wywołał alert.
    • Pełny Chrome extension URI pliku z alertem.
    • Typ pliku, np. Background Page script, Content Script, Browser Action, itd.
    • Jeśli podatna linia znajduje się w pliku JavaScript, ścieżki wszystkich stron, w których jest dołączona, typy tych stron oraz status [web_accessible_resource].
  • Content Security Policy (CSP) analyzer and bypass checker: wskaże słabości CSP rozszerzenia i pokaże potencjalne sposoby obejścia CSP z powodu białych list CDNów, itp.
  • Known Vulnerable Libraries: używa Retire.js do sprawdzenia użycia znanych podatnych bibliotek JavaScript.
  • Pobieranie rozszerzenia i sformatowanych wersji.
  • Pobranie oryginalnego rozszerzenia.
  • Pobranie upiększonej wersji rozszerzenia (auto prettified HTML i JavaScript).
  • Automatyczne cachowanie wyników skanów — uruchomienie skanu rozszerzenia zajmie sporo czasu za pierwszym razem. Jednak przy drugim uruchomieniu, zakładając że rozszerzenie nie zostało zaktualizowane, będzie prawie natychmiastowe dzięki cache.
  • Linkowalne URL raportów, łatwo udostępnić komuś raport rozszerzenia wygenerowany przez tarnish.

Neto

Project Neto to pakiet Python 3 zaprojektowany do analizy i odkrywania ukrytych cech pluginów i rozszerzeń przeglądarek dla znanych przeglądarek takich jak Firefox i Chrome. Automatyzuje proces rozpakowywania spakowanych plików, aby wydobyć te cechy z istotnych zasobów w rozszerzeniu, takich jak manifest.json, foldery lokalizacji czy pliki źródłowe Javascript i HTML.

References

Tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks