← All security articles
HIGHWashington+

ServiceNow Forensic Evidence Collection & Chain of Custody

Domain 11: Incident Response & Forensics·

ServiceNow forensics is platform-level rather than disk-level: your evidence is audit records, transaction logs, system logs, login history and configuration change records, not memory dumps or disk images. Collect them read-only, hash every export, and record who exported what and when — an investigation that cannot prove its evidence was unaltered will not support a regulatory filing or survive a challenge. The scripts below collect without writing anything back to the instance.

What This Is

When a security incident occurs in ServiceNow, the platform's built-in logging and auditing capabilities become the primary source of forensic evidence. Unlike traditional infrastructure forensics (disk images, memory dumps, network captures), ServiceNow forensics focuses on platform-level artifacts: audit records, transaction logs, system logs, login histories, and configuration change records.

Key Forensic Data Sources in ServiceNow

Data Source Table What It Records Retention
Audit Log sys_audit Field-level changes on audited tables (old/new values) Configurable (default: 180 days)
Audit Delete sys_audit_delete Record deletions with who deleted, when, and original values Configurable (default: 180 days)
Transaction Log syslog_transaction HTTP requests: URL, method, user, session, response time, status 7 days (default, often shorter)
System Log syslog Application-level events, errors, script output 7 days (default)
Login History sys_user_session Login/logout events, IP address, session ID, user agent Varies
Security Event Log sysevent System events including security-relevant triggers Configurable
Email Log sys_email_log All email notifications sent including recipients and content Configurable
Update History sys_update_version Record version history with snapshots Varies
Import Log sys_import_log Data import operations from external sources Configurable
Outbound HTTP ecc_queue All MID Server communications and integration payloads 7 days (default)

The Forensic Challenge

ServiceNow is a multi-tenant SaaS platform (for most customers). This means:

  1. No disk-level access — you cannot image the underlying servers
  2. No memory forensics — no access to JVM heap or process memory
  3. No network captures — no packet-level analysis between ServiceNow components
  4. Log retention is aggressive — transaction logs default to 7 days; critical evidence may be purged before investigation begins
  5. Audit coverage is selective — only tables/fields with auditing enabled generate audit records
  6. Admin can disable auditing — a compromised admin account can turn off auditing to cover tracks

Chain of Custody Requirements

For forensic evidence to be admissible in legal proceedings, regulatory investigations, or insurance claims, it must demonstrate:

Requirement Challenge in ServiceNow
Integrity Admins can modify sys_audit records (if ACLs are weak)
Authenticity No cryptographic signing of log entries
Completeness Gaps where auditing was disabled or retention purged records
Preservation No native write-once storage; logs can be deleted
Documentation No built-in chain of custody tracking for exported evidence

Why This Is Dangerous

Attack Scenario: Audit Trail Destruction by Compromised Admin

Precondition: Attacker has gained admin access (via credential compromise, privilege escalation, or SAML bypass).

Attack chain:

  1. Attacker checks current audit configuration to understand what is logged:

    // Enumerate which tables have auditing enabled
    var dict = new GlideRecord('sys_dictionary');
    dict.addQuery('audit', true);
    dict.query();
    var auditedTables = {};
    while (dict.next()) {
        var table = dict.getValue('name');
        if (!auditedTables[table]) {
            auditedTables[table] = 0;
        }
        auditedTables[table]++;
    }
    for (var t in auditedTables) {
        gs.info('Audited: ' + t + ' (' + auditedTables[t] + ' fields)');
    }
    
  2. Attacker disables auditing on critical tables before making malicious changes:

    // Attacker disables audit on sys_user_has_role to hide privilege escalation
    // This change itself IS logged in sys_audit (if sys_dictionary is audited)
    // But many orgs don't audit sys_dictionary changes
    var dict = new GlideRecord('sys_dictionary');
    dict.addQuery('name', 'sys_user_has_role');
    dict.addQuery('audit', true);
    dict.query();
    while (dict.next()) {
        dict.setValue('audit', false);
        dict.update();
    }
    // Now privilege changes to sys_user_has_role are invisible
    
  3. Attacker performs malicious actions (role assignments, data export, backdoor creation) — none are logged because auditing was disabled.

  4. Attacker re-enables auditing to avoid detection by monitoring that checks for disabled audit flags.

  5. The forensic gap: Investigators see audit trail up to the disable point, then nothing, then the trail resumes. The window of unlogged activity is the attack window.

