← All KB Checks
HIGHScriptOnlyCheck

External Auth — Disable Local Login

nowisor-external-auth-policy··Source on GitHub →

External Auth — Disable Local Login

This page describes the nowisor-external-auth-policy check from the nowisor Instance Scan Pack v1.0.0. The check first detects whether SSO is configured on the instance (via active records in sso_properties or the glide.authenticate.sso.enabled property). If SSO is not active, the check produces no finding — local login is the only authentication path, so there is nothing to disable. If SSO is active, the check audits glide.authentication.external.disable_local_login and produces a finding when the value is not true.

What this finding means

Your ServiceNow instance has SSO configured — and at the same time accepts local-credential authentication. The system property glide.authentication.external.disable_local_login is either not true or has been removed from sys_properties. The check's SSO-detection logic confirmed that at least one SSO provider is active before flagging the finding; on an instance without SSO, the check exits silently.

The risk is structural. When SSO is configured but local login remains enabled, your authentication posture has two layers operating in parallel:

Without disable_local_login = true, the local layer is a bypass path around the SSO layer. Every control you invested in at the IdP — MFA, geographic restrictions, device posture, session monitoring — is optional from the attacker's perspective. They simply use the bypass.

The property defaults to "not set" on a fresh Zurich Patch 6 instance. Production deployments are expected to enable it explicitly as part of SSO go-live hardening.

Why it matters for your compliance

This is the canonical "second path defeats the first path" finding. Regulators and auditors specifically look for it once SSO is in scope.

NIS2 Article 21§2(j) — identity and access management. NIS2's identity-management expectations are layered: organizations should implement MFA or continuous authentication, AND should ensure that all access paths are subject to those controls consistently. A local-login path that bypasses IdP-enforced controls violates the consistency requirement, even if the IdP-enforced controls themselves are sound.

ISO 27001 A.5.16 — identity management. The control text covers the full identity lifecycle, with strong language about ensuring identities are managed authoritatively. When local credentials persist alongside SSO, the platform has two parallel identity systems — one managed by the IdP, one managed by the platform's sys_user.user_password table. ISO auditors increasingly probe this gap.

DORA Article 9's authentication requirements route through MFA on this check rather than appearing as a direct mapping. When SSO + IdP-enforced MFA is the entity's authentication baseline, the local-login bypass becomes the path that defeats MFA — so DORA Article 9§4(b) (authentication mechanisms) lands on this finding via the nowisor-mfa-enforcement companion. The mapping is intentional: the manifest's framework_mappings carry the direct controls, and the body-text framing covers the routed-through-companion relationships.

Severity 2 (high) reflects the dependence on a second factor — the bypass requires either a leaked local password or a successful credential-stuffing attempt against local accounts. Severity 1 would require the bypass to be exploitable on its own; in practice, this finding is paired with credential exposure to produce the breach.

The attack path

The path runs through the /side_door.do endpoint and equivalents — well-documented in incident reports as the "I broke SSO, let me in" emergency access surface.

Step 1 — Identify the instance + the bypass. The attacker has a target instance URL and knows ServiceNow exposes alternative auth endpoints. They probe https://yourinstance.service-now.com/side_door.do or hit the standard /login.do and observe that local credentials are still accepted (the login form has a password field alongside the SSO button).

Step 2 — Acquire local credentials. The attacker uses a leaked-password list (same source as the MFA bypass attack), filtered to your organization's user-naming convention. Most leaked passwords fail — the SSO-only users don't have local passwords, or have rotated them. A few succeed: typically the legacy admin accounts created before SSO migration, the integration users with passwords still in sys_user.user_password, or the "emergency break-glass" admin everyone forgot was still configured.

Step 3 — Authenticate via local. The attacker submits the credentials to /login.do (or /side_door.do) directly, bypassing the SSO redirect. The platform authenticates them via the local-password path. No IdP involvement, no IdP MFA challenge, no IdP audit trail. The session is established and the attacker has whatever roles the bypassed account holds.

