59

I'm working on an application which allows a moderator to edit information of user. So, at the moment, I have URL's like

http://www.example.com/user/1/edit
http://www.example.com/user/2/edit

I'm a bit worried here, as I'm directly exposing the users table primary key (ID) from the database. I simply take the ID from the URLs (for ex: 1 and 2 from above URLs), query the database with the ID and get user information (of course I am sanitizing the input - ID from URL).

Please note that I'm validating every request to check if moderator has access to edit that user.

Is what I'm doing safe? If not, how should I be doing it?

I can think of one alternative i.e. have a separate column for users table with 25 character key and use the keys in URLs and query database with those keys.

However,

  • What difference does it make? (Since key is exposed now)
  • Querying by primary key yields result faster than other columns
SilverlightFox
  • 33,408
  • 6
  • 67
  • 178
  • 1
    Take a look at [OWASP Top 10 2013-A4-Insecure Direct Object References](https://www.owasp.org/index.php/Top_10_2013-A4-Insecure_Direct_Object_References) – Songo Apr 22 '14 at 19:15
  • Link above is broken. Current (related): [OWASP - Insecure Direct Object Reference Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Insecure_Direct_Object_Reference_Prevention_Cheat_Sheet.html) – sleske Aug 25 '22 at 11:38

6 Answers6

40

The only piece of information that you could hope to "hide" is the sequence: since a database will allocate primary key values with a counter, people who see they key can make a guess as to when the corresponding user account was created. Apart from that, there is no other information that any obscuring scheme may actually hide. The attacker already knows that distinct users are referenced through distinct keys, and that's the extent of the semantics of the database "primary key".

Therefore, unless you consider that the account creation order is private information that needs to be hidden, I'd say that your obscuring scheme is pointless.

Thomas Pornin
  • 320,799
  • 57
  • 780
  • 949
  • 22
    It may still be desirable to prevent end users from enumerating *all* of the records that they have access to. You may want to support viewing any single user's profile, but discourage mass automated scraping of data from many users' profiles. If you use integers from 0 to n then it's trivial to download every user's profile. – Rag Apr 23 '14 at 00:19
  • 4
    If you want to use user IDs in your URL, but want to obscure the sequence, you might be able to use a guid. would also improve scaling options because it's basically impossible to run out of possible options (compared to, say, an int or even a long). – Nzall Apr 23 '14 at 11:24
  • 14
    You would also reveal the *number* of IDs (someone keeps reading until they find the lowest non-existent ID) and the *current rate of creation* (someone creates one, then a set time later finds the new maximum). This might be of interest in the case of user accounts, as eg. it gives clues as to the financial viability of your application to a competitor. – Julia Hayward Apr 23 '14 at 12:42
30

One easy way would be to use the method youtube and other websites use. This is hashids (http://hashids.org).

With this method you can give links like: http://www.example.org/user/fce7db/edit while fce7db would equal to a number e.g.: 12

This has the advantage of performance in contrary to generating another random hash in the database, because you only have to translate it back once and then you can search in the database by the original primary key as you did before.

To make it more difficult for users to randomly find other IDs you can use a secret salt for one and a minimum length to prevent “brute-forcing” them.

By having one secret salt people can still interchange the URLs in cases like http://www.example.com/image/c8yDa .

Jay Claiton
  • 417
  • 3
  • 5
  • You can create a salt per user to make it more difficult to decypher. – SPRBRN Apr 23 '14 at 07:58
  • Never, ever use the same salt for two different records; that is doing it wrong. In security, if one is going to do it wrong, it is better to not do it at all, as a false sense of security will arise. – BryanH Apr 23 '14 at 16:26
  • 1
    This may be an incredibly ignorant question, and I apologize if it is, but since this is run with JavaScript (and is thus client side), couldn't an attacker just View Source and check for what's passed into the hash function? Or at least see the salt, if only decryption is happening? – asteri Apr 23 '14 at 17:51
  • You'd generate the hashids server side and send them to the client. As far as the client is concerned it only knows the hashid, with the salt safely managed on the server. There's implementations for many languages/runtimes on the site. – afrazier Apr 23 '14 at 18:20
  • @afrazier Ah, so the JavaScript is just an example. – asteri Apr 23 '14 at 18:39
  • @BryanH the one secret salt was for all the e.g. images. I mean that one person can give this URL to another person and not have to face the problem that this link is dead for him. * Not sure if your statement isn't excluding this. – Jay Claiton Apr 23 '14 at 18:50
  • @JeffGohlke they also provide a library for other languages like PHP! – Jay Claiton Apr 23 '14 at 18:52
  • For the sake of completeness: hashids are NOT secure! Not when the salt is shared, but also not when every user has a separate salt. It's still easy to bruteforce and could be done in a completely automated way. Again, hashids do not claim and cryptographic security!! It's obvious but I don't want new users to read this answer and think they can "hide" information with hashids. You can hide it from plain sight, yes. But if someone wants to analyse your website's growth by analysing the ID, they will still figure it out easily. – Potaito Dec 31 '18 at 08:07
5

No. One way or another, you need to identify the specific record with a unique identifier in the URL. That can be the primary key or something else. If you need to hide the order or sequence you can use a second column with a random and unique string like Z2wDKo0ubb1D2VngFh4N.

If you want more security, use SSL. This doesn't prevent the user from seeing the URL and the parameters though, as @eggyal noted.

Using SSL, the URL paramaters are encrypted and prevent eavesdropping. And yes we know by now that SSL is flawed, not really that secure, but it gives more security than not using it. But do not use the URL for passwords! Use POST for logging in and submitting data, GET for retrieving information, like always.

See https://stackoverflow.com/questions/499591/are-https-urls-encrypted

Reasons not to use secret information in a URL: DNS requests are probably not encrypted (but as @tgies notes in the comments, the request portion is not included so no ID here), browser history and server logs.

SPRBRN
  • 7,379
  • 6
  • 33
  • 37
  • 10
    SSL only hides the requested URI from eavesdroppers, not from the user (whom I had understood would be the source of any attack). – eggyal Apr 22 '14 at 19:28
  • 1
    The request portion of the URI does not appear in any DNS query. – tgies Apr 22 '14 at 21:43
  • "yes we know by now that SSL is flawed" - can you support that statement with some evidence or something ? As someone who is NOT in the security world, I find that to be very.... worrying. Thanks. – Radu Murzea Jan 11 '17 at 08:30
3

I've created a few systems that either do or don't use the primary key (or other identifier) in the URL. Which we use is totally dependant on the harm-factor -- for instance, for a site that is data driven read only, we use the primary key in the URL; we don't care if someone goes through the products sequentially.

For systems that have security requirements, instead of using a sequential predictable id, we have used one of two schemes:

  1. We keep the key information the browser's session. Simple, straightforward and if our server isn't secure, we have bigger problems.
  2. We have generated a random number (and checked for uniqueness) either when the record is created OR for the session only; and used that random number within the URL.

Good luck,

Larry

LarryN
  • 31
  • 2
1

Yes, you should obfuscate those IDs, since you're leaking information.

If your application is perfectly secure, the only information (ab)users may learn is a guesstimate of the number of users through the use of something called the Doomsday Argument. Also, if they learn the IDs of several users, they'll know who signed up first and can guess how far apart that happened. And they can make educated guesses of the IDs of other users as well.

If your application is perfectly secure.

If it isn't, and you should always assume it may not be, you've given out useful information. If a user ID was just a random number, an attacker could not easily figure out another valid ID. With the (mostly consecutive) numbers usually given out by an automatic primary key, it's trivial.

You could either change the primary key, or add a secondary key, if you want to retain the ease of use of small numbers for internal use. This new key should typically be a GUID.

‡: Of course it's an xkcd link.

SQB
  • 421
  • 3
  • 11
0

You don't provide enough information about the application to know whether you should obscure it or not. As such, this part of your question isn't answerable. However, if you have to ask, then the answer is probably yes, and even if it isn't then going above and beyond the call of duty provides more protection than the alternative.

Rather than hiding or obscuring the primary key, consider changing it. There's no need for it to be sequential. When you create a new user, make a random 32 bit number, do a quick select to make sure it's not in use, and use it as your primary key.

If anyone is editing a user, they won't be able to gain any information from the user number, and trying numbers immediately adjacent to the number they currently have access to is likely to yield a non-existing record.

The overhead/burden of this system is minimal. It requires one additional select during account creation, which is likely rare, and doesn't impose any additional overhead afterwords - you're still providing the primary key in the URL, so DB searches are still fast, but the key has no information.

Adam Davis
  • 1,071
  • 7
  • 11