Impact: Complete destruction of forensic evidence for the most critical attack actions. Investigators cannot reconstruct what happened during the blind window.

Attack Scenario: Evidence Tampering via sys_audit Manipulation

Precondition: Attacker has admin access and write access to sys_audit table.

Attack chain:

  1. Attacker queries for their own audit trail:

    // Find all audit records created by the attacker's session
    var audit = new GlideRecord('sys_audit');
    audit.addQuery('user', 'compromised_admin');
    audit.addEncodedQuery('sys_created_onONLast 24 hours@javascript:gs.daysAgoStart(1)@javascript:gs.daysAgoEnd(0)');
    audit.query();
    gs.info('Audit records to clean: ' + audit.getRowCount());
    
  2. If sys_audit has no delete ACL restriction, the attacker deletes incriminating audit records.

  3. Alternative: Attacker modifies audit records to attribute actions to a different user (if the user field on sys_audit is not read-only).

  4. Investigators reviewing the audit trail see a clean history — or worse, see evidence pointing to an innocent user.

Impact: Forensic evidence is tampered with, leading investigators to wrong conclusions or making evidence inadmissible in legal proceedings.

Attack Scenario: Evidence Loss via Default Retention

Precondition: Default log retention settings. Incident discovered 10 days after initial compromise.

Attack chain:

  1. Attacker compromises an account on Day 0. Transaction logs record the malicious login with source IP, user agent, and session ID.

  2. Transaction log retention is 7 days. By Day 8, the login evidence is purged by the scheduled cleanup job.

  3. Incident is discovered on Day 10 via anomalous behavior detection.

  4. Investigators attempt to determine initial access vector but the transaction logs showing the first malicious login are gone. The sys_audit trail shows what changed but not the HTTP-level detail of how the attacker authenticated.

Impact: Critical initial-access evidence is permanently lost. Investigators cannot determine the attack vector, cannot scope the compromise timeline accurately, and cannot provide a definitive root cause for regulatory reporting.

How to Detect

Forensic Readiness Assessment

/*
 * IR-004 Detection Script
 * Assesses forensic evidence collection readiness:
 * audit coverage, log retention, integrity controls,
 * and chain of custody capabilities
 *
 * Run as: Background script with admin role
 * Impact: Read-only, safe for production
 * Versions: Washington+
 */

gs.info('=== IR-004: FORENSIC READINESS ASSESSMENT ===');
gs.info('Scan started: ' + new GlideDateTime().getDisplayValue());
gs.info('');

var totalFindings = 0;

// 1. Check audit coverage on critical security tables
gs.info('--- AUDIT COVERAGE ON CRITICAL TABLES ---');
var criticalTables = [
    'sys_user', 'sys_user_has_role', 'sys_user_group',
    'sys_user_grmember', 'sys_properties', 'sys_security_acl',
    'sys_script', 'sys_script_include', 'sys_ws_operation',
    'sys_rest_message', 'sn_si_incident', 'sys_dictionary',
    'sys_db_object', 'oauth_entity', 'sys_portal_page'
];

for (var i = 0; i < criticalTables.length; i++) {
    var tableName = criticalTables[i];
    var dict = new GlideRecord('sys_dictionary');
    dict.addQuery('name', tableName);
    dict.addQuery('audit', true);
    dict.query();
    var auditedFields = dict.getRowCount();

    // Also check if table-level auditing is enabled
    var tableDict = new GlideRecord('sys_dictionary');
    tableDict.addQuery('name', tableName);
    tableDict.addQuery('element', '');
    tableDict.query();
    var tableAudit = false;
    if (tableDict.next()) {
        tableAudit = tableDict.getValue('audit') === 'true' ||
                     tableDict.getValue('audit') === '1';
    }

    if (!tableAudit && auditedFields === 0) {
        gs.info('[CRITICAL] ' + tableName + ' — NO auditing enabled');
        totalFindings++;
    } else {
        gs.info('[OK] ' + tableName + ' — ' + auditedFields + ' audited fields' +
            (tableAudit ? ' (table-level audit on)' : ''));
    }
}
gs.info('');

