Metodologia Pentestingu Rozszerzeń Przeglądarki

Reading time: 25 minutes

tip

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

Wsparcie 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 interakcje z DOM-ami innych stron. Oznacza to, że mogą naruszać poufność, integralność i dostępność (CIA) innych stron.

Główne Komponenty

Układy rozszerzeń wyglądają najlepiej, gdy są wizualizowane i składają się z trzech komponentów. Przyjrzyjmy się każdemu komponentowi dokładniej.

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

Skrypty Treści

Każdy skrypt treści ma bezpośredni dostęp do DOM jednej strony internetowej i jest narażony na potencjalnie złośliwe dane wejściowe. Jednak skrypt treści nie ma żadnych uprawnień poza możliwością wysyłania wiadomości do rdzenia rozszerzenia.

Rdzeń Rozszerzenia

Rdzeń rozszerzenia zawiera większość uprawnień/dostępu rozszerzenia, ale rdzeń rozszerzenia może wchodzić w interakcje z treścią internetową tylko za pośrednictwem XMLHttpRequest i skryptów treści. Ponadto rdzeń rozszerzenia nie ma bezpośredniego dostępu do maszyny gospodarza.

Natychmiastowy Plik Binarny

Rozszerzenie pozwala na natywny plik binarny, który może uzyskać dostęp do maszyny gospodarza z pełnymi uprawnieniami użytkownika. Natywny plik binarny wchodzi w interakcje z rdzeniem rozszerzenia za pośrednictwem standardowego interfejsu programowania aplikacji Netscape Plugin (NPAPI), używanego 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 ze skryptu treści do rdzenia rozszerzenia i z rdzenia rozszerzenia do natywnego pliku binarnego.

Każdy komponent rozszerzenia jest oddzielony od siebie przez silne granice ochronne. Każdy komponent działa w oddzielnym procesie systemu operacyjnego. Skrypty treści i rdzenie rozszerzeń działają w procesach piaskownicy, niedostępnych dla większości usług systemu operacyjnego.

Co więcej, skrypty treści są oddzielone od swoich powiązanych stron internetowych przez działanie w oddzielnym stosie JavaScript. Skrypt treści i strona internetowa mają dostęp do tego samego podstawowego DOM, ale obie nigdy nie wymieniają wskaźników JavaScript, co zapobiega wyciekowi funkcjonalności JavaScript.

manifest.json

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

Przykład:

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

Skrypty zawartości są ładowane za każdym razem, gdy użytkownik nawiguje do pasującej strony, w naszym przypadku każda strona pasująca do wyrażenia https://example.com/* i niepasująca do wyrażenia regex *://*/*/business*. Wykonują się jak własne skrypty strony i mają dowolny dostęp do Modelu Obiektowego Dokumentu (DOM).

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

Aby dodać lub wykluczyć więcej adresów URL, można również użyć include_globs i exclude_globs.

To jest przykładowy skrypt zawartości, który doda przycisk wyjaśnienia do strony, gdy API storage zostanie użyte do pobrania wartości message z pamięci rozszerzenia.

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

Wiadomość jest wysyłana do stron rozszerzenia przez skrypt treści, gdy ten przycisk jest kliknięty, za pomocą runtime.sendMessage() API. Wynika to z ograniczenia skryptu treści w bezpośrednim dostępie do API, przy czym storage jest jednym 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 skrypty treści mogą komunikować się.

warning

W zależności od przeglądarki, możliwości skryptu treści mogą się nieco różnić. Dla przeglądarek opartych na Chromium, lista możliwości jest dostępna w dokumentacji Chrome Developers, a dla Firefox, MDN służy jako główne źródło.
Warto również zauważyć, że skrypty treści mają możliwość komunikacji z skryptami w tle, co umożliwia im wykonywanie działań i przekazywanie odpowiedzi z powrotem.

Aby wyświetlić i debugować skrypty treści w Chrome, menu narzędzi dewelopera Chrome można uzyskać z Opcje > Więcej narzędzi > Narzędzia dewelopera LUB naciskając Ctrl + Shift + I.

Po wyświetleniu narzędzi dewelopera należy kliknąć zakładkę Źródło, a następnie zakładkę Skrypty treści. Umożliwia to obserwację działających skryptów treści z różnych rozszerzeń oraz ustawienie punktów przerwania w celu śledzenia przepływu wykonania.

Wstrzyknięte skrypty treści

