
Finde sensible Daten, die aus ServiceNow-Instanzen leaken.
snowpick ist ein ServiceNow-Expositionsscanner, der falsch konfigurierte Widget-ACLs, REST-API-Zugriffskontrollen und nicht authentifizierte Datenexposition erkennt.
ServiceNow stellt Daten über zwei häufige öffentliche Oberflächen bereit: Service-Portal-Widgets und die Tabellen-REST-API. Wenn die Zugriffskontrollen auf einer der beiden Oberflächen falsch konfiguriert sind, können nicht authentifizierte Benutzer möglicherweise interne Datensätze abfragen, wie z. B. Incident-Tickets, Benutzerverzeichnisse, Knowledge-Base-Artikel und Servicekatalog-Elemente. snowpick testet beide Oberflächen, trennt Zeilenexposition von reiner Zählevidenz und erzeugt Reproduktionsdetails für autorisierte Validierung.
snowpick erhält ohne Anmeldeinformationen ein Sitzungstoken von der ServiceNow-Anmeldeseite und verwendet es anschließend, um die internen APIs der Plattform abzufragen. Es prüft zwei Angriffsflächen:
Widgets sind kleine Anwendungen, die das ServiceNow Service Portal betreiben. Jedes hat seine eigenen Zugriffskontroll-Einstellungen. Wenn diese Einstellungen zu permissiv sind, kann eine nicht authentifizierte Sitzung möglicherweise Daten aus dem Widget abrufen. snowpick fragt die Instanz zunächst, wo möglich, nach einer Liste installierter Widgets, testet dann jedes einzelne und prüft, ob es Datensätze oder nur Zählevidenzen zurückgibt.
Table API ist eine REST-Schnittstelle unter /api/now/table/ zum direkten Abfragen von ServiceNow-Tabellen. Sie hat von den Widgets getrennte Zugriffskontrollen, sodass ein Ziel auf der einen Oberfläche abgesichert und auf der anderen offen sein kann. snowpick testet hochwertige Tabellen wie sys_user, incident und oauth_entity.
Count-Orakel. Manchmal bestätigt ein Widget oder eine Tabelle, dass übereinstimmende Datensätze existieren, ohne die Zeilen zurückzugeben. snowpick kennzeichnet dies als count_oracle – keine vollständige Exposition, aber auch nicht sauber, da Zähler mit Filtern abgefragt werden können, um Daten blind abzuleiten.
❯ go install github.com/BishopFox/snowpick@latest
Oder aus dem Quellcode bauen:
❯ git clone https://github.com/BishopFox/snowpick.git
❯ cd snowpick
❯ go build -o snowpick .
Erfordert Go 1.21 oder höher.
Richten Sie snowpick auf eine ServiceNow-Instanz. Keine Anmeldeinformationen erforderlich.
❯ 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
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
Übergeben Sie eine Datei mit einem Hostnamen pro Zeile. snowpick scannt sie parallel.
❯ 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 stellt außerdem eine Tabellen-REST-API unter /api/now/table/ zum direkten Abfragen von Backend-Tabellen bereit. Dies ist eine von den Widgets getrennte Zugriffskontroll-Oberfläche. Verwenden Sie -table-api, um beide zu prüfen.
❯ 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
Bei Table-API-Ergebnissen stellt record_count die insgesamt exponierten Datensätze dar, wenn ServiceNow eine Gesamtzahl zurückgibt. sample_count zeigt, wie viele Datensätze snowpick als begrenzte Evidenz gesammelt hat.
Standardmäßig prüft snowpick eine kuratierte Auswahl hochwertiger Tabellen (Incident-Tickets, Benutzerdatensätze, Anhänge usw.). Mit -discover versucht es, Tabellennamen aus der Instanz aufzulisten und auch diese zu prüfen.
❯ snowpick -target prod.service-now.com -discover -discover-limit 200
Verwenden Sie -fingerprint, um zu identifizieren, welche ServiceNow-Version das Ziel ausführt. Dies hilft festzustellen, welche Sicherheitspatches installiert sind.
❯ 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
Leiten Sie den Datenverkehr über Burp, ZAP oder einen SOCKS-Proxy. Verwenden Sie -rate, um Anforderungen auf Produktionsinstanzen zu drosseln.
❯ snowpick -target prod.service-now.com -proxy socks5://127.0.0.1:1080 -insecure -rate 200ms
Unterdrücken Sie die Fortschrittsausgabe mit -quiet. JSON geht standardmäßig an stdout oder mit -output in eine Datei. Exit-Code 2 bedeutet, dass Ergebnisse vorhanden sind, 1, dass ein oder mehrere Ziele nicht getestet werden konnten, und 0, dass der Scan ohne Ergebnisse abgeschlossen wurde.
❯ snowpick -target prod.service-now.com -quiet -output results.json
❯ echo $?
2
Die JSON-Ausgabe enthält Ergebnisdetails, einschließlich Auswirkungshinweisen, Anforderungsdetails, begrenzter Beispieldatensätze, wenn Zeilen zurückgegeben werden, und Reproduktionsmaterial, sofern zutreffend.
{
"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' ..."
}
}
]
}
Ergebnisse der Tabellenenumeration erscheinen unter table_findings, und Ergebnisse der Tabellen-REST-API erscheinen unter table_api_findings. Sie verwenden dieselbe hints-Struktur. Table-API-Ergebnisse enthalten außerdem sowohl record_count als auch sample_count, wenn eine Gesamtzahl verfügbar ist.
Alle Ziele mit Ergebnissen auflisten:
❯ 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
Reproduktions-curl-Befehle für exponierte Ergebnisse abrufen (zum Reproduzieren kopieren und einfügen):
❯ 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' ...
PII-Ergebnisse mit Ziel, Vektor, Status, Anzahl, Stichproben und Hinweisbasis anzeigen:
❯ 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
Beispieltitel für ein Ergebnis abrufen:
❯ 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
Gesamtzahl exponierter Datensätze zählen:
❯ 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 wird mit 6 Standard-Widgets und 26 Standard-Tabellen-/Feldpaaren ausgeliefert. Sie können ohne Codeänderungen weitere hinzufügen, indem Sie JSON-Dateien verwenden:
Widget-Kandidaten (-widgets-file):
[{"id": "WIDGET_ID", "name": "custom-widget"}]
Tabellen-Kandidaten (-tables-file):
[{"table": "TABLE_NAME", "field": "FIELD_NAME"}]
Diese werden mit den integrierten Standardwerten zusammengeführt. Duplikate werden automatisch entfernt.
Dieses Tool wird ausschließlich für autorisierte Sicherheitstests und Forschungszwecke bereitgestellt. Sie sind allein dafür verantwortlich, sicherzustellen, dass Sie über eine ausdrückliche Genehmigung zum Testen jeder ServiceNow-Instanz verfügen, die Sie anvisieren. Unbefugter Zugriff auf Computersysteme ist illegal. Die Autoren haften nicht für Missbrauch.