Overview
In this write‑up I detail how a replayed SAML assertion can grant an attacker access to a WordPress site that uses the WP Simple SAML plugin. The attack exploits the fact that the plugin stores the raw assertion in a cookie without expiration checks, allowing an attacker to capture and reuse it.
Prerequisites
- A WordPress instance with the WP Simple SAML plugin enabled.
- A user account on the IdP capable of generating SAML assertions.
Steps
- Capture the assertion – Use a browser‑network inspector or a tool like
wiresharkto grab the SAML response cookie after login. - Replay the assertion – Set the same cookie value in another session (or in the same browser after clearing). The WP plugin will treat it as valid and log you in.
- Bypass MFA – If MFA is enabled on the IdP, replaying an older assertion still works because the assertion contains the
AuthnStatementwith the original issue time.
Validation technique
The retest used synthetic assertions and checked the temporal conditions before any session was created. A small verifier should reject an assertion outside its validity window and bind it to the expected audience and recipient:
from datetime import datetime, timezone
def assertion_is_fresh(not_before: datetime, not_on_or_after: datetime) -> bool:
now = datetime.now(timezone.utc)
return not_before <= now < not_on_or_after
def require_audience(actual: str, expected: str) -> None:
if actual != expected:
raise ValueError("unexpected SAML audience")
For a safe black-box check, use only a synthetic account and an expired fixture; the expected result is a denial rather than a second session. Log the assertion identifier so the service can reject the same identifier twice.
Mitigation
- Set a short expiration for the SAML cookie (
$samlCookieLifetime = 5;). - Validate
NotOnOrAfterin the assertion on every request. - Use
SameSite=Lax/Strictto mitigate CSRF.