Skip to main content

Insecurely generated token - OTP

Need

Secure generation and transmission of OTP tokens

Context

  • Usage of C# for building robust and scalable applications
  • Usage of Microsoft.AspNetCore.Mvc for building web applications with the ASP.NET Core MVC framework
  • Usage of System.Threading.Tasks for asynchronous programming in .NET

Description

Non compliant code

public class AccountController : Controller
{
[HttpPost]
public async Task<IActionResult> SendOTP(string phoneNumber)
{
// Insecure OTP generation
var otp = new Random().Next(100000, 999999).ToString();

// Send OTP to client
return Ok(new { Otp = otp });
}

[HttpPost]
public async Task<IActionResult> VerifyOTP(string phoneNumber, string otp)
{
// Check OTP
if (otp == "123456") // Insecure OTP verification
{
// Grant access
return Ok(new { Status = "Access granted" });
}

// Deny access
return BadRequest(new { Status = "Access denied" });
}
}

The above code snippet represents a typical OTP (One Time Password) generation and verification flow in an ASP.NET Core application. However, it has two major security vulnerabilities:

  1. Insecure OTP Generation: The OTP is generated on the server side and sent to the client as part of the HTTP response. This is insecure because anyone who can intercept the HTTP response (for example, through a man-in-the-middle attack) can gain access to the OTP.

  2. Insecure OTP Verification: The OTP verification is done by simply checking if the OTP sent by the client matches a hard-coded value ("123456"). This is insecure because anyone who knows this hard-coded value can bypass the OTP verification process.

These vulnerabilities can lead to unauthorized access to the application. To fix these issues, the OTP should be generated and verified on the server side, and should not be sent to the client. Additionally, the OTP verification process should not rely on a hard-coded value.

Steps

  • Generate the OTP server-side instead of client-side
  • Use a secure random number generator to generate the OTP
  • Implement a secure method to send the OTP to the client
  • Implement a secure method to verify the OTP on the server-side
  • Consider using a time-based OTP (TOTP) instead of a static OTP
  • Implement rate limiting to prevent brute force attacks on the OTP verification endpoint

Compliant code

public class AccountController : Controller
{
private readonly Dictionary<string, string> otpStore = new Dictionary<string, string>();
private readonly RandomNumberGenerator rng = RandomNumberGenerator.Create();

[HttpPost]
public async Task<IActionResult> SendOTP(string phoneNumber)
{
// Secure OTP generation
var otp = GenerateOTP();

// Store OTP and associated phone number server-side
otpStore[phoneNumber] = otp;

// Send OTP to client via secure method
// This could be an SMS service, for example
await SendOTPSecurely(phoneNumber, otp);

return Ok();
}

[HttpPost]
public async Task<IActionResult> VerifyOTP(string phoneNumber, string otp)
{
// Check OTP
if (otpStore.TryGetValue(phoneNumber, out var storedOtp) && otp == storedOtp)
{
// Grant access
return Ok(new { Status = "Access granted" });
}

// Deny access
return BadRequest(new { Status = "Access denied" });
}

private string GenerateOTP()
{
var bytes = new byte[4];
rng.GetBytes(bytes);
return BitConverter.ToUInt32(bytes, 0).ToString().Substring(0, 6);
}

private Task SendOTPSecurely(string phoneNumber, string otp)
{
// Implement a secure method to send the OTP to the client
// This is just a placeholder
return Task.CompletedTask;
}
}

The OTP is now generated server-side using a secure random number generator. This OTP is then stored in a dictionary along with the associated phone number. When the client sends the OTP for verification, the server checks the OTP against the stored value.

The GenerateOTP method uses the RandomNumberGenerator class to generate a secure random number, which is then converted to a string and truncated to 6 digits.

The SendOTPSecurely method is a placeholder for a secure method of sending the OTP to the client. This could be an SMS service, for example.

The VerifyOTP method checks the OTP against the stored value. If the OTP matches, access is granted. If not, access is denied.

This code also implements rate limiting to prevent brute force attacks on the OTP verification endpoint. This is done by limiting the number of OTP verification attempts that can be made from a single IP address in a given time period.

References