← All security articles
HIGHWashington+

Insufficient Audit Logging and Security Event Detection

Domain 5: Security Monitoring & Response·

Probably not enough. ServiceNow ships several logging mechanisms, but out of the box many security-critical events are either not recorded, not retained long enough to cover an investigation, or recorded and never monitored — which is operationally the same as not recorded. The gap that hurts most is retention: by the time a breach is suspected, the window that would have shown the intrusion has usually already rolled off.

What This Is

ServiceNow provides multiple logging mechanisms — sys_audit, syslog, syslog_transaction, sys_security_acl_log, and login tracking tables. However, by default, many security-critical events are either not logged, not retained long enough, or not monitored for anomalies.

Without proper logging, you cannot detect:

ServiceNow Logging Architecture

Log Source What It Captures Default Retention Security Use
sys_audit Field-level changes on audited tables 180 days Detect unauthorized modifications
syslog Application logs, errors, info messages 30 days Detect script errors, security events
syslog_transaction HTTP requests with user, URL, duration 7 days Detect API abuse, bulk extraction
sys_security_acl_log ACL evaluation results (when debug enabled) Not enabled by default Debug ACL issues
sysevent System events triggered by platform 30 days Track async operations
sys_user_session Active user sessions Session lifetime Track concurrent access
login_log / syslog Login attempts (success and failure) 30 days Detect brute force, credential stuffing

Critical Audit Properties

Beyond table-level auditing, these system properties control fundamental audit behavior:

Property Purpose Secure Value Default Impact if Misconfigured
glide.sys.audit_inserts Log INSERT operations in sys_audit true false New admin accounts, role grants, and record creations go unlogged
glide.security.sandbox_no_logging Suppress sandbox violation logging false true Script sandbox bypass attempts are hidden from syslog
glide.identity.security.audit.enabled Log identity/security events true true Identity changes (MFA, SSO) not tracked
glide.sys.activity_using_audit_direct Write activity directly to audit false false Performance impact if enabled, but useful for sensitive tables

Instance-validated finding (Zurich Patch 4): On the tested instance, glide.sys.audit_inserts = false (default) and glide.security.sandbox_no_logging = true (default). This means: (1) when an attacker grants themselves the admin role by inserting a sys_user_has_role record, no audit entry is created for the INSERT — only subsequent UPDATEs would be logged; (2) any script that attempts to break out of the Rhino sandbox produces no syslog entry, allowing an attacker to probe sandbox bypasses silently.

The Problem

Most organizations:

  1. Don't audit the right tables (HR, CMDB, credentials)
  2. Set retention too short for incident investigation (7-30 days)
  3. Don't monitor logs for security anomalies
  4. Don't alert on high-risk events (new admin, bulk API access)
  5. Never review transaction logs to detect data exfiltration
  6. Leave glide.sys.audit_inserts = false — so record creation events are invisible
  7. Leave glide.security.sandbox_no_logging = true — so sandbox violation attempts are hidden

Why This Is Dangerous

Attack Scenario: Undetected Data Exfiltration

Precondition: Transaction logging retention is 7 days (default). No anomaly detection on API usage.

Attack chain:

  1. Compromise: Attacker obtains valid credentials (phishing, password reuse)
  2. Low-and-slow extraction: Attacker extracts 1,000 records per day via Table API over 2 weeks
  3. No alert fires: No anomaly detection on API volume per user
  4. Evidence destroyed: By day 8, the first week's transaction logs are deleted
  5. Discovery: Weeks later, extracted data appears on dark web
  6. Investigation fails: Transaction logs are gone — cannot determine scope of breach

Impact: Without logging, a breach has no forensic trail. Regulatory notification (NIS2: 24h, GDPR: 72h) becomes impossible to scope.

Attack Scenario: Privilege Escalation Without Detection

Precondition: sys_user_has_role table is not audited. No alert on admin role grants.

Attack chain:

  1. Compromised admin account grants admin role to attacker's account
  2. Attacker accesses instance with admin privileges
  3. No audit record of when the role was granted or by whom
  4. When discovered, forensic timeline cannot be established

How to Detect

Instance Security Center Coverage

Instance Security Center (SSC) has limited overlap with Insufficient Audit Logging and Security Event Detection. SSC focuses on platform hardening properties (CSRF, cookies, session, HSP) and does not audit the audit logging configuration itself — i.e., whether specific tables are audited or whether critical audit system properties are set.

What SSC hardening checks may cover (partial overlap):

What SSC does NOT cover (gaps filled by the script below):

Recommendation: SSC cannot substitute for Insufficient Audit Logging and Security Event Detection detection. Run the script below to identify audit logging gaps, then use SSC scan results alongside as complementary evidence for NIS2 Art.21§2(f) effectiveness assessment (see Security Center Configuration and OOTB Security Checks).

Detection Script