tip

Należy zauważyć, że Skrypty treści nie są obowiązkowe, ponieważ możliwe jest również dynamiczne wstrzykiwanie skryptów oraz programowe wstrzykiwanie ich na stronach internetowych za pomocą tabs.executeScript. To w rzeczywistości zapewnia bardziej szczegółową kontrolę.

Aby programowo wstrzyknąć skrypt treści, rozszerzenie musi mieć uprawnienia hosta dla strony, do której skrypty mają być wstrzyknięte. Uprawnienia te mogą być zabezpieczone albo przez zażądanie ich w manifeście rozszerzenia, albo tymczasowo przez activeTab.

Przykład rozszerzenia opartego na activeTab

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

Przykład z uprawnieniami skryptów

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

Aby dodać lub wykluczyć więcej adresów URL, można również użyć include_globs i exclude_globs.

Skrypty zawartości run_at

Pole run_at kontroluje kiedy pliki JavaScript są wstrzykiwane do strony internetowej. Preferowana i domyślna wartość to "document_idle".

Możliwe wartości to:

  • document_idle: Kiedy tylko to możliwe
  • document_start: Po załadowaniu jakichkolwiek plików z css, ale przed skonstruowaniem jakiegokolwiek innego DOM lub uruchomieniem jakiegokolwiek innego skryptu.
  • document_end: Natychmiast po zakończeniu DOM, ale przed załadowaniem subzasobów, takich jak obrazy i ramki.

Poprzez manifest.json

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

Via service-worker.js

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

background

Wiadomości wysyłane przez skrypty zawartości są odbierane przez background page, która odgrywa centralną rolę w koordynowaniu komponentów rozszerzenia. Należy zauważyć, że background page utrzymuje się przez cały czas życia rozszerzenia, działając dyskretnie bez bezpośredniej interakcji użytkownika. Posiada własny Model Obiektów Dokumentu (DOM), co umożliwia złożone interakcje i zarządzanie stanem.

Kluczowe punkty:

  • Rola Background Page: Działa jako centrum nerwowe dla rozszerzenia, zapewniając komunikację i koordynację między różnymi częściami rozszerzenia.
  • Trwałość: To zawsze obecny byt, niewidoczny dla użytkownika, ale integralny dla funkcjonalności rozszerzenia.
  • Automatyczne generowanie: Jeśli nie jest wyraźnie zdefiniowane, przeglądarka automatycznie utworzy background page. Ta automatycznie generowana strona będzie zawierać wszystkie skrypty w tle określone w manifeście rozszerzenia, zapewniając płynne działanie zadań w tle rozszerzenia.

tip

Wygoda zapewniana przez przeglądarkę w automatycznym generowaniu background page (gdy nie jest wyraźnie zadeklarowana) zapewnia, że wszystkie niezbędne skrypty w tle są zintegrowane i działają, upraszczając proces konfiguracji rozszerzenia.

Przykład skryptu w tle:

js
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 do otwarcia strony w nowej karcie.

Aby debugować skrypt w tle, możesz przejść do szczegółów rozszerzenia i zbadać serwis worker, co otworzy narzędzia deweloperskie z skryptem w tle:

Strony opcji i inne

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

  • Strony akcji są wyświetlane w rozwijanym menu, gdy kliknięta jest ikona rozszerzenia.
  • Strony, które rozszerzenie załaduje w nowej karcie.
  • Strony opcji: Ta strona wyświetla się na górze rozszerzenia po kliknięciu. W poprzednim manifeście w moim przypadku mogłem uzyskać dostęp do tej strony w chrome://extensions/?options=fadlhnelkbeojnebcbkacjilhnbjfjca lub klikając:

Zauważ, że te strony nie są trwałe jak strony w tle, ponieważ ładują dynamicznie treści w zależności od potrzeb. Mimo to, dzielą pewne możliwości z stroną w tle:

  • Komunikacja z skryptami treści: Podobnie jak strona w tle, te strony mogą odbierać wiadomości od skryptów treści, ułatwiając interakcję w ramach rozszerzenia.
  • Dostęp do specyficznych API rozszerzenia: Te strony mają pełny dostęp do specyficznych API rozszerzenia, w zależności od uprawnień zdefiniowanych dla rozszerzenia.

permissions & host_permissions

