Skip to main content

Lack of data validation - OTP

Need

Implementation of robust data validation for OTP tokenCode parameter

Context

  • Usage of PHP 7.0 for server-side scripting and web development
  • Usage of Request for making HTTP requests
  • Usage of the User model for managing user data

Description

Non compliant code

public function verifyOTP(Request $request) {
$tokenCode = $request->input('tokenCode');

$user = User::where('otp', $tokenCode)->first();

if($user) {
// User found with the OTP
$user->is_verified = 1;
$user->save();
return response()->json(['status' => 'success'], 200);
} else {
// User not found with the OTP
return response()->json(['status' => 'error', 'message' => 'Invalid OTP'], 400);
}
}

In the above code, the user sends an OTP (One-Time Password) via a request to the verifyOTP function. This function takes the 'tokenCode' from the request and searches for a user with an OTP that matches the 'tokenCode'.

If a user is found, it sets the user's 'is_verified' status to '1' and saves this to the database. If no user is found, it returns an error message 'Invalid OTP'.

The vulnerability lies in the fact that there is no validation of the 'tokenCode' parameter. An attacker can send random values in multiple attempts to the server until a valid OTP is found. This is known as a brute force attack and can lead to unauthorized access.

Steps

  • Implement rate limiting to prevent brute force attacks on the OTP.
  • Add a server-side validation to ensure the OTP is in the correct format and length.
  • Implement a time-based OTP that expires after a certain period of time.
  • Store the OTP as a hashed value in the database to prevent it from being exposed even if the database is compromised.
  • Use a secure random number generator to generate the OTP.
  • Implement multi-factor authentication to add an additional layer of security.

Compliant code

public function verifyOTP(Request $request) {
$tokenCode = $request->input('tokenCode');

// Validate the OTP format and length
if (!preg_match('/^[0-9]{6}$/', $tokenCode)) {
return response()->json(['status' => 'error', 'message' => 'Invalid OTP format'], 400);
}

// Get the user with the hashed OTP
$user = User::where('otp', hash('sha256', $tokenCode))->first();

// Check if the OTP has expired
if ($user && $user->otp_expires_at > now()) {
// User found with the OTP
$user->is_verified = 1;
$user->save();
return response()->json(['status' => 'success'], 200);
} else {
// User not found with the OTP or OTP expired
return response()->json(['status' => 'error', 'message' => 'Invalid or expired OTP'], 400);
}
}

The updated code includes several security enhancements to address the lack of data validation vulnerability in the OTP verification process.

Firstly, the code now includes a server-side validation check to ensure that the OTP is in the correct format and length. This is done using a regular expression that checks if the OTP is a six-digit number.

Secondly, the OTP is now stored as a hashed value in the database. This is done using the SHA-256 hashing algorithm. This means that even if the database is compromised, the OTPs will not be exposed.

Thirdly, the code now checks if the OTP has expired before verifying it. This is done by comparing the current time with the otp_expires_at field in the user's record. If the OTP has expired, the verification process will fail.

These changes help to prevent brute force attacks on the OTP by limiting the number of valid OTPs and the time period in which they can be used. They also help to protect the OTPs in the event of a database breach.

References