| Title | Leantime leantime v3.9.5 Cross-Site Request Forgery |
|---|
| Description | ### Summary
The OIDC authentication callback does not verify the `state` parameter. The
verification function always returns `true` without checking the value against
what was stored in the session. An attacker who can lure a victim to a crafted
URL can force the victim's browser session to be authenticated as the attacker's
OIDC identity (login CSRF).
### Details
The state parameter in OAuth 2.0 / OIDC is the CSRF protection mechanism for
the authorization code flow. After generating a state value during login
initiation, the application must verify that the state returned by the IdP
matches the stored value before accepting the authorization code.
In `app/Domain/Oidc/Services/Oidc.php` the verification method is a stub:
```php
// line 557-560
private function verifyState(string $state): bool
{
// TODO
return true;
}
```
The callback handler at `app/Domain/Oidc/Controllers/Callback.php` passes the
URL-supplied `state` to `callback()`:
```php
// line 26-30
$code = $_GET['code'];
$state = $_GET['state'];
return $this->oidc->callback($code, $state);
```
And `Oidc::callback()` calls `verifyState()` but the check is meaningless:
```php
// line 160-162
if (! $this->verifyState($state)) {
$this->displayError('oidc.error.invalidState');
}
```
The callback route (`/oidc/callback`) is listed in `publicActions` in
`app/Core/Middleware/AuthCheck.php` (line 37), so no existing session is
required to reach it. Any caller can supply an arbitrary `code` and `state`
and the application will complete the token exchange and create a session.
### PoC
Prerequisites: OIDC is configured and enabled. The attacker has an OIDC
identity that Leantime will accept (either an existing account or auto-creation
is enabled).
1. Attacker initiates OIDC login from their own browser. The IdP redirects back
to the attacker's browser at:
`https://<leantime>/oidc/callback?code=<attacker_code>&state=<attacker_state>`
2. The attacker captures this URL before the browser follows the redirect (e.g.,
by intercepting the response or using a page that logs the redirect).
3. The attacker delivers the captured URL to the victim (phishing email, injected
link, etc.).
4. The victim's browser fetches:
`https://<leantime>/oidc/callback?code=<attacker_code>&state=anything`
5. Leantime calls the IdP token endpoint with the code, receives the attacker's
identity tokens, and creates a session for the victim authenticated as the
attacker.
6. The victim is now logged in as the attacker. Any subsequent actions the victim
takes (viewing projects, uploading files, entering data) occur in the
attacker's account context.
### Impact
An unauthenticated attacker who can trick a user into visiting a crafted URL can
force that user's session to be bound to the attacker's OIDC identity. This
enables:
- Exfiltration of data the victim enters after the login (files uploaded,
comments written, tickets created all land in the attacker's account).
- Social engineering escalation: if the attacker's account has been pre-configured
with attacker-controlled content, the victim is exposed to that content while
believing they are logged in to their own account.
Affects any Leantime deployment with OIDC authentication enabled. |
|---|
| Source | ⚠️ https://github.com/Leantime/leantime/issues/3535 |
|---|
| User | geochen (UID 78995) |
|---|
| Submission | 06/20/2026 16:53 (1 month ago) |
|---|
| Moderation | 08/02/2026 12:22 (1 month later) |
|---|
| Status | Duplicate |
|---|
| VulDB entry | 376545 [Leantime up to 3.4.4 OIDC Login verifyState cross-site request forgery] |
|---|
| Points | 0 |
|---|