
trova dati sensibili che trapelano dalle istanze ServiceNow.
snowpick è uno scanner di esposizione per ServiceNow che rileva ACL dei widget mal configurati, controlli di accesso alle API REST ed esposizione di dati non autenticata.
ServiceNow espone i dati attraverso due superfici pubbliche comuni: i widget del Service Portal e l'API REST Table. Quando i controlli di accesso su una delle due superfici sono mal configurati, gli utenti non autenticati potrebbero essere in grado di interrogare record interni come ticket incident, directory utente, articoli della knowledge base e voci del catalogo servizi. snowpick testa entrambe le superfici, separa l'esposizione delle righe dall'evidenza basata solo sul conteggio e produce dettagli di riproduzione per una validazione autorizzata.
snowpick ottiene un token di sessione dalla pagina di login di ServiceNow senza alcuna credenziale, quindi lo usa per interrogare le API interne della piattaforma. Controlla due superfici di attacco:
I widget sono piccole applicazioni che alimentano il Service Portal di ServiceNow. Ognuna ha le proprie impostazioni di controllo accessi. Quando queste impostazioni sono troppo permissive, una sessione non autenticata potrebbe essere in grado di recuperare dati dal widget. snowpick prima chiede all'istanza un elenco dei widget installati, quando possibile, poi sonda ciascuno di essi e verifica se restituisce record o solo evidenze basate sul conteggio.
Table API è un'interfaccia REST su /api/now/table/ per interrogare direttamente le tabelle di ServiceNow. Ha controlli di accesso separati da quelli dei widget, quindi un target può essere bloccato su una superficie e aperto sull'altra. snowpick sonda tabelle di alto valore come sys_user, incident e oauth_entity.
Count oracle. A volte un widget o una tabella conferma che esistono record corrispondenti senza restituire le righe. snowpick lo segnala come count_oracle — non è esposizione completa, ma nemmeno pulito, poiché i conteggi possono essere sondati con filtri per dedurre i dati alla cieca.
❯ go install github.com/BishopFox/snowpick@latest
Oppure compila dal sorgente:
❯ git clone https://github.com/BishopFox/snowpick.git
❯ cd snowpick
❯ go build -o snowpick .
Richiede Go 1.21 o successivo.
Punta snowpick su un'istanza ServiceNow. Non sono necessarie credenziali.
❯ 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 scopre automaticamente i widget installati sull'istanza e sonda ciascuno di essi insieme alle sue impostazioni predefinite integrate. I risultati sono classificati per gravità.
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
Passa un file con un hostname per riga. snowpick li scansiona in parallelo.
❯ 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 espone anche un'API REST Table su /api/now/table/ per interrogare direttamente le tabelle backend. Questa è una superficie di controllo accessi separata dai widget. Usa -table-api per verificare entrambe.
❯ 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
Per i risultati dell'API Table, record_count rappresenta il totale dei record esposti quando ServiceNow restituisce un conteggio totale. sample_count indica quanti record snowpick ha raccolto come evidenza limitata.
Per impostazione predefinita, snowpick controlla un set curato di tabelle ad alto valore (ticket incident, record utente, allegati, ecc.). Con -discover, tenta di enumerare i nomi delle tabelle dall'istanza e li controlla anch'essi.
❯ snowpick -target prod.service-now.com -discover -discover-limit 200
Usa -fingerprint per identificare quale release di ServiceNow sta eseguendo il target. Questo aiuta a determinare quali patch di sicurezza sono applicate.
❯ 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
Instrada il traffico attraverso Burp, ZAP o un proxy SOCKS. Usa -rate per limitare le richieste sulle istanze di produzione.
❯ snowpick -target prod.service-now.com -proxy socks5://127.0.0.1:1080 -insecure -rate 200ms
Sopprimi l'output di avanzamento con -quiet. Il JSON va su stdout per impostazione predefinita, o su un file con -output. Il codice di uscita 2 indica che esistono risultati, 1 che uno o più target non hanno potuto essere testati e 0 che la scansione è terminata senza risultati.
❯ snowpick -target prod.service-now.com -quiet -output results.json
❯ echo $?
2
L'output JSON contiene i dettagli dei risultati, inclusi suggerimenti sull'impatto, dettagli delle richieste, record campione limitati quando vengono restituite righe e materiale di riproduzione dove applicabile.
{
"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' ..."
}
}
]
}
I risultati dell'enumerazione delle tabelle appaiono sotto table_findings, e i risultati dell'API REST Table sotto table_api_findings. Usano la stessa struttura hints. I risultati dell'API Table includono anche sia record_count che sample_count quando è disponibile un conteggio totale.
Elenca tutti i target con risultati:
❯ 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
Ottieni i comandi curl di riproduzione per i risultati esposti (copia-incolla per riprodurre):
❯ 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' ...
Mostra i risultati PII con target, vettore, stato, conteggio, campioni e base del suggerimento:
❯ 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
Ottieni i titoli campione per un risultato:
❯ 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
Conta il totale dei record esposti:
❯ 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 include 6 widget predefiniti e 26 coppie tabella/campo predefinite. Puoi aggiungerne altri senza modificare il codice usando file JSON:
Candidati widget (-widgets-file):
[{"id": "WIDGET_ID", "name": "custom-widget"}]
Candidati tabella (-tables-file):
[{"table": "TABLE_NAME", "field": "FIELD_NAME"}]
Questi si uniscono alle impostazioni predefinite integrate. I duplicati vengono rimossi automaticamente.
Questo strumento è fornito esclusivamente per test di sicurezza autorizzati e scopi di ricerca. Sei l'unico responsabile di assicurarti di avere il permesso esplicito di testare qualsiasi istanza ServiceNow che prendi di mira. L'accesso non autorizzato ai sistemi informatici è illegale. Gli autori non sono responsabili per un uso improprio.