// 2. Check sys_audit table size and oldest record
gs.info('--- AUDIT LOG RETENTION ---');
var oldestAudit = new GlideRecord('sys_audit');
oldestAudit.orderBy('sys_created_on');
oldestAudit.setLimit(1);
oldestAudit.query();
if (oldestAudit.next()) {
    var oldest = new GlideDateTime(oldestAudit.getValue('sys_created_on'));
    var now = new GlideDateTime();
    var retentionDays = Math.round(
        (now.getNumericValue() - oldest.getNumericValue()) / 86400000
    );
    gs.info('Oldest audit record: ' + oldest.getDisplayValue() +
        ' (' + retentionDays + ' days ago)');
    if (retentionDays < 90) {
        gs.info('[WARNING] Audit retention is less than 90 days — may not meet regulatory requirements');
        totalFindings++;
    }
    if (retentionDays < 365) {
        gs.info('[INFO] NIS2 and DORA may require 12+ months of audit data for investigations');
    }
}

// Check transaction log retention
var oldestTx = new GlideRecord('syslog_transaction');
if (oldestTx.isValid()) {
    oldestTx.orderBy('sys_created_on');
    oldestTx.setLimit(1);
    oldestTx.query();
    if (oldestTx.next()) {
        var oldestTxDate = new GlideDateTime(oldestTx.getValue('sys_created_on'));
        var nowTx = new GlideDateTime();
        var txRetention = Math.round(
            (nowTx.getNumericValue() - oldestTxDate.getNumericValue()) / 86400000
        );
        gs.info('Oldest transaction log: ' + oldestTxDate.getDisplayValue() +
            ' (' + txRetention + ' days ago)');
        if (txRetention < 30) {
            gs.info('[CRITICAL] Transaction log retention under 30 days — HTTP-level forensics limited');
            totalFindings++;
        }
    }
}
gs.info('');

// 3. Check sys_audit write protection
gs.info('--- AUDIT LOG INTEGRITY CONTROLS ---');
var auditAcl = new GlideRecord('sys_security_acl');
auditAcl.addQuery('name', 'sys_audit');
auditAcl.query();
var hasWriteProtection = false;
var hasDeleteProtection = false;
while (auditAcl.next()) {
    var operation = auditAcl.getValue('operation');
    gs.info('  ACL on sys_audit: operation=' + operation +
        ' | active=' + auditAcl.getValue('active'));
    if (operation === 'write') hasWriteProtection = true;
    if (operation === 'delete') hasDeleteProtection = true;
}
if (!hasWriteProtection) {
    gs.info('[CRITICAL] No write ACL on sys_audit — audit records can be modified');
    totalFindings++;
}
if (!hasDeleteProtection) {
    gs.info('[CRITICAL] No delete ACL on sys_audit — audit records can be deleted');
    totalFindings++;
}
gs.info('');

// 4. Check for recent audit disabling events
gs.info('--- RECENT AUDIT CONFIGURATION CHANGES ---');
var dictAudit = new GlideRecord('sys_audit');
dictAudit.addQuery('tablename', 'sys_dictionary');
dictAudit.addQuery('fieldname', 'audit');
dictAudit.addEncodedQuery('sys_created_onONLast 90 days@javascript:gs.daysAgoStart(90)@javascript:gs.daysAgoEnd(0)');
dictAudit.query();
var auditChanges = dictAudit.getRowCount();
if (auditChanges > 0) {
    gs.info('[WARNING] ' + auditChanges + ' audit configuration changes in last 90 days');
    totalFindings++;
    dictAudit.setLimit(10);
    dictAudit.query();
    while (dictAudit.next()) {
        gs.info('  Table: ' + dictAudit.getValue('documentkey') +
            ' | Changed by: ' + dictAudit.getValue('user') +
            ' | Old: ' + dictAudit.getValue('oldvalue') +
            ' | New: ' + dictAudit.getValue('newvalue') +
            ' | When: ' + dictAudit.getValue('sys_created_on'));
    }
} else {
    gs.info('[OK] No audit configuration changes in last 90 days');
}
gs.info('');

