# SSO Bypass Password Audit

LLMS index: [llms.txt](/llms.txt)

---

<div class="alert alert-primary border shadow-sm mb-4" role="note">
  <div class="d-flex flex-column flex-lg-row align-items-lg-center justify-content-between gap-3">
    <div>
      <strong>New to psLens?</strong> This page documents one specific report. To see how it runs in the product, what
      the output looks like, and how teams use it in practice, start with a live walkthrough.
    </div>
    <div class="d-flex flex-wrap gap-2">
      <a class="btn btn-sm btn-primary" href="/contact/">Book a Demo</a>
      <a class="btn btn-sm btn-outline-primary" href="/docs/reports/">Browse Reports</a>
    </div>
  </div>
</div><div id="pslens-context-panel" class="card border-info mb-4 d-none">
  <div class="card-header bg-light text-info py-2 fw-bold d-flex align-items-center border-bottom border-info-subtle">
    <i class="bi bi-info-circle-fill me-2"></i>
    <span>Tailored Operational Context</span>
  </div>
  <div class="card-body p-0">
    <ul class="list-group list-group-flush">
      <li id="row-db" class="list-group-item d-flex align-items-center justify-content-between py-2 d-none">
        <strong>Target Database:</strong>
        <span id="ctx-db" class="badge bg-secondary font-monospace">&mdash;</span>
      </li>
      <li id="row-type" class="list-group-item d-flex align-items-center justify-content-between py-2 d-none">
        <strong>Context Type:</strong>
        <span id="ctx-type" class="badge bg-light text-dark border font-monospace text-uppercase">&mdash;</span>
      </li>
      <li id="row-severity" class="list-group-item d-flex align-items-center justify-content-between py-2 d-none">
        <strong>Alert Severity:</strong>
        <span id="ctx-severity" class="badge">&mdash;</span>
      </li>
      <li id="row-time" class="list-group-item d-flex align-items-center justify-content-between py-2 d-none">
        <strong>Triggered Time:</strong>
        <span id="ctx-time" class="text-muted small">&mdash;</span>
      </li>
      <li id="row-details" class="list-group-item py-2 d-none">
        <strong id="label-details" class="d-block mb-1">Firing Context:</strong>
        <code id="ctx-details" class="d-block p-2 bg-light border rounded small"
          style="white-space: pre-wrap; word-break: break-all;">&mdash;</code>
      </li>
    </ul>
  </div>
</div>

<script>
  (function () {
    const params = new URLSearchParams(window.location.search);
    const metadata = params.get('metadata');
    if (!metadata) return;

    try {
      
      const base64 = metadata.replace(/-/g, '+').replace(/_/g, '/');
      const jsonStr = decodeURIComponent(escape(window.atob(base64)));
      const data = JSON.parse(jsonStr);

      if (data) {
        let hasData = false;

        if (data.db) {
          document.getElementById('ctx-db').textContent = data.db;
          document.getElementById('row-db').classList.remove('d-none');
          hasData = true;
        }

        if (data.type) {
          document.getElementById('ctx-type').textContent = data.type;
          document.getElementById('row-type').classList.remove('d-none');
          hasData = true;
        }

        if (data.severity) {
          const severityBadge = document.getElementById('ctx-severity');
          const severity = data.severity.toLowerCase();
          severityBadge.textContent = severity.toUpperCase();
          if (severity === 'critical') {
            severityBadge.className = 'badge bg-danger';
          } else if (severity === 'warning') {
            severityBadge.className = 'badge bg-warning text-dark';
          } else {
            severityBadge.className = 'badge bg-info';
          }
          document.getElementById('row-severity').classList.remove('d-none');
          hasData = true;
        }

        if (data.t) {
          const date = new Date(data.t * 1000);
          document.getElementById('ctx-time').textContent = date.toLocaleString();
          document.getElementById('row-time').classList.remove('d-none');
          hasData = true;
        }

        if (data.details) {
          document.getElementById('ctx-details').textContent = data.details;

          
          const labelDetails = document.getElementById('label-details');
          if (data.type === 'object') {
            labelDetails.textContent = 'Object Metadata Details:';
          } else if (data.type === 'report') {
            labelDetails.textContent = 'Report Description:';
          } else {
            labelDetails.textContent = 'Firing Context:';
          }

          document.getElementById('row-details').classList.remove('d-none');
          hasData = true;
        }

        if (hasData) {
          document.getElementById('pslens-context-panel').classList.remove('d-none');
        }
      }
    } catch (e) {
      console.error('Failed to parse operational context metadata:', e);
    }
  })();
