AspNetCore Hashids: Avoid predictable ids in your APIs
Sometimes developers donโt take care about security concerns when they develop a REST API. I know this scope is not straightforward but at least we should comply with a minimum of basic security rules in order to avoid the most common attacks.
The first thing we must take into account is to avoid using predictable ids like identity fields. The last vulnerability Iโve seen was a simple bash script which using a for statement it was making calls to the api passing an autoincremental id and retrieving all the confidentials documents from the system. It was an error configuring the authorization of the system, but the use of predictable ids facilitated the attack.
There are some ways to fix this kind of vulnerability, for example using GUIDs to represent this ids but from my side and for many DBAโs there are some caveats on the usage of this approach:
- 4 times larger than the traditional 4-byte index value
- Cumbersome to debug
- They are not monotonically increasing
Looking for a better alternative to avoid exposing our database ids to the clients is to use hashing ids (it creates short, unique, decryptable hashes from unsigned integers). There is a library written in many languages to generate short unique ids from integers hashids and of course there is an available version for .NET hashids.net
Getting started
You should install the package
Install-Package AspNetCore.Hashids
Or via the .NET Core CLI
dotnet add package AspNetCore.Hashids
In your ConfigureServices in the Startup class
public void ConfigureServices(IServiceCollection services)
{
services
.AddHashids(setup => setup.Salt = "your_salt");
}
In your DTOs, decorate the properties that you want to be hash with the JsonConverter attribute HashidsJsonConverter
public class CustomerDto
{
[JsonConverter(typeof(HashidsJsonConverter))]
public int Id { get; set; }
public int NonHashid { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
It will be hashed:
[
{
"id": "rA3d",
"nonHashid": 10000,
"firstName": "Luis",
"lastName": "Ruiz"
},
{
"id": "1vzX",
"nonHashid": 20000,
"firstName": "Unai",
"lastName": "Zorrilla"
}
]
You can encoding the ids specifying minimum hash length:
public void ConfigureServices(IServiceCollection services)
{
services
.AddHashids(setup =>
{
setup.Salt = "your_salt";
setup.MinHashLength = 8;
});
}
and the output will be something like this:
[
{
"id": "omrA3dl2",
"nonHashid": 10000,
"firstName": "Luis",
"lastName": "Ruiz"
},
{
"id": "gj1vzXlz",
"nonHashid": 20000,
"firstName": "Unai",
"lastName": "Zorrilla"
}
]
Also you can use the HashidsRouteConstarint and the HashidsModelBinder to convert the hashid generated in the original integer value:
[HttpGet]
[Route("{id:hashids}")]
[Produces(MediaTypeNames.Application.Json)]
public ActionResult<CustomerDto> Get(
[ModelBinder(typeof(HashidsModelBinder))] int id)
{
return Ok(customers.SingleOrDefault(c => c.Id == id));
}
You see a full example here and how to modify our swagger-ui to change the type of the hashids from integers to strings.
... and last but not least a big thanks to all our contributors!
Code of conduct
This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact [email protected] with any additional questions or comments.