
ServiceNow 인스턴스에서 유출되는 민감 데이터를 찾습니다.
snowpick는 잘못 구성된 위젯 ACL, REST API 접근 제어, 인증되지 않은 데이터 노출을 찾는 ServiceNow 노출 스캐너입니다.
ServiceNow는 Service Portal 위젯과 Table REST API라는 두 가지 일반적인 공개 표면을 통해 데이터를 노출합니다. 두 표면 중 하나에서 접근 제어가 잘못 구성되면 인증되지 않은 사용자가 인시던트 티켓, 사용자 디렉터리, 지식 기반 문서, 서비스 카탈로그 항목과 같은 내부 레코드를 조회할 수 있습니다. snowpick은 두 표면을 모두 테스트하고, 행 노출과 개수 전용 증거를 구분하며, 승인된 검증을 위한 재현 세부 정보를 생성합니다.
snowpick은 자격 증명 없이 ServiceNow 로그인 페이지에서 세션 토큰을 얻은 다음, 이를 사용하여 플랫폼의 내부 API를 조회합니다. 두 가지 공격 표면을 확인합니다.
위젯(Widgets) 은 ServiceNow Service Portal을 구동하는 작은 애플리케이션입니다. 각 위젯은 고유한 접근 제어 설정을 가지고 있습니다. 이러한 설정이 너무 허용적으로 설정되면 인증되지 않은 세션이 위젯에서 데이터를 검색할 수 있습니다. snowpick은 가능한 경우 인스턴스에 설치된 위젯 목록을 먼저 요청한 다음, 각 위젯을 프로빙하여 레코드를 반환하는지 또는 개수 전용 증거를 반환하는지 확인합니다.
Table API는 ServiceNow 테이블을 직접 쿼리하기 위한 /api/now/table/의 REST 인터페이스입니다. 위젯과 별개의 접근 제어를 가지므로, 한 표면은 잠겨 있고 다른 표면은 열려 있을 수 있습니다. snowpick은 sys_user, incident, oauth_entity 같은 고가치 테이블을 프로빙합니다.
개수 오라클(Count oracle). 때때로 위젯이나 테이블은 일치하는 레코드가 존재한다는 것을 확인하면서 행을 반환하지 않습니다. snowpick은 이를 count_oracle로 표시합니다. 완전한 노출은 아니지만 깨끗한 상태도 아닙니다. 개수는 필터로 프로빙하여 맹목적으로 데이터를 추론할 수 있기 때문입니다.
❯ go install github.com/BishopFox/snowpick@latest
또는 소스에서 빌드:
❯ git clone https://github.com/BishopFox/snowpick.git
❯ cd snowpick
❯ go build -o snowpick .
Go 1.21 이상이 필요합니다.
snowpick을 ServiceNow 인스턴스에 지정합니다. 자격 증명은 필요 없습니다.
❯ 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은 인스턴스에 설치된 위젯을 자동으로 발견하고, 기본 제공 위젯과 함께 각 위젯을 프로빙합니다. 발견 항목은 심각도별로 정렬됩니다.
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
한 줄에 호스트 이름 하나씩 있는 파일을 전달합니다. snowpick은 이를 병렬로 스캔합니다.
❯ 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는 또한 백엔드 테이블을 직접 쿼리하기 위한 /api/now/table/의 Table REST API를 노출합니다. 이는 위젯과 별개의 접근 제어 표면입니다. -table-api를 사용하여 둘 다 확인하십시오.
❯ 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
Table API 발견 항목의 경우, record_count는 ServiceNow가 총 개수를 반환할 때 노출된 총 레코드를 나타냅니다. sample_count는 snowpick이 제한된 증거로 수집한 레코드 수를 보여줍니다.
기본적으로 snowpick은 큐레이팅된 고가치 테이블 세트(인시던트 티켓, 사용자 레코드, 첨부 파일 등)를 확인합니다. -discover를 사용하면 인스턴스에서 테이블 이름을 열거하려고 시도하고 해당 테이블도 확인합니다.
❯ snowpick -target prod.service-now.com -discover -discover-limit 200
-fingerprint를 사용하여 대상이 실행 중인 ServiceNow 릴리스를 식별합니다. 이는 어떤 보안 패치가 적용되어 있는지 판단하는 데 도움이 됩니다.
❯ 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
Burp, ZAP 또는 SOCKS 프록시를 통해 트래픽을 라우팅합니다. 프로덕션 인스턴스에서 요청을 제한하려면 -rate를 사용하십시오.
❯ snowpick -target prod.service-now.com -proxy socks5://127.0.0.1:1080 -insecure -rate 200ms
-quiet로 진행 출력을 억제합니다. JSON은 기본적으로 stdout으로, 또는 -output으로 파일로 출력됩니다. 종료 코드 2는 발견 항목이 있음을, 1은 하나 이상의 대상을 테스트할 수 없음을, 0은 발견 항목 없이 스캔이 완료되었음을 의미합니다.
❯ snowpick -target prod.service-now.com -quiet -output results.json
❯ echo $?
2
JSON 출력에는 영향 힌트, 요청 세부 정보, 행이 반환될 때의 제한된 샘플 레코드, 해당되는 경우 재현 자료를 포함한 발견 항목 세부 정보가 포함됩니다.
{
"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' ..."
}
}
]
}
테이블 열거 발견 항목은 table_findings 아래에, Table REST API 발견 항목은 table_api_findings 아래에 나타납니다. 둘 다 동일한 hints 구조를 사용합니다. Table API 발견 항목은 총 개수를 사용할 수 있을 때 record_count와 sample_count를 모두 포함합니다.
발견 항목이 있는 모든 대상 나열:
❯ 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
노출된 발견 항목에 대한 재현 curl 명령 가져오기(복사-붙여넣기로 재현):
❯ 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 발견 항목 표시:
❯ 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
발견 항목의 샘플 제목 가져오기:
❯ 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
노출된 총 레코드 수 계산:
❯ 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은 기본 위젯 6개와 기본 테이블/필드 쌍 26개를 포함합니다. 코드를 수정하지 않고 JSON 파일을 사용하여 더 추가할 수 있습니다.
위젯 후보(-widgets-file):
[{"id": "WIDGET_ID", "name": "custom-widget"}]
테이블 후보(-tables-file):
[{"table": "TABLE_NAME", "field": "FIELD_NAME"}]
이들은 기본 제공 값과 병합됩니다. 중복은 자동으로 제거됩니다.
이 도구는 승인된 보안 테스트 및 연구 목적으로만 제공됩니다. 대상으로 하는 모든 ServiceNow 인스턴스를 테스트할 수 있는 명시적 권한이 있는지 확인하는 것은 전적으로 사용자의 책임입니다. 컴퓨터 시스템에 대한 무단 접근은 불법입니다. 저자는 오용에 대해 책임을 지지 않습니다.