permissions i host_permissions to wpisy z manifest.json, które wskazują jakie uprawnienia ma rozszerzenie przeglądarki (przechowywanie, lokalizacja...) oraz na jakich stronach internetowych.

Ponieważ rozszerzenia przeglądarki mogą być tak uprzywilejowane, złośliwe lub skompromitowane mogłyby umożliwić atakującemu różne sposoby kradzieży wrażliwych informacji i szpiegowania użytkownika.

Sprawdź, jak te ustawienia działają i jak mogą być nadużywane w:

{{#ref}} browext-permissions-and-host_permissions.md {{#endref}}

content_security_policy

Polityka bezpieczeństwa treści może być również zadeklarowana wewnątrz manifest.json. Jeśli jest zdefiniowana, może być wrażliwa.

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

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

Dla uzyskania dodatkowych informacji na temat CSP i potencjalnych obejść sprawdź:

{{#ref}} ../content-security-policy-csp-bypass/ {{#endref}}

web_accessible_resources

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

javascript
{
...
"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 identyfikator rozszerzenia jest dostępny:

Jednakże, jeśli parametr manifest.json use_dynamic_url jest używany, ten identyfikator może być dynamiczny.

tip

Zauważ, że nawet jeśli strona jest tutaj wymieniona, może być chroniona przed ClickJacking dzięki Polityce Bezpieczeństwa Treści. Dlatego musisz również to sprawdzić (sekcja frame-ancestors) przed potwierdzeniem, że atak ClickJacking jest możliwy.

Dopuszczenie dostępu do tych stron sprawia, że są one potencjalnie podatne na ClickJacking:

{{#ref}} browext-clickjacking.md {{#endref}}

tip

Zezwolenie na ładowanie tych stron tylko przez rozszerzenie, a nie przez losowe adresy URL, może zapobiec atakom ClickJacking.

caution

Zauważ, że strony z web_accessible_resources oraz inne strony rozszerzenia również mogą kontaktować się z skryptami w tle. Jeśli jedna z tych stron jest podatna na XSS, może to otworzyć większą lukę.

Ponadto, zauważ, że możesz otwierać tylko strony wskazane w web_accessible_resources wewnątrz iframe, ale z nowej karty możliwe jest uzyskanie dostępu do dowolnej strony w rozszerzeniu, znając identyfikator rozszerzenia. Dlatego, jeśli znajdziesz XSS wykorzystujące te same parametry, może być to wykorzystane, nawet jeśli strona nie jest skonfigurowana w web_accessible_resources.

externally_connectable

Zgodnie z dokumentacją, właściwość manifestu "externally_connectable" deklaruje które rozszerzenia i strony internetowe 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 jest zadeklarowany jako "ids": ["*"], wszystkie rozszerzenia mogą się łączyć, ale żadne strony internetowe nie mogą się łączyć.
  • Jeśli określone identyfikatory są podane, jak w "ids": ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"], tylko te aplikacje mogą się łączyć.
  • Jeśli dopasowania są określone, te aplikacje internetowe będą mogły się łączyć:
json
"matches": [
"https://*.google.com/*",
"*://*.chromium.org/*",
  • Jeśli jest określone jako puste: "externally_connectable": {}, żadna aplikacja ani strona internetowa nie będą mogły się połączyć.

Im mniej rozszerzeń i adresów URL wskazanych tutaj, tym mniejsza powierzchnia ataku.

caution

Jeśli strona internetowa wrażliwa na XSS lub przejęcie jest wskazana w externally_connectable, atakujący będzie mógł wysyłać wiadomości bezpośrednio do skryptu w tle, całkowicie omijając Content Script i jego CSP.

Dlatego jest to bardzo potężne obejście.

Co więcej, jeśli klient zainstaluje złośliwe rozszerzenie, nawet jeśli nie jest dozwolone do komunikacji z wrażliwym rozszerzeniem, może wstrzyknąć dane XSS w dozwolonej stronie internetowej lub nadużyć API WebRequest lub DeclarativeNetRequest, aby manipulować żądaniami na docelowej domenie, zmieniając żądanie strony dla pliku JavaScript. (Zauważ, że CSP na docelowej stronie może zapobiec tym atakom). Ten pomysł pochodzi z tego opisu.

Podsumowanie komunikacji

Rozszerzenie <--> WebApp

Aby komunikować się między skryptem treści a stroną internetową, zazwyczaj używane są wiadomości post. Dlatego w aplikacji internetowej zazwyczaj znajdziesz wywołania funkcji window.postMessage oraz w skrypcie treści nasłuchiwacze, takie jak window.addEventListener. Należy jednak zauważyć, że rozszerzenie może również komunikować się z aplikacją internetową, wysyłając wiadomość Post (a zatem strona powinna się tego spodziewać) lub po prostu sprawić, że strona załaduje nowy skrypt.

Wewnątrz rozszerzenia

Zazwyczaj funkcja chrome.runtime.sendMessage jest używana do wysyłania wiadomości wewnątrz rozszerzenia (zazwyczaj obsługiwana przez skrypt background), a aby ją odebrać i obsłużyć, deklarowany jest nasłuchiwacz wywołujący chrome.runtime.onMessage.addListener.

Możliwe jest również użycie chrome.runtime.connect(), aby mieć stałe połączenie zamiast wysyłania pojedynczych wiadomości, można go użyć do wysyłania i odbierania wiadomości, jak w poniższym przykładzie:

chrome.runtime.connect() przykład
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
})

Możliwe jest również wysyłanie wiadomości z skryptu w tle do skryptu treści znajdującego się w określonej karcie, wywołując chrome.tabs.sendMessage, gdzie będziesz musiał wskazać ID karty, do której chcesz wysłać wiadomość.

Z dozwolonego externally_connectable do rozszerzenia

Aplikacje internetowe i zewnętrzne rozszerzenia przeglądarki dozwolone w konfiguracji externally_connectable mogą wysyłać żądania za pomocą:

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

Gdzie należy wspomnieć o identyfikatorze rozszerzenia.

Native Messaging

Możliwe jest, aby skrypty w tle komunikowały się z binariami w systemie, które mogą być narażone na krytyczne luki, takie jak RCE jeśli ta komunikacja nie jest odpowiednio zabezpieczona. Więcej na ten temat później.

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

Web ↔︎ Komunikacja Skryptów Treści

Środowiska, w których działają skrypty treści, oraz gdzie istnieją strony hosta, są oddzielone od siebie, zapewniając izolację. Pomimo tej izolacji, obie strony mają możliwość interakcji z Modelem Obiektów Dokumentu (DOM) strony, wspólnym zasobem. Aby strona hosta mogła nawiązać komunikację z skryptem treści, lub pośrednio z rozszerzeniem przez skrypt treści, konieczne jest wykorzystanie DOM, który jest dostępny dla obu stron jako kanał komunikacyjny.

Wiadomości Post

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
)

Bezpieczna komunikacja Post Message powinna sprawdzać autentyczność otrzymanej wiadomości, co można zrobić, sprawdzając:

  • event.isTrusted: To jest True tylko wtedy, gdy zdarzenie zostało wywołane przez akcję użytkownika
  • Skrypt treści może oczekiwać wiadomości tylko wtedy, gdy użytkownik wykona jakąś akcję
  • origin domain: może oczekiwać wiadomości tylko z dozwolonej listy domen.
  • Jeśli używana jest wyrażenie regularne, należy być bardzo ostrożnym
  • Source: received_message.source !== window może być użyte do sprawdzenia, czy wiadomość była z tej samej okna, w którym skrypt treści nasłuchuje.

Poprzednie kontrole, nawet jeśli są przeprowadzane, mogą być podatne, więc sprawdź na następującej stronie potencjalne obejścia Post Message:

{{#ref}} ../postmessage-vulnerabilities/ {{#endref}}

Iframe

Innym możliwym sposobem komunikacji mogą być Iframe URLs, przykład można znaleźć w:

{{#ref}} browext-xss-example.md {{#endref}}

DOM

To nie jest "dokładnie" sposób komunikacji, ale sieć i skrypt treści będą miały dostęp do DOM sieci. Więc, jeśli skrypt treści odczytuje jakieś informacje z niego, ufając DOM sieci, sieć może zmodyfikować te dane (ponieważ sieć nie powinna być ufana, lub ponieważ sieć jest podatna na XSS) i kompromitować skrypt treści.

Możesz również znaleźć przykład XSS oparty na DOM, aby skompromitować rozszerzenie przeglądarki w:

{{#ref}} browext-xss-example.md {{#endref}}

Komunikacja Skryptu Treści ↔︎ Skryptu Tła

Skrypt treści może używać funkcji runtime.sendMessage() lub tabs.sendMessage(), aby wysłać jednorazową wiadomość serializowalną w formacie JSON.

Aby obsłużyć odpowiedź, użyj zwróconego Promise. Chociaż, dla zachowania zgodności wstecznej, nadal możesz przekazać callback jako ostatni argument.

Wysyłanie żądania z skryptu treści wygląda tak:

javascript
;(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 rozszerzenia (zwykle skryptu w tle). Przykład, jak wysłać wiadomość do skryptu zawartości w wybranej karcie:

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

Na odbiorze musisz ustawić runtime.onMessage nasłuchiwacz zdarzeń, aby obsłużyć wiadomość. Wygląda to tak samo z skryptu treści lub strony rozszerzenia.

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

W podanym przykładzie, sendResponse() został wykonany w sposób synchroniczny. Aby zmodyfikować obsługę zdarzenia onMessage na asynchroniczne wykonanie sendResponse(), konieczne jest dodanie return true;.

Ważnym zagadnieniem jest to, że w scenariuszach, w których wiele stron ma odbierać zdarzenia onMessage, pierwsza strona, która wykona sendResponse() dla konkretnego zdarzenia, będzie jedyną, która skutecznie dostarczy odpowiedź. Jakiekolwiek kolejne odpowiedzi na to samo zdarzenie nie będą brane pod uwagę.

Podczas tworzenia nowych rozszerzeń, preferencje powinny być skierowane ku obietnicom zamiast do callbacków. Jeśli chodzi o użycie callbacków, funkcja sendResponse() jest uznawana za ważną tylko wtedy, gdy jest wykonywana bezpośrednio w kontekście synchronicznym lub jeśli obsługa zdarzenia wskazuje na operację asynchroniczną, zwracając true. Jeśli żaden z handlerów nie zwróci true lub jeśli funkcja sendResponse() zostanie usunięta z pamięci (zbieranie śmieci), callback związany z funkcją sendMessage() zostanie wywołany domyślnie.

Native Messaging

Rozszerzenia przeglądarki umożliwiają również komunikację z binariami w systemie za pomocą stdin. Aplikacja musi zainstalować plik json wskazujący na to w formacie json, takim jak:

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

Gdzie name to ciąg przekazywany do runtime.connectNative() lub runtime.sendNativeMessage() w celu komunikacji z aplikacją z tła skryptów rozszerzenia przeglądarki. path to ścieżka do binarnego pliku, istnieje tylko 1 ważny type, którym jest stdio (użyj stdin i stdout), a allowed_origins wskazuje rozszerzenia, które mogą uzyskać do niego dostęp (i nie mogą mieć znaku wieloznacznego).

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

tip

Rozszerzenie przeglądarki również potrzebuje uprawnienia nativeMessaing zadeklarowanego, aby móc korzystać z tej komunikacji.

Tak wygląda kod niektórego skryptu tła wysyłającego wiadomości do aplikacji natywnej:

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

W tym wpisie na blogu zaproponowano podatny wzór wykorzystujący natywne wiadomości:

  1. Rozszerzenie przeglądarki ma wzór wildcard dla skryptu treści.
  2. Skrypt treści przesyła wiadomości postMessage do skryptu w tle za pomocą sendMessage.
  3. Skrypt w tle przesyła wiadomość do aplikacji natywnej za pomocą sendNativeMessage.
  4. Aplikacja natywna niebezpiecznie obsługuje wiadomość, co prowadzi do wykonania kodu.

A w jego wnętrzu wyjaśniono przykład przechodzenia z dowolnej strony do RCE wykorzystując rozszerzenie przeglądarki.

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

Jeśli Rozszerzenie Przeglądarki przechowuje wrażliwe informacje w swojej pamięci, mogą one być zrzucane (szczególnie na maszynach z systemem Windows) i wyszukiwane w tych informacjach.

Dlatego pamięć Rozszerzenia Przeglądarki nie powinna być uważana za bezpieczną, a wrażliwe informacje, takie jak dane logowania czy frazy mnemoniczne, nie powinny być 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 ustawień rozszerzenia przeglądarki, klikając Inspect pop-up -> W sekcji Memory -> Take a snapshot i CTRL+F, aby wyszukać w zrzucie wrażliwe informacje.

Ponadto, bardzo wrażliwe informacje, takie jak klucze mnemoniczne czy hasła, nie powinny mieć możliwości kopiowania do schowka (lub przynajmniej powinny być usuwane ze schowka w ciągu kilku sekund), ponieważ wtedy procesy monitorujące schowek będą mogły je zdobyć.

Ładowanie rozszerzenia w przeglądarce

  1. Pobierz Rozszerzenie Przeglądarki i rozpakuj je.
  2. Przejdź do chrome://extensions/ i włącz Tryb dewelopera.
  3. Kliknij przycisk Load unpacked.

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

Uzyskiwanie kodu źródłowego ze sklepu

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

Pobierz rozszerzenie jako ZIP za pomocą wiersza poleceń

Kod źródłowy rozszerzenia Chrome można pobrać jako plik ZIP za pomocą wiersza poleceń. Wymaga to użycia curl, aby pobrać plik ZIP z określonego adresu URL, a następnie wyodrębnić zawartość pliku ZIP do katalogu. Oto kroki:

  1. Zastąp "extension_id" rzeczywistym ID rozszerzenia.
  2. Wykonaj następujące polecenia:
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"

Użyj strony CRX Viewer

https://robwu.nl/crxviewer/

Użyj rozszerzenia CRX Viewer

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

Wyświetl źródło lokalnie zainstalowanego rozszerzenia

Rozszerzenia Chrome zainstalowane lokalnie można również sprawdzić. Oto jak:

  1. Uzyskaj dostęp do lokalnego katalogu profilu Chrome, odwiedzając chrome://version/ i lokalizując pole "Profile Path".
  2. Przejdź do podfolderu Extensions/ w katalogu profilu.
  3. Ten folder zawiera wszystkie zainstalowane rozszerzenia, zazwyczaj z ich kodem źródłowym w czytelnym formacie.

Aby zidentyfikować rozszerzenia, możesz powiązać ich identyfikatory z nazwami:

  • Włącz tryb dewelopera na stronie about:extensions, aby zobaczyć identyfikatory każdego rozszerzenia.
  • W każdym folderze rozszerzenia plik manifest.json zawiera czytelne pole name, co pomaga w identyfikacji rozszerzenia.

Użyj archiwizera plików lub dekompresora

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 plików (takiego jak WinRAR, 7-Zip itp.), aby wyodrębnić zawartość pliku ZIP.

Użyj trybu dewelopera w Chrome

Otwórz Chrome i przejdź do chrome://extensions/. Włącz "Tryb dewelopera" w prawym górnym rogu. Kliknij "Załaduj rozpakowane rozszerzenie...". Przejdź do katalogu swojego rozszerzenia. To nie pobiera kodu źródłowego, ale jest przydatne do przeglądania i modyfikowania kodu już pobranego lub opracowanego rozszerzenia.

Zbiór danych manifestu rozszerzenia Chrome

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

Lista kontrolna audytu bezpieczeństwa

Chociaż rozszerzenia przeglądarki mają ograniczoną powierzchnię ataku, niektóre z nich mogą zawierać luki lub możliwości wzmocnienia. Oto najczęstsze z nich:

  • Ogranicz tak bardzo, jak to możliwe, żądane permissions
  • Ogranicz tak bardzo, jak to możliwe host_permissions
  • Użyj silnej content_security_policy
  • Ogranicz tak bardzo, jak to możliwe externally_connectable, jeśli nie jest potrzebne i możliwe, nie zostawiaj go domyślnie, określ {}
  • Jeśli URL podatny na XSS lub przejęcie jest tutaj wymieniony, atakujący będzie mógł wysyłać wiadomości do skryptów w tle bezpośrednio. Bardzo potężne obejście.
  • Ogranicz tak bardzo, jak to możliwe web_accessible_resources, nawet puste, jeśli to możliwe.
  • Jeśli web_accessible_resources nie jest puste, sprawdź ClickJacking
  • Jeśli jakakolwiek komunikacja zachodzi z rozszerzenia do strony internetowej, sprawdź XSS luki spowodowane w komunikacji.
  • Jeśli używane są Post Messages, sprawdź luki w Post Message.
  • Jeśli Content Script ma dostęp do szczegółów DOM, sprawdź, czy nie wprowadza XSS, jeśli zostanie zmodyfikowany przez sieć
  • Zwróć szczególną uwagę, jeśli ta komunikacja jest również zaangażowana w komunikację Content Script -> Background script
  • Jeśli skrypt w tle komunikuje się za pomocą native messaging, sprawdź, czy komunikacja jest bezpieczna i oczyszczona
  • Wrażliwe informacje nie powinny być przechowywane wewnątrz kodu rozszerzenia przeglądarki
  • Wrażliwe informacje nie powinny być przechowywane wewnątrz pamięci rozszerzenia przeglądarki
  • Wrażliwe informacje nie powinny być przechowywane w systemie plików bez ochrony

Ryzyka związane z rozszerzeniem przeglądarki

  • Aplikacja https://crxaminer.tech/ analizuje dane, takie jak uprawnienia, które żąda rozszerzenie przeglądarki, aby określić poziom ryzyka korzystania z rozszerzenia przeglądarki.

Narzędzia

Tarnish

  • Pobiera dowolne rozszerzenie Chrome z podanego linku do sklepu Chrome.
  • manifest.json viewer: po prostu wyświetla wersję JSON rozszerzenia.
  • Analiza odcisków palców: Wykrywanie web_accessible_resources i automatyczne generowanie JavaScript do odcisków palców rozszerzenia Chrome.
  • Potencjalna analiza Clickjacking: Wykrywanie stron HTML rozszerzenia z ustawionym dyrektywą web_accessible_resources. Mogą być potencjalnie podatne na clickjacking w zależności od celu stron.
  • Viewer ostrzeżeń o uprawnieniach: który pokazuje listę wszystkich ostrzeżeń o uprawnieniach Chrome, które będą wyświetlane po próbie zainstalowania rozszerzenia przez użytkownika.
  • Niebezpieczna funkcja: pokazuje lokalizację niebezpiecznych funkcji, które mogą być potencjalnie wykorzystywane przez atakującego (np. funkcje takie jak innerHTML, chrome.tabs.executeScript).
  • Punkty wejścia: pokazuje, gdzie rozszerzenie przyjmuje dane wejściowe od użytkownika/zewnętrzne. To jest przydatne do zrozumienia powierzchni rozszerzenia i szukania potencjalnych punktów do wysyłania złośliwie skonstruowanych danych do rozszerzenia.
  • Zarówno skanery Niebezpiecznych Funkcji, jak i Punktów Wejścia mają następujące dla swoich wygenerowanych alertów:
  • Odpowiedni fragment kodu i linia, która spowodowała alert.
  • Opis problemu.
  • Przycisk „Zobacz plik”, aby zobaczyć pełny plik źródłowy zawierający kod.
  • Ścieżka alertowanego pliku.
  • Pełny URI rozszerzenia Chrome alertowanego pliku.
  • Typ pliku, taki jak skrypt strony w tle, skrypt treści, akcja przeglądarki itp.
  • Jeśli podatna linia znajduje się w pliku JavaScript, ścieżki wszystkich stron, na których jest zawarta, a także typ tych stron oraz status web_accessible_resource.
  • Analizator polityki bezpieczeństwa treści (CSP) i sprawdzacz obejść: Wskaże słabości w CSP twojego rozszerzenia i również oświetli wszelkie potencjalne sposoby obejścia twojego CSP z powodu białych list CDN itp.
  • Znane podatne biblioteki: Używa Retire.js do sprawdzenia użycia znanych podatnych bibliotek JavaScript.
  • Pobierz rozszerzenie i sformatowane wersje.
  • Pobierz oryginalne rozszerzenie.
  • Pobierz piękniejszą wersję rozszerzenia (automatycznie sformatowany HTML i JavaScript).
  • Automatyczne buforowanie wyników skanowania, uruchomienie skanowania rozszerzenia zajmie sporo czasu przy pierwszym uruchomieniu. Jednak przy drugim uruchomieniu, zakładając, że rozszerzenie nie zostało zaktualizowane, będzie prawie natychmiastowe dzięki buforowanym wynikom.
  • Linkowalne adresy URL raportów, łatwo linkuj kogoś do raportu rozszerzenia wygenerowanego przez tarnish.

Neto

Projekt Neto to pakiet Pythona 3 stworzony do analizy i odkrywania ukrytych funkcji wtyczek i rozszerzeń przeglądarek dla znanych przeglądarek, takich jak Firefox i Chrome. Automatyzuje proces rozpakowywania spakowanych plików, aby wydobyć te funkcje z odpowiednich zasobów w rozszerzeniu, takich jak manifest.json, foldery lokalizacyjne lub pliki źródłowe JavaScript i HTML.

Odniesienia

tip

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

Wsparcie HackTricks