// 5. Check for sys_audit_delete coverage
gs.info('--- DELETION AUDIT COVERAGE ---');
var auditDel = new GlideRecord('sys_audit_delete');
if (auditDel.isValid()) {
    auditDel.addEncodedQuery('sys_created_onONLast 30 days@javascript:gs.daysAgoStart(30)@javascript:gs.daysAgoEnd(0)');
    auditDel.query();
    gs.info('Deletion audit records (last 30 days): ' + auditDel.getRowCount());
} else {
    gs.info('[WARNING] sys_audit_delete table not accessible');
    totalFindings++;
}
gs.info('');

// 6. Check for external log forwarding (SIEM integration)
gs.info('--- EXTERNAL LOG FORWARDING ---');
var syslogDest = new GlideRecord('syslog_destination');
if (syslogDest.isValid()) {
    syslogDest.query();
    var destCount = syslogDest.getRowCount();
    gs.info('Syslog destinations configured: ' + destCount);
    if (destCount === 0) {
        gs.info('[CRITICAL] No external log forwarding — all evidence is inside ServiceNow only');
        gs.info('  If attacker has admin access, they can tamper with ALL evidence');
        totalFindings++;
    }
    while (syslogDest.next()) {
        gs.info('  Destination: ' + syslogDest.getValue('name') +
            ' | Type: ' + syslogDest.getValue('type') +
            ' | Active: ' + syslogDest.getValue('active'));
    }
} else {
    gs.info('[INFO] syslog_destination table not found — check SIEM integration via different method');
}
gs.info('');

// 7. Check login tracking coverage
gs.info('--- LOGIN TRACKING ---');
var sessions = new GlideRecord('sys_user_session');
if (sessions.isValid()) {
    sessions.addEncodedQuery('sys_created_onONLast 7 days@javascript:gs.daysAgoStart(7)@javascript:gs.daysAgoEnd(0)');
    sessions.query();
    gs.info('Login sessions tracked (last 7 days): ' + sessions.getRowCount());
}

// Check if login events are in syslog
var loginLogs = new GlideRecord('syslog');
loginLogs.addEncodedQuery('messageLIKElogin^ORmessageLIKEauthentication^ORmessageLIKELogin');
loginLogs.addEncodedQuery('sys_created_onONLast 1 day@javascript:gs.daysAgoStart(1)@javascript:gs.daysAgoEnd(0)');
loginLogs.query();
gs.info('Authentication log entries (last 24h): ' + loginLogs.getRowCount());
gs.info('');

// Summary
gs.info('=== SUMMARY ===');
gs.info('Total forensic readiness findings: ' + totalFindings);
gs.info('');
gs.info('CRITICAL ACTIONS:');
gs.info('1. Enable auditing on all critical security tables (see list above)');
gs.info('2. Extend sys_audit retention to minimum 365 days');
gs.info('3. Extend syslog_transaction retention to minimum 90 days');
gs.info('4. Add write/delete ACL restrictions on sys_audit and sys_audit_delete');
gs.info('5. Configure external log forwarding to SIEM for tamper-proof evidence');
gs.info('6. Monitor sys_dictionary changes to audit flags via business rule');
gs.info('7. Implement evidence export procedures with hash verification');

Remediation

Step 1: Enable Auditing on All Critical Tables

Tables requiring audit for forensic readiness:

| Priority | Table | Why |
|----------|-------|-----|
| P1 | sys_user | Track account creation, modification, deactivation |
| P1 | sys_user_has_role | Track role assignments and revocations |
| P1 | sys_security_acl | Track ACL changes that affect access control |
| P1 | sys_properties | Track property changes (many control security) |
| P1 | sys_dictionary | Track schema changes including audit flag toggles |
| P1 | sys_script | Track business rule changes |
| P1 | sys_script_include | Track script include modifications |
| P2 | sys_rest_message | Track integration credential changes |
| P2 | oauth_entity | Track OAuth app modifications |
| P2 | sys_user_group | Track group membership changes |
| P2 | sys_user_grmember | Track group membership assignments |
| P2 | sys_ws_operation | Track web service operation changes |
| P3 | sys_update_set | Track update set promotions |
| P3 | sys_attachment | Track attachment uploads/deletions |

