Vulnerable Behavior

Affected Endpoint

  • Method: findOne (GraphQL or REST)
  • Purpose: Fetch a user by their id.
  • Input: User ID (id passed as a request parameter).

Vulnerable Behavior

  1. An authenticated user sends a request to retrieve their personal information using a JWT token.
  2. The endpoint accepts the id parameter without validating whether it belongs to the user associated with the request’s token.
  3. Consequently, any id can be passed, granting unauthorized access to another user’s information.

Example

GraphQL Request:

graphql

Copiar código

query {
  usersPermissionsUser(id: "2") {
    id
    username
    email
    role {
      name
    }
  }
}

Response:

json

Copiar código

{
  "data": {
    "usersPermissionsUser": {
      "id": "2",
      "username": "another_user",
      "email": "another_user@email.com",
      "role": {
        "name": "Admin"
      }
    }
  }
}

In this example, the authenticated user accesses data for id: 2, belonging to another user.

Impact

  • Exposure of sensitive information (e.g., email, username, permissions).
  • Privilege escalation risks, depending on the exposed user’s roles.

Implemented Solution

Correction Strategy

The solution involves validating the authenticated user via the JWT token, ensuring that only the authenticated user can access their data. A custom method was developed to replace the vulnerable behavior of findOne.

Implemented Code

  1. Custom Method in Strapi API:

javascript

Copiar código

// src/api/users/controllers/users.js
module.exports = {
  async findOne(ctx) {
    const userIdFromToken = ctx.state.user?.id; // Retrieve user ID from JWT

    if (!userIdFromToken) {
      return ctx.forbidden('Invalid token or unauthenticated user.');
    }

    // Ensure the requested ID matches the authenticated user's ID
    const requestedId = ctx.params.id;
    if (requestedId && requestedId !== userIdFromToken.toString()) {
      return ctx.forbidden('You do not have permission to access another user\'s data.');
    }

    // Fetch the authenticated user
    const user = await strapi.query('plugin::users-permissions.user').findOne({
      where: { id: userIdFromToken },
    });

    if (!user) {
      return ctx.notFound('User not found.');
    }

    return user;
  },
};
  1. Modify GraphQL Endpoint (if needed):

Custom resolver to replace the default one:

javascript

Copiar código

module.exports = {
  "query.usersPermissionsUser": {
    resolverOf: "plugins::users-permissions.user.findOne",
    resolver: async (obj, options, ctx) => {
      return await strapi.controllers['users'].findOne(ctx);
    },
  },
};

Outcome

After implementation, the findOne method ensures the following:

  1. Validates the user’s ID via the JWT token.
  2. Allows only authenticated users to access their own data.
  3. Returns an appropriate error (403 Forbidden) for unauthorized access attempts.

Fixed Example:

Invalid Request (attempt to access another user’s data):

graphql

Copiar código

query {
  usersPermissionsUser(id: "2") {
    id
    username
    email
  }
}

Response:

json

Copiar código

{
  "errors": [
    {
      "message": "You do not have permission to access another user's data."
    }
  ]
}

Conclusion

The vulnerability in the findOne method has been successfully mitigated. This solution adds a critical layer of security, ensuring user data is not improperly exposed.

We recommend extending this approach to other endpoints handling sensitive data and conducting additional security tests to validate the solution’s robustness.