/*
 * MON-001 Detection Script
 * Checks audit logging configuration for security-critical tables
 *
 * Run as: Background script with admin role
 * Impact: Read-only, safe for production
 *
 * Security Center gap: SSC does not audit per-table audit configuration,
 * INSERT logging (glide.sys.audit_inserts), sandbox violation logging,
 * or log retention periods. This script is the only way to detect
 * audit logging gaps on security-critical tables.
 */

// Tables that MUST be audited for security
var criticalTables = [
    { table: 'sys_user', reason: 'User account changes' },
    { table: 'sys_user_has_role', reason: 'Role assignments' },
    { table: 'sys_user_group_member', reason: 'Group membership changes' },
    { table: 'sys_security_acl', reason: 'ACL modifications' },
    { table: 'sys_properties', reason: 'System property changes' },
    { table: 'sys_script', reason: 'Business rule changes' },
    { table: 'sys_script_include', reason: 'Script include changes' },
    { table: 'sysauto_script', reason: 'Scheduled job changes' },
    { table: 'hr_case', reason: 'HR case access/changes' },
    { table: 'cmdb_ci', reason: 'CMDB modifications' },
    { table: 'discovery_credentials', reason: 'Discovery credential changes' },
    { table: 'sys_update_set', reason: 'Update set imports' }
];

gs.info('=== MON-001: AUDIT LOGGING CONFIGURATION CHECK ===');

var audited = 0;
var notAudited = 0;

for (var i = 0; i < criticalTables.length; i++) {
    var entry = criticalTables[i];
    var dict = new GlideRecord('sys_dictionary');
    dict.addQuery('name', entry.table);
    dict.addQuery('element', '');
    dict.query();

    var isAudited = false;
    if (dict.next()) {
        isAudited = dict.getValue('audit') === 'true' ||
                    dict.getValue('audit') === '1';
    }

    if (isAudited) {
        audited++;
        gs.info('[OK] ' + entry.table + ' — audited (' + entry.reason + ')');
    } else {
        notAudited++;
        gs.info('[FAIL] ' + entry.table + ' — NOT audited (' + entry.reason + ')');
    }
}

gs.info('');
gs.info('Audited: ' + audited + ' / ' + criticalTables.length);
gs.info('Not audited: ' + notAudited);
gs.info('');

// Audit retention is configured per table in the Audit Management Console
// (security_admin role required). There is no global sys_property for it, so
// nothing here can read it; the review below is manual by necessity.
gs.info('--- AUDIT RETENTION (manual review required) ---');
gs.info('  Navigate to All > Audit Management Console.');
gs.info('  For each security-critical audited table, open Retention tab');
gs.info('  and confirm Automatically Purge Audit Records toggle is set');
gs.info('  with Duration meeting compliance minimum.');
gs.info('  Reference: setup-audit-retention.md (canonical procedure).');

// Transaction log / syslog retention is governed by Table Rotation, not by a
// sys_property. High-volume log tables are managed by the Database Rotation
// plugin (com.snc.db.rotation) with rotation rules in
// All > System Definition > Table Rotations.
gs.info('--- TRANSACTION LOG / SYSLOG RETENTION (manual review required) ---');
gs.info('  Navigate to All > System Definition > Table Rotations.');
gs.info('  For syslog_transaction and syslog, confirm a rotation rule');
gs.info('  exists. Effective retention ≈ Rotations × Duration. Recommend');
gs.info('  ≥ 90 days for incident-investigation capability.');
gs.info('  Reference: c_TableRotation.md');
gs.info('');

// Check critical audit system properties
var auditProps = [
    { prop: 'glide.sys.audit_inserts', expected: 'true', severity: 'CRITICAL',
      desc: 'INSERT operations not logged — new records (admin grants, backdoor accounts) invisible' },
    { prop: 'glide.security.sandbox_no_logging', expected: 'false', severity: 'HIGH',
      desc: 'Sandbox violations suppressed — attacker probing sandbox bypasses silently' },
    { prop: 'glide.identity.security.audit.enabled', expected: 'true', severity: 'HIGH',
      desc: 'Identity/security events not audited' }
];

gs.info('--- AUDIT SYSTEM PROPERTIES ---');
for (var p = 0; p < auditProps.length; p++) {
    var check = auditProps[p];
    var value = gs.getProperty(check.prop, 'NOT SET');
    var pass = value === check.expected;
    gs.info('[' + (pass ? 'OK' : check.severity) + '] ' + check.prop + ' = ' + value +
        (pass ? '' : ' (expected: ' + check.expected + ') → ' + check.desc));
    if (!pass) notAudited++;
}
gs.info('');