Configuration path:
1. Navigate to System Definition > Dictionary
2. Filter by table name
3. Set "Audit" = true on the table record and critical fields
4. Verify by checking sys_audit for new entries

Step 2: Protect Audit Log Integrity

1. Create restrictive ACLs on sys_audit:
   - READ: admin, security_admin roles only
   - WRITE: No one (set condition: false)
   - DELETE: No one (set condition: false)

2. Create restrictive ACLs on sys_audit_delete:
   - Same restrictions as sys_audit

3. Create a business rule on sys_dictionary to alert on audit flag changes:
   - Table: sys_dictionary
   - When: Before update
   - Condition: current.audit.changes()
   - Action: Create security incident + send email to security team

4. Enable sys_dictionary auditing to ensure the business rule cannot be
   disabled without an audit trail

Step 3: Extend Log Retention

Minimum retention for forensic readiness:

| Log Type | Default | Recommended | Regulatory Driver |
|----------|---------|-------------|-------------------|
| sys_audit | 180 days | 365+ days | NIS2 Art.23§4: final report up to 1 month; investigations may extend |
| sys_audit_delete | 180 days | 365+ days | Same as sys_audit |
| syslog_transaction | 7 days | 90+ days | HTTP-level forensics for attack reconstruction |
| syslog | 7 days | 90+ days | Application-level event correlation |
| sys_user_session | Varies | 365+ days | Login forensics for compromise timeline |
| ecc_queue | 7 days | 30+ days | MID Server communication forensics |

Configuration:
1. Navigate to System Scheduler > Scheduled Jobs
2. Find cleanup jobs for each table
3. Modify retention period
4. Monitor database size impact
5. Consider archiving to external storage for long-term retention

Step 4: Configure External Log Forwarding

Forward critical logs to an external SIEM for tamper-proof storage:

1. Configure syslog forwarding:
   - Navigate to System Logs > Syslog Destinations
   - Add SIEM endpoint (Splunk, QRadar, Sentinel, etc.)
   - Select log sources: audit, transaction, security events

2. Use ServiceNow Event Management integration:
   - Forward security events to SIEM in real-time
   - Configure bidirectional sync for incident correlation

3. Implement scheduled export for audit trail backup:
   - Create scheduled job exporting sys_audit to encrypted external storage
   - Include SHA-256 hash of each export file for integrity verification
   - Store hashes separately from data for independent verification

Step 5: Create Evidence Export Procedures

Standard evidence export procedure for investigations:

1. Define scope: time range, tables, users, affected records
2. Export using encoded query to JSON or XML format
3. Generate SHA-256 hash of export file
4. Record export metadata: who exported, when, query used, record count
5. Store export + hash + metadata in evidence management system
6. Two-person rule: second analyst verifies export completeness

Evidence export template fields:
- Case/Incident number
- Export date/time (UTC)
- Exported by (name + role)
- Source table(s)
- Query used
- Record count
- File hash (SHA-256)
- Storage location
- Verification signature

Regulatory Impact

NIS2 Mapping

Article Requirement How Forensic Gaps Violate It Evidence After Fix
Art.23§4 Final report with root cause analysis Insufficient audit coverage and short retention prevent root cause determination 365-day retention, full table auditing, external log backup enabling complete timeline reconstruction

DORA Mapping

Article Requirement How Forensic Gaps Violate It Evidence After Fix
Art.17§3 Root cause analysis for major ICT incidents Missing transaction logs and tampered audit trails prevent accurate root cause analysis Tamper-proof external logs, extended retention, comprehensive audit coverage

ISO 27001:2022 Mapping

Control Requirement How Forensic Gaps Violate It Evidence After Fix
A.5.28 Collection of evidence No chain of custody procedures, no integrity controls on audit logs, inadequate retention Evidence export procedures with hashing, write-protected audit tables, SIEM forwarding for independent evidence store

Expert Notes

Practitioner annotations pending — article content has been technically validated.