Step 4 — Operate. From the attacker's view, this is indistinguishable from a normal session. They have the role grants, they can navigate the UI, they can call the API. From the IdP's view, no authentication happened — there's no SAML response logged, no MFA challenge issued, no anomaly to alert on. From ServiceNow's view, the user authenticated normally. The misalignment between the two systems is precisely what allows the bypass to remain invisible.

The defense is the disable-local-login property. With it set, the local password path is closed. Authentication must go through the IdP. The leaked-password list becomes useless.

How to fix it

Three steps, in order:

Step 1 — Confirm SSO is working end-to-end before disabling local login. If you disable local login and SSO has a configuration issue, no one can log in. Verify the SSO flow with at least three users (one admin, two non-admins) before proceeding.

Step 2 — Establish a break-glass account. Keep exactly one local-credential admin account for emergency access in case SSO breaks. The account should have:

Document the break-glass procedure in your runbook so an on-call engineer can use it during an SSO outage without re-inventing the path.

Step 3 — Enable the property.

glide.authentication.external.disable_local_login = true

Apply via System Properties → glide.authentication.external.disable_local_login. The change is immediate. Subsequent local-login attempts will be rejected, and the platform will redirect to the SSO provider for all authentication.

After the change, audit sys_user.user_password and clear it for SSO-only users (users who should never have a local password). This prevents an attacker who finds a way to bypass the property (e.g., through a future vulnerability) from finding a usable password to exploit.

How to verify the fix

Run this Background Script after applying the property change:

/*
 * Verify nowisor-external-auth-policy
 * Read-only, safe for production.
 * Confirms SSO is active AND local login is disabled.
 */
(function verifyExternalAuth() {
    var SENTINEL = '__NOT_REGISTERED__';

    // 1. Confirm SSO is active (precondition for the property's relevance)
    var ssoActive = false;
    try {
        var ssoGr = new GlideRecord('sso_properties');
        ssoGr.addQuery('active', true);
        ssoGr.query();
        if (ssoGr.getRowCount() > 0) ssoActive = true;
    } catch (e) {}
    var ssoProp = gs.getProperty('glide.authenticate.sso.enabled', SENTINEL);
    gs.print('SSO detection:');
    gs.print('  sso_properties active rows: ' + (ssoActive ? 'yes' : 'no'));
    gs.print('  glide.authenticate.sso.enabled = ' + ssoProp);
    if (!ssoActive && ssoProp !== 'true') {
        gs.print('[INFO] SSO is not active. This check is informational-only here.');
        return;
    }

    // 2. Audit disable_local_login
    var prop = 'glide.authentication.external.disable_local_login';
    var value = gs.getProperty(prop, SENTINEL);

    gs.print('');
    if (value === SENTINEL) {
        gs.print('[FAIL] ' + prop + ' is NOT REGISTERED. Local login still permitted.');
    } else if (value !== 'true') {
        gs.print('[FAIL] ' + prop + ' = "' + value + '" (expected "true").');
    } else {
        gs.print('[PASS] ' + prop + ' = true. Local login disabled.');
    }

    // 3. Break-glass account count (informational)
    gs.print('');
    var localPasswordHolders = new GlideAggregate('sys_user');
    localPasswordHolders.addQuery('active', true);
    localPasswordHolders.addQuery('user_password', '!=', '');
    localPasswordHolders.addAggregate('COUNT');
    localPasswordHolders.query();
    if (localPasswordHolders.next()) {
        gs.print(
            'Active users with non-empty user_password: ' +
                localPasswordHolders.getAggregate('COUNT') +
                ' (target: 1 break-glass admin)'
        );
    }
})();

If the script reports "SSO is not active," the finding does not apply — your instance does not have an SSO provider configured, and local login is the legitimate authentication path. If SSO is active and the property is correctly true, the finding is resolved.

What to do next

The local-login bypass is one of several "second path defeats the first" patterns. Audit the related findings:

For organizations operating under DORA or NIS2 with SSO, the nowisor advisor product is being built to surface the local-login finding alongside the alternative-auth-endpoint audit (/side_door.do, /nav_to.do, the API surface) into a single "IdP coexistence" view — showing every path an attacker can use to bypass your IdP's controls. Check your IdP coexistence with the advisor →