DEV Community

Mykhailo Krainik
Mykhailo Krainik

Posted on

How Simple IDs Can Leak Your Secrets

Most of us tend to overlook how we handle certain data, such as social security numbers, date of birth, or phone numbers, assuming they’re automatically treated as confidential. Seemingly ordinary elements like customer ID numbers in our database can unexpectedly lead to sensitive data leaks and can lead to unexpected complications.

For example, consider a scenario where the browser’s address bar displays “/customer/56/edit”. A customer ID that increments with each new entry might inadvertently reveal the actual size of our database to competitors. This implies that we have a minimum of 56 clients in our database. Such unintended information disclosure could potentially have adverse effects on our business.

We should grasp that any numbers displayed in the address bar or accessible via the request inspector in a browser’s developer tool can potentially lead to information leakage.

Here are some examples. What can they tell us?

{
  "user": "John Mallon",
  "payment": {
    "amount": 150.00,
    "cardNumber": "**** **** **** 1234",
  },
  "billId": "17"
}
Enter fullscreen mode Exit fullscreen mode

The first impression is that everything is done safely, However, I encourage you to examine the billId more closely. It’s the same incremental ID that could potentially reveal whether there have been 17 payments specifically for John Mallon or 17 payments in the entire service.

So, what measures can we take to address this situation?

There are several approaches we can take in this situation. For instance, you could develop a custom algorithm to mask the actual database ID or opt to use UUIDs for this purpose.

A Universally Unique Identifier (UUID) is a 128-bit identifier used in computer systems to label information. UUIDs are designed to be practically unique when generated using standard methods. Unlike many other numbering systems, their uniqueness doesn’t rely on a central authority or coordination between parties. Although there’s a very slim chance of duplication, the probability is considered negligible due to the sheer uniqueness of UUIDs.

We can utilize the alphanumeric string representation of UUID characters to achieve the unique identification of data entries. UUIDs typically follow a specific pattern of 8–4–4–4–12 characters, where each segment is a combination of alphanumeric characters and hyphens.

123e4567-e89b-12d3-a456-426655440000
Enter fullscreen mode Exit fullscreen mode

Let’s slightly rework our answers from the server API.

{
  "user": "John Mallon",
  "payment": {
    "amount": 150.00,
    "cardNumber": "**** **** **** 1234",
  },
  "billId": "123e4567-e89b-12d3-a456-426655440000"
}
Enter fullscreen mode Exit fullscreen mode

/customer/a1f57b85-3f2c-49d9-b649-8b17cf8e2a9e/edit

As you can observe, the UUID effectively conceals the incremental value, ensuring that the actual number of data entries in the Bills or Customers table remains hidden.

HashIds

If your system cannot switch to UUID or you want to continue using IDs as an incremented number, consider using hashes.

Examples:

// JAVASCRIPT
// see: https://www.npmjs.com/package/hashids

import Hashids from 'hashids'
const hashids = new Hashids()

// Encode
const hashedId = hashids.encode(1, 2, 3) // o2fXhV
// Decode
const numbers = hashids.decode(hashedId) // [1, 2, 3]
Enter fullscreen mode Exit fullscreen mode
// RUST
// see: https://github.com/charsyam/hashids_rust

use hashids::HashIds;

let ids_hash = HashIds::new_with_salt("this is my salt".to_string()).unwrap();

// Encode
let hashedId = ids_hash.encode(vec![12345]); // NkK9

// Decode
let numbers = ids.decode("NkK9".to_string()).next().unwrap(); // [12345]
Enter fullscreen mode Exit fullscreen mode
// C#
// see: https://github.com/ullmark/hashids.net

using HashidsNet;
var hashids = new Hashids("this is my salt");

// Encode
var hash = hashids.Encode(12345);

// Decode
numbers = hashids.Decode("NkK9");
Enter fullscreen mode Exit fullscreen mode
# Python
# see: https://pypi.org/project/hashids/

from hashids import Hashids
hashids = Hashids()

# Encode
hashid = hashids.encode(123) # 'Mj3'

# Decode
ints = hashids.decode('Mj3') # (123,)
Enter fullscreen mode Exit fullscreen mode

This way, your backend continues working with simple numeric IDs, while the frontend receives a non-cryptographic, obfuscated version of the number.

{
  "user": "John Mallon",
  "payment": {
    "amount": 150.00,
    "cardNumber": "**** **** **** 1234",
  },
  "billId": "Mj3"
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)