Skip to main content

Insecure deserialization

Need

Secure deserialization process

Context

  • Usage of Dart 2.0 for building scalable and efficient applications
  • Usage of dart:convert for encoding and decoding data in Dart
  • Usage of Shelf for building web applications in Dart

Description

Non compliant code

import 'dart:convert';
import 'package:shelf/shelf.dart';

class User {
final String username;
final String password;

User(this.username, this.password);

User.fromJson(Map<String, dynamic> json)
: username = json['username'],
password = json['password'];

Map<String, dynamic> toJson() =>
{
'username': username,
'password': password,
};
}

Response handleRequest(Request request) async {
var body = await request.readAsString();
var data = jsonDecode(body);
var user = User.fromJson(data);

// ... rest of the code
}

In the above code, the handleRequest function reads the request body as a string and then deserializes the JSON data into a Dart object. This is done using the jsonDecode function which returns a Map<String, dynamic> object. This map is then passed to the User.fromJson constructor to create a User object.

The vulnerability lies in the fact that there is no validation of the content of the deserialized object. The User.fromJson constructor blindly trusts that the provided map contains the correct keys and values. If an attacker were to send a request with additional keys in the JSON object or with values of the wrong type, this could lead to unexpected behavior, crashes, or even security vulnerabilities.

For example, an attacker could send a request with a password value that is not a string. When the User constructor tries to assign this value to the password field (which expects a string), a runtime error will occur. This could lead to a Denial of Service (DoS) attack if the server crashes.

Furthermore, because the User class includes sensitive information (the password field), insecure deserialization could potentially allow an attacker to inject malicious data or extract sensitive data.

Insecure deserialization vulnerabilities can be mitigated by validating the content of the deserialized object before using it. This could involve checking the types and values of the fields, as well as the presence or absence of expected keys.

Steps

  • First, you should validate the incoming JSON data before deserializing it. This can be done by checking if the necessary fields are present and are of the correct type.
  • Second, you should implement a method in the User class that validates the data before creating a new User object. This method should throw an exception if the data is invalid.
  • Third, you should catch any exceptions thrown during the deserialization process in the handleRequest function. If an exception is caught, you should return an error response to the client.
  • Lastly, consider using a safe deserialization library or tool that provides built-in protections against insecure deserialization.

Compliant code

import 'dart:convert';
import 'package:shelf/shelf.dart';

class User {
final String username;
final String password;

User(this.username, this.password);

User.fromJson(Map<String, dynamic> json)
: username = json['username'],
password = json['password'];

Map<String, dynamic> toJson() =>
{
'username': username,
'password': password,
};

static void validate(Map<String, dynamic> json) {
if (json == null) {
throw Exception('Invalid data: null');
}
if (json['username'] == null || json['username'] is! String) {
throw Exception('Invalid data: username');
}
if (json['password'] == null || json['password'] is! String) {
throw Exception('Invalid data: password');
}
}
}

Response handleRequest(Request request) async {
try {
var body = await request.readAsString();
var data = jsonDecode(body);
User.validate(data);
var user = User.fromJson(data);

// ... rest of the code

} catch (e) {
return Response(400, body: 'Invalid data');
}
}

The code above fixes the insecure deserialization vulnerability in the following ways:

  1. Data Validation: Before deserializing the JSON data, we validate it by checking if the necessary fields are present and are of the correct type. This is done in the validate method of the User class. If the data is invalid, an exception is thrown.

  2. Exception Handling: In the handleRequest function, we catch any exceptions thrown during the deserialization process. If an exception is caught, we return an error response to the client with a status code of 400, indicating that the request was malformed.

  3. Safe Deserialization: By validating the data before deserializing it, we ensure that only valid data is used to create a User object. This prevents an attacker from exploiting the deserialization process to inject malicious data into the application.

By implementing these measures, we can significantly reduce the risk of insecure deserialization vulnerabilities in the application.

References