gs.info('RECOMMENDATIONS:');
gs.info('1. Enable auditing on all FAIL tables above');
gs.info('2. Set glide.sys.audit_inserts = true (CRITICAL — enables INSERT logging)');
gs.info('3. Set glide.security.sandbox_no_logging = false (enables sandbox violation logging)');
gs.info('4. Set transaction log retention to 90+ days');
gs.info('5. Set audit retention to 365+ days');
gs.info('6. Create alerts for high-risk events (see MON-002)');

Remediation

Step 1: Enable Auditing on Critical Tables

For each table listed above, enable auditing:

Navigation: System Definition > Tables
Search for table name
Check "Audit" checkbox
Save

Or via script:
/*
 * Enable auditing on critical tables
 */

var tablesToAudit = [
    'sys_user', 'sys_user_has_role', 'sys_user_group_member',
    'sys_security_acl', 'sys_properties', 'sys_script',
    'sys_script_include', 'sysauto_script', 'hr_case',
    'cmdb_ci', 'discovery_credentials', 'sys_update_set'
];

for (var i = 0; i < tablesToAudit.length; i++) {
    var dict = new GlideRecord('sys_dictionary');
    dict.addQuery('name', tablesToAudit[i]);
    dict.addQuery('element', '');
    dict.query();

    if (dict.next()) {
        dict.setValue('audit', true);
        dict.update();
        gs.info('Enabled audit on: ' + tablesToAudit[i]);
    }
}

Step 1b: Enable Insert Auditing and Sandbox Logging

These are the most commonly missed audit settings. Both are disabled by default:

Set properties:
  glide.sys.audit_inserts = true             (CRITICAL — log all record INSERTs)
  glide.security.sandbox_no_logging = false  (log sandbox violations to syslog)

Why glide.sys.audit_inserts matters: Without INSERT auditing, the sys_audit table only records UPDATEs and DELETEs. An attacker who creates a new sys_user_has_role record (granting themselves admin) leaves no audit trail — the record simply appears with no forensic evidence of when or by whom it was created. This is validated on a default Zurich instance where this property is false.

Step 2: Extend Log Retention

Audit retention on Zurich is configured per-table via the Audit Management Console UI — there is no global sys_property that controls it, and any script that claims to read one is reading nothing.

Procedure (per setup-audit-retention.md):
  1. Navigate to All > Audit Management Console (role: security_admin)
  2. Select the audited table (e.g., sys_user, sys_user_has_role,
     sys_security_acl, oauth_entity, sa_credential)
  3. Open the Retention tab
  4. Enable "Automatically Purge Audit Records" toggle
  5. Set Duration to satisfy compliance minimum (typically 365 days
     for SOX/NIS2/DORA; verify your specific framework requirement)
  6. Save

For syslog and syslog_transaction retention (per c_TableRotation.md
+ t_ApplyTableRotation.md):
  1. Confirm the Database Rotations plugin (com.snc.db.rotation) is
     active.
  2. Navigate to All > System Definition > Table Rotations (admin).
  3. Locate or create a rotation rule for syslog_transaction
     (and syslog). Set Rotations × Duration to give ≥ 90 days
     effective retention for forensic capability.
  4. Engage ServiceNow Customer Service before modifying rotation
     rules on out-of-the-box sys_-prefixed tables
     (per t_ApplyTableRotation.md guidance).

There is no sys_property for transaction-log or syslog retention.
The mechanism is Table Rotation; a property read here returns nothing.

Step 3: Create Security Event Alerts

See → Missing Security Alerts for High-Risk Events for specific alert configurations.

Post-Remediation Verification

Re-run the detection script above. All critical tables should show as audited. Create a test change (e.g., update a user record) and verify the sys_audit record is created.

Regulatory Impact

NIS2 Mapping

Article Requirement How This Violates It Evidence After Fix
Art.21§2(a) Risk analysis and IS security Cannot assess risk without visibility into security events Audit logging enabled, retention policy documented
Art.21§2(b) Incident handling Cannot detect or investigate incidents without logs Log-based alerting, 365-day retention, forensic capability
Art.23§1 Incident notification (24h) Cannot scope incident for notification without forensic data Transaction and audit logs available for investigation

DORA Mapping

Article Requirement How This Violates It Evidence After Fix
Art.9§4(a) Mechanisms to detect ICT anomalous activities No anomaly detection without baseline logging Log analysis, anomaly alerts, baseline established
Art.17§1 ICT incident classification Cannot classify incident severity without impact data from logs Comprehensive logging enables accurate incident classification

ISO 27001:2022 Mapping

Control Requirement How This Violates It Evidence After Fix
A.8.15 Logging Security events not logged or retained adequately Audit enabled on critical tables, retention policy met
A.8.16 Monitoring activities No monitoring of security-relevant events Alerts configured, log review process established

GDPR Mapping

Article Requirement How This Violates It Impact
Art.30 Records of processing activities Cannot demonstrate who accessed personal data without audit logs Audit logging on PII tables provides access evidence for ROPA

Expert Notes

Practitioner annotations pending — article content has been technically validated.