I have a number of MySQL tables describing models such as "User", "Business" etc. Should the primary keys of these tables ever be exposed to the client-side? I am asking from a security perspective, primarily, but are there also other considerations that I haven't thought of?
问题:
回答1:
Exposing your primary keys (especially if they are predictable) is a vulnerability called Insecure Direct Object Reference.
By having a URL (or any other client provided param) like this:
http://www.domain.com/myaccount?userid=12
You give your end users the opportunity to mess with those variables and pass any data that they like. The counter measure to mitigate this vulnerability is to create indirect object references instead. This may sound like a big change, but it does not necessarily have to be. You don't have to go and rekey all your tables or anything, you can do it just by being clever with your data through the use of an indirect reference map.
Consider this: You have a user who is making a purchase on your site. And when it is time to pay they are presented with a drop down of the credit card numbers of theirs that you have "on file". If you look at the code for the drop down you see that the credit card numbers are associated with the keys 8055, 9044, and 10099.
The user might look at this and think that they look a lot like auto-incrementing primary keys (the user would probably be right). So he starts trying other keys to see if he can pay with someone else's card.
Now technically, you should have code on the server-side that ensures that the selected card is part of the user's account and that they can use it. This is a contrived example. For now we will assume that this is not the case or that this is another type of form that perhaps does not have that kind of server side control.
So how do we prevent the end user from choosing a key that should not be available to them?
Instead of showing them a direct reference to the record in the DB, give them an indirect reference.
Instead of putting the DB keys into the dropdown, we will create an array on the server and stuff it in the user's session.
Array cards = new Array(3);
cards[0] = 8055;
cards[1] = 9044;
cards[2] = 10099;
In the drop down we now provide the reference to the index of the array where the card is stored. So instead of seeing the actual keys, the end user will see the values 0, 1 and 2, if they view the source.
When the form is submitted one of those values will be passed along. Then we get the array out of the user's session and use the index to get the value. The actual key has never left the server.
And the user can pass in different values all-day-long if he wants, but he will never, ever, get a result other than his own cards, regardless of the server-side access control thats in place.
Keep in mind though that when using the passed-in index to get the value out that if the user does mess with it that you could get some exceptions (ArrayOutOfBounds, InvalidIndex, whatever). So wrap that stuff in a try/catch so you can suppress those errors and log the failures to look for cracking attempts.
Hope this helps.
To read more about Insecure Direct Object References, check out the OWASP Top 10. It is risk number A4. https://www.owasp.org/index.php/Top_10_2010-A4-Insecure_Direct_Object_References
回答2:
In general, it's OK to post whatever data to the browser. But don't forget:
Any data passed to the client and passed back to the server might be compromised in any way. Don't trust data returned by the client!
Unfortunately, if you post keys - somehow - the user changes it and you fail to correctly verify the key, if posted back from client to server, harmful things might happen.
Thus, you should write very defensive code regarding client-to-server-post/get keys. In fact, you shouldn't trust any data posted back from client to server.
My question might be of interest too.
As stated in my question, my latest application never ever posts identifying data to the client. More generally, not even those stuff typically used as parameters in GET/POST parameters ever get written to the client.
All keys or somehow entity related attributes which control the flow of the application are strictly server-side only.
What I proposed as alternative D) in my question provides this:
- Any request received by the server is valid by default, since all valid link gets born at page-rendering time, rather than at request time.
- User can't forward links using email or post them, since they only exists while his or her session is active.
- User can only request those links inside a web-page, since only these links are registered at the server-side.
- All state related data is keep at the server side, and thus unchangable by the client.
回答3:
Have a column of UUIDs along with the auto-increment primary keys in your table. Expose the UUID in the clients, and use the auto-increment keys for joins and backend processes.
回答4:
One could use hashing if looking for another layer of security, if your users don't mind the obfuscated URL. Something like the below in PHP.
$id = 1234;
$url = 'http://domain.com?id='. $id .'&v='. sha1('SALT1'. $id. 'SALT2');
Data on the server end can be double checked as below.
if ( $_GET['v'] == sha1('SALT1'. $_GET['id']. 'SALT2') ){
//run further checks here
}
回答5:
It is generally considered a bad idea to expose the autoincrement keys. Domain primary keys are fine.
回答6:
There would be not much harm in exposing it in general. People would be able to guess which records were created later by having a higher id.
But you would need to have any required security in place. If the users changes the id, are you already running checks to see if they are allowed to view that data? If you have some other method of looking up the data, it will probably be harder to guess the correct values. But you would have to ensure they are distinct, and the data you use might also be guessable.