</script>

## SSO Bypass Password Audit

**Report ID:** `security-sso-password-audit`
**Category:** Security

This page documents the SSO Bypass Password Audit report, which identifies native PeopleSoft user passwords stored in the PSOPRDEFN table.

## Purpose

Environments using Single Sign-On (SSO) should not store native passwords in the PeopleSoft database. If `PSOPRDEFN` passwords exist, users can bypass SSO controls—including Multi-Factor Authentication (MFA)—by accessing PeopleSoft backdoor login paths (such as `?cmd=login` query parameters or web service endpoints).

To prevent backdoor access, the fields `OPERPSWD`, `PTOPERPSWDV2`, and `OPERPSWDSALT` must be cleared to a single space, since PeopleSoft does not support database nulls.

**Important:** There are types of users that need to maintain their PeopleSoft password for various reasons that this report will flag.

- Anyone who needs 2-tier PeopleTools Access
  - Developers
  - Administrators
- Special Accounts
  - App Server Accounts
  - API User Accounts for External Integrations
  - SOAP-to-CI Users (maybe)

For any accounts that you know should have a password, you can enter rolename for accounts to avoid. One idea here is to create a new role like `X_CAN_HAVE_PS_PASSWORD` (replace `X_` With your desired prefix) so you can clearly mark these special users and everyone else should have their passwords cleared.

## What It Detects

The report checks the `PSOPRDEFN` table for any row where `OPERPSWD`, `PTOPERPSWDV2`, or `OPERPSWDSALT` is not a single space. Results are grouped into two sections based on account status:

1. **Active Users with Local Passwords:** High risk. These accounts can be logged into directly.
2. **Locked Users with Local Passwords:** Low risk. These accounts are locked, but their passwords should still be cleared.

## Table Queried

### PSOPRDEFN

The primary table containing PeopleSoft operator definitions.

|     Field      |        Description         |           Values           |
| -------------- | -------------------------- | -------------------------- |
| OPRID          | User ID (primary key)      |                            |
| OPRDEFNDESC    | User description           |                            |
| ACCTLOCK       | Account lock status        | `1` = Locked, `0` = Active |
| LASTSIGNONDTTM | Last sign-on date and time |                            |
| OPERPSWD       | Legacy password hash       |                            |
| PTOPERPSWDV2   | Password hash (V2)         |                            |
| OPERPSWDSALT   | Password salt              |                            |

### PSROLEDEFN

Used to validate that the entered excluded roles are real roles.

### PSROLEUSER

Used via a subquery to exclude users assigned to the specified excluded roles.

## Data Flow

```text
1. If excludeRoles is provided, validate each role against PSROLEDEFN
    |
    v
2. Query PSOPRDEFN for rows where OPERPSWD, PTOPERPSWDV2, or OPERPSWDSALT is not ' ' (filtering out users assigned to excluded roles via a PSROLEUSER subquery)
    |
    v
3. Segment users based on ACCTLOCK (0 = Active, 1 = Locked)
    |
    v
4. Compile a bulk SQL update script containing individual UPDATE statements for all affected users
```

## Report Output

The report outputs:

- A summary of active and locked users containing passwords.
- Tables listing the user ID, description, last sign-on date, and active password fields.
- A bulk remediation SQL script with individual update queries.

## Parameters

- **excludeRoles**: Comma-separated list of role names to exclude from the audit (optional). If specified, any user assigned to any of these roles is excluded from the report. The report validates that all entered role names exist in `PSROLEDEFN`.
