
encontre dados sensíveis vazando de instâncias ServiceNow.
snowpick é um scanner de exposição do ServiceNow que identifica ACLs de widgets mal configuradas, controles de acesso da API REST e exposição de dados não autenticada.
O ServiceNow expõe dados por meio de duas superfícies públicas comuns: os widgets do Service Portal e a Table REST API. Quando os controles de acesso em qualquer uma dessas superfícies estão mal configurados, usuários não autenticados podem consultar registros internos, como chamados de incidentes, diretórios de usuários, artigos da base de conhecimento e itens do catálogo de serviços. O snowpick testa ambas as superfícies, separa a exposição de registros da evidência apenas de contagem e produz detalhes de reprodução para validação autorizada.
O snowpick obtém um token de sessão da página de login do ServiceNow sem nenhuma credencial e o utiliza para consultar as APIs internas da plataforma. Ele verifica duas superfícies de ataque:
Widgets são pequenos aplicativos que alimentam o Service Portal do ServiceNow. Cada um tem suas próprias configurações de controle de acesso. Quando essas configurações são permissivas demais, uma sessão não autenticada pode recuperar dados do widget. Primeiro, o snowpick solicita à instância uma lista dos widgets instalados, quando possível; em seguida, testa cada um e verifica se ele retorna registros ou evidência apenas de contagem.
Table API é uma interface REST em /api/now/table/ para consultar tabelas do ServiceNow diretamente. Ela possui controles de acesso separados dos widgets, portanto um alvo pode estar protegido em uma superfície e aberto na outra. O snowpick testa tabelas de alto valor como sys_user, incident e oauth_entity.
Oráculo de contagem. Às vezes, um widget ou tabela confirma que existem registros correspondentes sem retornar as linhas. O snowpick marca isso como count_oracle — não é exposição completa, mas também não está limpo, já que as contagens podem ser sondadas com filtros para inferir dados às cegas.
❯ go install github.com/BishopFox/snowpick@latest
Ou compile a partir do código-fonte:
❯ git clone https://github.com/BishopFox/snowpick.git
❯ cd snowpick
❯ go build -o snowpick .
Requer Go 1.21 ou posterior.
Aponte o snowpick para uma instância do ServiceNow. Nenhuma credencial é necessária.
❯ 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
O snowpick descobre automaticamente os widgets instalados na instância e testa cada um junto com seus padrões integrados. Os achados são classificados por severidade.
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
Passe um arquivo com um hostname por linha. O snowpick faz a varredura deles em 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
O ServiceNow também expõe uma Table REST API em /api/now/table/ para consultar tabelas de backend diretamente. Essa é uma superfície de controle de acesso separada dos widgets. Use -table-api para verificar 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 achados da Table API, record_count representa o total de registros expostos quando o ServiceNow retorna uma contagem total. sample_count mostra quantos registros o snowpick coletou como evidência limitada.
Por padrão, o snowpick verifica um conjunto selecionado de tabelas de alto valor (chamados de incidentes, registros de usuários, anexos etc.). Com -discover, ele tenta enumerar os nomes das tabelas da instância e também verifica essas tabelas.
❯ snowpick -target prod.service-now.com -discover -discover-limit 200
Use -fingerprint para identificar qual versão do ServiceNow o alvo está executando. Isso ajuda a determinar quais correções de segurança estão aplicadas.
❯ 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
Direcione o tráfego pelo Burp, ZAP ou um proxy SOCKS. Use -rate para limitar as requisições em instâncias de produção.
❯ snowpick -target prod.service-now.com -proxy socks5://127.0.0.1:1080 -insecure -rate 200ms
Suprima a saída de progresso com -quiet. O JSON vai para o stdout por padrão, ou para um arquivo com -output. O código de saída 2 significa que existem achados, 1 significa que um ou mais alvos não puderam ser testados e 0 significa que a varredura foi concluída sem achados.
❯ snowpick -target prod.service-now.com -quiet -output results.json
❯ echo $?
2
A saída JSON contém os detalhes dos achados, incluindo dicas de impacto, detalhes das requisições, registros de amostra limitados quando as linhas são retornadas e material de reprodução quando aplicável.
{
"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' ..."
}
}
]
}
Os achados de enumeração de tabelas aparecem em table_findings, e os achados da Table REST API aparecem em table_api_findings. Eles usam a mesma estrutura de hints. Os achados da Table API também incluem record_count e sample_count quando uma contagem total está disponível.
Liste todos os alvos com achados:
❯ 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
Obtenha comandos curl de reprodução para achados expostos (copie e cole para reproduzir):
❯ 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' ...
Mostre achados de PII com alvo, vetor, status, contagem, amostras e base da dica:
❯ 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
Obtenha títulos de amostra para um achado:
❯ 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
Conte o total de registros expostos:
❯ 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
O snowpick vem com 6 widgets padrão e 26 pares de tabela/campo padrão. Você pode adicionar mais sem modificar o código usando arquivos JSON:
Candidatos de widgets (-widgets-file):
[{"id": "WIDGET_ID", "name": "custom-widget"}]
Candidatos de tabelas (-tables-file):
[{"table": "TABLE_NAME", "field": "FIELD_NAME"}]
Eles são mesclados com os padrões integrados. Duplicatas são removidas automaticamente.
Esta ferramenta é fornecida apenas para testes de segurança autorizados e fins de pesquisa. Você é o único responsável por garantir que possui permissão explícita para testar qualquer instância do ServiceNow que escolher como alvo. O acesso não autorizado a sistemas de computador é ilegal. Os autores não são responsáveis pelo uso indevido.