
encuentra datos sensibles que se filtran de instancias de ServiceNow.
snowpick es un escáner de exposición de ServiceNow que detecta ACL de widgets mal configuradas, controles de acceso de la API REST y exposición de datos no autenticada.
ServiceNow expone datos a través de dos superficies públicas comunes: los widgets de Service Portal y la API REST de tablas. Cuando los controles de acceso en cualquiera de las dos superficies están mal configurados, los usuarios no autenticados pueden consultar registros internos como tickets de incidentes, directorios de usuarios, artículos de la base de conocimiento y elementos del catálogo de servicios. snowpick prueba ambas superficies, separa la exposición de filas de la evidencia de solo recuento y genera detalles de reproducción para una validación autorizada.
snowpick obtiene un token de sesión de la página de inicio de sesión de ServiceNow sin credenciales y lo usa para consultar las APIs internas de la plataforma. Comprueba dos superficies de ataque:
Widgets son pequeñas aplicaciones que impulsan el Service Portal de ServiceNow. Cada una tiene su propia configuración de control de acceso. Cuando esa configuración es demasiado permisiva, una sesión no autenticada puede recuperar datos del widget. snowpick primero solicita a la instancia una lista de widgets instalados cuando es posible, luego sondea cada uno y comprueba si devuelve registros o evidencia de solo recuento.
API de tablas es una interfaz REST en /api/now/table/ para consultar tablas de ServiceNow directamente. Tiene controles de acceso separados de los widgets, por lo que un objetivo puede estar restringido en una superficie y abierto en la otra. snowpick sondea tablas de alto valor como sys_user, incident y oauth_entity.
Oráculo de recuento. A veces un widget o tabla confirma que existen registros coincidentes sin devolver las filas. snowpick lo marca como count_oracle — no es una exposición completa, pero tampoco está limpio, ya que los recuentos pueden probarse con filtros para inferir datos a ciegas.
❯ go install github.com/BishopFox/snowpick@latest
O compilar desde el código fuente:
❯ git clone https://github.com/BishopFox/snowpick.git
❯ cd snowpick
❯ go build -o snowpick .
Requiere Go 1.21 o posterior.
Apunta snowpick a una instancia de ServiceNow. No se necesitan credenciales.
❯ snowpick -target prod.service-now.com
snowpick v1.0.0 | 1 targets | 6 widgets | proxy direct
[!] prod.service-now.com
| ticket-attachments 272 records [internal_operations:inferred]
[*] 1/1 targets complete, 1 with findings
RESULTS
1 targets scanned in 8.2s | 1 reachable | 1 with findings
EXPOSED prod.service-now.com 272 records [internal_operations:inferred]
| ticket-attachments 272 records [internal_operations:inferred]
| ServiceNow Overview and Open House 'How To.... Videos'
| How to - JIRA and Confluence Access Process
| Updating Your Personal Time Zone in ServiceNow
ATTACK SURFACE
ticket-attachments #################### 272 across 1 target
DATA CLASSIFICATION
OPERATIONS 1 finding across 1 target
snowpick descubre automáticamente los widgets instalados en la instancia y sondea cada uno junto con sus valores predeterminados integrados. Los hallazgos se clasifican por severidad.
Progress markers (left of each host):
[!] findings [+] clean [-] unreachable or no session [*] progress
Summary buckets:
EXPOSED data or a confirmed count came back CLEAN nothing reachable
SKIP no public session (e.g. SSO-only login) DOWN host unreachable
Counts:
272 records total the instance reports as matching
272 records, 10 sampled total, plus the rows snowpick pulled as evidence
Tags:
[pii:observed] a hint, written as category:basis
basis observed = value seen in returned data
inferred = from a table/field/widget name
category pii, credentials, infrastructure,
internal_operations, review_required
[blind oracle] count confirmed but no rows returned (a count_oracle)
[API] finding came from the Table REST API, not a widget
[stats.do] the /stats.do diagnostic endpoint is publicly reachable
Pasa un archivo con un hostname por línea. snowpick los escanea en paralelo.
❯ snowpick -targets hosts.txt -concurrency 5
snowpick v1.0.0 | 6 targets | 6 widgets | proxy direct
[!] prod.service-now.com
| ticket-attachments 272 records [internal_operations:inferred]
[*] 1/6 targets complete, 1 with findings
[+] staging.service-now.com clean
[*] 2/6 targets complete, 1 with findings
[!] dev.service-now.com [stats.do]
| ticket-attachments 61 records [internal_operations:inferred]
[*] 3/6 targets complete, 2 with findings
[+] sandbox.service-now.com clean
[*] 4/6 targets complete, 2 with findings
[-] internal.service-now.com bootstrap failed: /login.do returned 302
[+] test.service-now.com clean
[*] 6/6 targets complete, 2 with findings
ServiceNow también expone una API REST de tablas en /api/now/table/ para consultar tablas backend directamente. Esta es una superficie de control de acceso separada de los widgets. Usa -table-api para comprobar ambas.
❯ snowpick -target prod.service-now.com -table-api
snowpick v1.0.0 | 1 targets | 6 widgets | proxy direct
[!] prod.service-now.com
| [API] cmn_department 1,234 records, 10 sampled [infrastructure:inferred]
[*] 1/1 targets complete, 1 with findings
Para los hallazgos de la API de tablas, record_count representa el total de registros expuestos cuando ServiceNow devuelve un recuento total. sample_count muestra cuántos registros recopiló snowpick como evidencia acotada.
De forma predeterminada, snowpick comprueba un conjunto seleccionado de tablas de alto valor (tickets de incidentes, registros de usuarios, adjuntos, etc.). Con -discover, intenta enumerar los nombres de tablas de la instancia y también los comprueba.
❯ snowpick -target prod.service-now.com -discover -discover-limit 200
Usa -fingerprint para identificar qué versión de ServiceNow está ejecutando el objetivo. Esto ayuda a determinar qué parches de seguridad están aplicados.
❯ snowpick -target prod.service-now.com -fingerprint
snowpick v1.0.0 | 1 targets | 6 widgets | proxy direct
[!] prod.service-now.com [Zurich (glide-zurich-07-01-2025__patch7-02-19-2026)] [stats.do]
| ticket-attachments 61 records [internal_operations:inferred]
[*] 1/1 targets complete, 1 with findings
Enruta el tráfico a través de Burp, ZAP o un proxy SOCKS. Usa -rate para limitar la velocidad de las solicitudes en instancias de producción.
❯ snowpick -target prod.service-now.com -proxy socks5://127.0.0.1:1080 -insecure -rate 200ms
Suprime la salida de progreso con -quiet. El JSON va a stdout por defecto, o a un archivo con -output. El código de salida 2 significa que existen hallazgos, 1 significa que uno o más objetivos no pudieron probarse y 0 significa que el escaneo se completó sin hallazgos.
❯ snowpick -target prod.service-now.com -quiet -output results.json
❯ echo $?
2
La salida JSON contiene detalles de los hallazgos, incluidos indicios de impacto, detalles de solicitud, registros de muestra acotados cuando se devuelven filas y material de reproducción cuando corresponde.
{
"target": "acme.service-now.com",
"reachable": true,
"token_obtained": true,
"stats_do": false,
"widgets": [
{
"id": "ff26f2b1db12220099f93691f0b8f5ce",
"name": "ticket-attachments",
"status": "exposed",
"record_count": 847,
"sample_count": 10,
"hints": [
{
"category": "pii",
"basis": "observed",
"reason": "email pattern in sample content",
"signals": ["content:[email protected]"]
},
{
"category": "internal_operations",
"basis": "inferred",
"reason": "widget name associated with internal operations",
"signals": ["widget:ticket-attachments"]
}
],
"reproduction": {
"method": "POST",
"url": "https://acme.service-now.com/api/now/sp/widget/ff26f2b1db12220099f93691f0b8f5ce",
"headers": {"X-UserToken": "...", "Cookie": "JSESSIONID=..."},
"body": "{\"payload\":{\"start\":0,\"end\":1}}",
"status_code": 200,
"curl": "curl -i -sS -X 'POST' ..."
}
}
]
}
Los hallazgos de enumeración de tablas aparecen bajo table_findings, y los hallazgos de la API REST de tablas bajo table_api_findings. Usan la misma estructura hints. Los hallazgos de la API de tablas también incluyen tanto record_count como sample_count cuando hay un recuento total disponible.
Lista todos los objetivos con hallazgos:
❯ jq -r '.findings[] | select(any(.widgets[]?; .status == "exposed" or .status == "count_oracle") or ((.table_findings // []) | length > 0) or ((.table_api_findings // []) | length > 0)) | .target' results.json
prod.service-now.com
dev.service-now.com
Obtén los comandos curl de reproducción para hallazgos expuestos (copiar y pegar para reproducir):
❯ jq -r '.findings[] | (.widgets[]?, .table_findings[]?, .table_api_findings[]?) | select(.status == "exposed") | .reproduction.curl' results.json
curl -i -sS -X 'POST' 'https://acme.service-now.com/api/now/sp/widget/ff26f...' -H 'X-UserToken: MOCK_TOKEN' ...
Muestra los hallazgos de PII con objetivo, vector, estado, recuento, muestras y base del indicio:
❯ jq -r '.findings[] as $f | ($f.widgets[]?, $f.table_findings[]?, $f.table_api_findings[]?) | select(any(.hints[]?; .category=="pii")) | [$f.target, (.name // (.table + (if .field then "." + .field else "" end))), .status, ((.record_count // .count // 0)|tostring), ((.sample_count // 0)|tostring), ([.hints[]? | select(.category=="pii") | .basis] | unique | join(","))] | @tsv' results.json
acme.service-now.com ticket-attachments exposed 847 10 observed
acme.service-now.com sys_user exposed 250 10 inferred
Obtén los títulos de muestra de un hallazgo:
❯ jq -r '.findings[] | select(.target == "acme.service-now.com") | .widgets[] | select(.name == "ticket-attachments") | .samples[]?.title' results.json
Synthetic ticket attachment title
Example onboarding guide
Cuenta el total de registros expuestos:
❯ jq '[.findings[] | (.widgets[]? | select(.status == "exposed") | .record_count), (.table_findings[]? | select(.status == "exposed") | .count), (.table_api_findings[]? | select(.status == "exposed") | .record_count)] | add // 0' results.json
12896
TARGETS
-target string Single ServiceNow hostname
-targets string File with one hostname per line
OUTPUT
-output string JSON output file (default: stdout)
-quiet Suppress informational output
-version Print version and exit
CONNECTION
-proxy string Proxy URL (http, https, socks5, socks5h)
-timeout duration HTTP timeout per request (default: 15s)
-insecure Skip TLS certificate verification
-rate duration Minimum interval between requests (default: unlimited)
-vendor string X-Vendor header for engagement tracking
SCANNING
-concurrency int Max parallel targets (default: 3)
-table-concurrency int Max parallel table probes per target (default: 5)
-samples int Sample records per exposed finding (default: 10)
WIDGET PROBING
-widgets string Comma-separated widget IDs (overrides defaults)
-widgets-file string JSON file with additional widget candidates
TABLE ENUMERATION
-tables Enumerate via widget-simple-list (default: true)
-tables-file string JSON file with additional table/field candidates
-table-api Probe /api/now/table/ REST API
DISCOVERY
-discover Discover tables dynamically from the instance
-discover-limit int Max tables from discovery (default: 100)
-discover-widgets-limit int
Max widgets from discovery (default: 50)
-fingerprint Detect ServiceNow version and release
snowpick incluye 6 widgets predeterminados y 26 pares de tabla/campo predeterminados. Puedes añadir más sin modificar el código usando archivos JSON:
Candidatos de widgets (-widgets-file):
[{"id": "WIDGET_ID", "name": "custom-widget"}]
Candidatos de tablas (-tables-file):
[{"table": "TABLE_NAME", "field": "FIELD_NAME"}]
Estos se combinan con los valores predeterminados integrados. Los duplicados se eliminan automáticamente.
Esta herramienta se proporciona únicamente para pruebas de seguridad autorizadas y fines de investigación. Eres el único responsable de asegurarte de tener permiso explícito para probar cualquier instancia de ServiceNow a la que apuntes. El acceso no autorizado a sistemas informáticos es ilegal. Los autores no son responsables del mal uso.