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
- An authenticated user sends a request to retrieve their personal information using a JWT token.
- The endpoint accepts the
id
parameter without validating whether it belongs to the user associated with the request’s token. - 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
- 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;
},
};
- 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:
- Validates the user’s ID via the JWT token.
- Allows only authenticated users to access their own data.
- 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.