Skip to main content

Insecure object reference - Personal information

Need

Secure access control and protection of personal information

Context

  • Usage of Dart 2.0 for developing high-performance web and mobile applications
  • Usage of Shelf for managing data persistence and querying in Node.js
  • Usage of shelf_router for routing and handling HTTP requests in a Shelf application

Description

Non compliant code

import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf_router/shelf_router.dart';

class UserController {
final Router _router;

UserController(this._router) {
_router.add('GET', '/user/<id>', _getUser);
_router.add('POST', '/user/<id>', _updateUser);
}

shelf.Response _getUser(shelf.Request request, String id) {
// Fetch user with id from database
var user = getUserFromDB(id);
return shelf.Response.ok(user.toJson());
}

shelf.Response _updateUser(shelf.Request request, String id) {
// Get body of request
var body = jsonDecode(await request.readAsString());
// Update user with id in database
updateUserInDB(id, body);
return shelf.Response.ok('User updated');
}
}

void main() {
final router = Router();

UserController(router);

var handler = const shelf.Pipeline().addMiddleware(shelf.logRequests()).addHandler(router);

io.serve(handler, 'localhost', 8080);
}

The above code is a simple backend server written in Dart using the Shelf framework. It has two endpoints, one for getting a user's information (/user/<id>) and another for updating a user's information (/user/<id>).

The vulnerability lies in the fact that the unique identifier (id) used to distinguish each user can be manipulated by the client. This id is directly passed to the _getUser and _updateUser functions without any form of authentication or authorization.

In the _getUser function, the id is used to fetch a user's information from the database. This means a malicious client could potentially retrieve any user's information by simply changing the id in the request URL.

Similarly, in the _updateUser function, the id is used to update a user's information in the database. This allows a malicious client to modify any user's information by simply changing the id in the request URL and sending a POST request with the new user information.

This is a classic example of an Insecure Direct Object References (IDOR) vulnerability, where the application exposes a reference to an internal implementation object, such as a file, directory, or database key without any proper validation or authorization.

Steps

  • Implement an authentication mechanism to verify the identity of the user making the request. This could be in the form of a token-based system where the user is given a token upon login, and this token is used to authenticate subsequent requests.
  • Implement an authorization mechanism to ensure that the authenticated user has the necessary permissions to perform the requested action. In this case, the user should only be able to update their own information, not that of other users.
  • Validate the user ID in the request against the authenticated user's ID. If they do not match, reject the request.
  • Ensure that the user ID is not directly exposed to the user. Instead, use a session ID or similar mechanism that is mapped to the user ID on the server side.
  • Encrypt sensitive data in transit and at rest. This includes the user ID and any personal information.
  • Regularly update and patch your systems to protect against known vulnerabilities.

Compliant code

import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf_router/shelf_router.dart';
import 'package:shelf_auth/shelf_auth.dart';

class UserController {
final Router _router;

UserController(this._router) {
_router.add('GET', '/user/<id>', _getUser);
_router.add('POST', '/user/<id>', _updateUser);
}

shelf.Response _getUser(shelf.Request request, String id) {
// Fetch user with id from database
var user = getUserFromDB(id);
// Check if the user is authenticated and authorized to access this data
if (request.context['authedUserId'] != id) {
return shelf.Response.forbidden('You are not authorized to access this data');
}
return shelf.Response.ok(user.toJson());
}

shelf.Response _updateUser(shelf.Request request, String id) async {
// Get body of request
var body = jsonDecode(await request.readAsString());
// Check if the user is authenticated and authorized to update this data
if (request.context['authedUserId'] != id) {
return shelf.Response.forbidden('You are not authorized to update this data');
}
// Update user with id in database
updateUserInDB(id, body);
return shelf.Response.ok('User updated');
}
}

void main() {
final router = Router();

var authMiddleware = authenticate([new JwtAuthenticator('sharedSecret', verify: JwtVerify.onDemand)]);

UserController(router);

var handler = const shelf.Pipeline()
.addMiddleware(shelf.logRequests())
.addMiddleware(authMiddleware)
.addHandler(router);

io.serve(handler, 'localhost', 8080);
}

The updated code now includes an authentication middleware that uses JWT (JSON Web Tokens) for user authentication. The shelf_auth package is used to provide this functionality. The JwtAuthenticator is configured with a shared secret key and is set to verify the JWT on every request.

In the getUser and updateUser methods, we now check if the authenticated user's ID matches the ID in the request. If they do not match, a 403 Forbidden response is returned, indicating that the user is not authorized to access or update the requested data.

This ensures that only authenticated users can access and update their own data, thereby fixing the insecure direct object reference vulnerability. The user ID is no longer directly exposed to the user, and sensitive data is encrypted in transit using JWT.

Remember to keep the shared secret key for the JwtAuthenticator secure and regularly update and patch your systems to protect against known vulnerabilities.

References