I want to let users share data outside of their account and have put together a proof of concept that essentially generates a unique URL with relevant URL parameters to display the data.
Obviously, a normal URL would simply let you modify the parameters, modify the query and extract any data you wished. So, with this, when the user generates a link to share data, I take the parameters, add a complex salt, hash the combined string (sha-2) and then use that as a key. So the URL might look like:
mydomain.com/app/shared.php?function=form&account=1&form=a19481e78dd87f5eb04afe94c85ea4f3&key=7dcaa38baa19e0f70262d8775582300346f5c544
When the URL is entered, the server recompiles the parameters and the secret salt and validates the key. If the key is not valid, no data is displayed.
I did think about further securing this by storing parameters in a DB, so the URL looks more like mydomain.com/app/h6Hs52ff2a, and the parameters never directly included in the URL, but equally I quite like the idea of generating sharable URLs on the fly with no DB backend.
I get the sense that the above approach might be a little frowned upon, but equally, unless you know the salt stored on the server (which is itself complex) I can't see any way of bypassing such a system.
Thoughts most welcome.
That's an entirely feasible approach, essentially a signed URL. The only weakness of that system is the secrecy of the salt/key; if you're using a fast hashing/encryption algorithm and a weak salt/key, it is feasible to brute-force the secret offline. So you'll need to use a strong (read: slow) enough algorithm to prevent that (a plain SHA2 is too fast!), and you need to ensure your key doesn't leak. You also need to ensure you don't lose your key accidentally, as that would reset all shared URLs. If this is done properly, it's a nice, stateless way to do things.
I'd look into JWTs as an alternative to your homegrown method, as they basically incorporate all your requirements already (they are essentially arbitrary signed data bags).
The advantage of the database approach is that it has no attack surface, and that you're able to invalidate shared URLs selectively. The drawback is that it uses database storage, which may have operational overhead.
One more deciding factor here would be URL-length, which you may or may not care about.
If you need reassurance, it is the same concept employed by Google Drive when you share document with a link.
Some comments:
Shared link readability. If the shared data is always about the same kind of model (for example account data), the link is OK. But if other types of data can be shared, like user details, I would include it in the URL so that the person getting the shared link can make some sense of the link.
If you are concerned with adding more security, you can enforce a key per user. You would need to keep the key in the database for each user.
When generating the url, add an expiry parameter and hash. When reading hashed link, you can then see if the link expired.
Make sure to use URL safe characters
Looking at what you are trying to achieve makes me wonder, why don't you simply:
map in your database a 128 bit random value to a set form parameters every time a user wishes to share a URL. It's as secure as the random number generator you use and the form values remain a secret (as they never leave the server).
encrypt the parameters using a cipher with message authentication (authenticated encryption with modes like GCM or OCB). Secure unless your keys leak. Hides the form parameters from the eyes of the users.
use only a part of 'authenticated encryption' like a message authentication code (MAC; HMAC). Does not hide the form parameters, adds a code that needs to be verified.
Related
What would be useful solutions for hiding true database object ID in URL for security purposes? I found that one of the solutions would be:
1) Using hashids open source project
2) Using something like same old md5 on creation of the object to generate hash and store it in database, then use it in url's and querying by them, but the drawback is that querying by auto-incremented primary keys (IDs) is faster than hashes. So I believe the possibility to hash/unhash would be better?
Also as I'm on Symfony, are there maybe bundles that I could not find or built in functionalities that would help?
Please tell me what you found useful based on your experiences.
This question has been asked a lot, with different word choice (which makes it difficult to say, "Just search for it!"). This fact prompted a blog post titled, The Comprehensive Guide to URL Parameter Encryption in PHP .
What People Want To Do Here
What People Should Do Instead
Explanation
Typically, people want short random-looking URLs. This doesn't allow you much room to encrypt then authenticate the database record ID you wish to obfuscate. Doing so would require a minimum URL length of 32 bytes (for HMAC-SHA256), which is 44 characters when encoded in base64.
A simpler strategy is to generate a random string (see random_compat for a PHP5 implementation of random_bytes() and random_int() for generating these strings) and reference that column instead.
Also, hashids are broken by simple cryptanalysis. Their conclusion states:
The attack I have described is significantly better than a brute force attack, so from a cryptographic stand point the algorithm is considered to be broken, it is quite easy to recover the salt; making it possible for an attacker to run the encoding in either direction and invalidates property 2 for an ideal hash function.
Don't rely on it.
Quote from the site:
Do you have a question or comment that involves "security" and "hashids" in the same sentence? Don't use Hashids.
I'd use true encryption algorithm, like function openssl_encrypt (for example), or something like this. And encrypt ids when passing outside, decrypt when using in your code (like for db queries).
And I won't recommend storing ids in a base like any kind of encrypted "garbage", in my opinion its very inconvenient to hash your real ids. Keep it clean and pretty inside and encrypt for external display only.
Following your idea, you just need to cipher your IDs before writing the URL to HTML page and decipher them when processing those URLs.
If you want just security by obscurity, which is sufficient for, maybe 99% of curious people out there who likes to iterate over IDs in URLs, you use something simple like base64 or rot13. Of course, you can also precalculate those "public IDs" and store in the database, not encrypting each time the URL is being shown to end user.
If you want true security you have to encrypt them with some serious asymmetric cypher, storing both keys at your side, as you essentially talking with yourself and don't want a man-in-the-middle attack. This you will not be able to precalculate as at each encrypting there'll be different cyphertext, which is good for this cause.
In any case, you need something two-way, so if I were you I'd forget about word "hash", hashes are for purposes different from yours.
EDIT:
But the solution which every blog out there uses for this task for several years already is just to utilize URL rewriting, converting, in your case, URLs like http://example.com/book/5 to URLs like http://example.com/rework-by-37signals. This will completely eradicate any sign of database ID from your URL.
Ideologically, you will need something which will uniquely map the request URL to your database content anyway. If you hide MySQL database IDs behind any layer of URL rewriting, you'll just make this rewritten URL a new ID for the same content. All you gain is protection from enumeration attacks and maybe SEF URLs.
What would be useful solutions for hiding true database object ID in URL for security purposes? I found that one of the solutions would be:
1) Using hashids open source project
2) Using something like same old md5 on creation of the object to generate hash and store it in database, then use it in url's and querying by them, but the drawback is that querying by auto-incremented primary keys (IDs) is faster than hashes. So I believe the possibility to hash/unhash would be better?
Also as I'm on Symfony, are there maybe bundles that I could not find or built in functionalities that would help?
Please tell me what you found useful based on your experiences.
This question has been asked a lot, with different word choice (which makes it difficult to say, "Just search for it!"). This fact prompted a blog post titled, The Comprehensive Guide to URL Parameter Encryption in PHP .
What People Want To Do Here
What People Should Do Instead
Explanation
Typically, people want short random-looking URLs. This doesn't allow you much room to encrypt then authenticate the database record ID you wish to obfuscate. Doing so would require a minimum URL length of 32 bytes (for HMAC-SHA256), which is 44 characters when encoded in base64.
A simpler strategy is to generate a random string (see random_compat for a PHP5 implementation of random_bytes() and random_int() for generating these strings) and reference that column instead.
Also, hashids are broken by simple cryptanalysis. Their conclusion states:
The attack I have described is significantly better than a brute force attack, so from a cryptographic stand point the algorithm is considered to be broken, it is quite easy to recover the salt; making it possible for an attacker to run the encoding in either direction and invalidates property 2 for an ideal hash function.
Don't rely on it.
Quote from the site:
Do you have a question or comment that involves "security" and "hashids" in the same sentence? Don't use Hashids.
I'd use true encryption algorithm, like function openssl_encrypt (for example), or something like this. And encrypt ids when passing outside, decrypt when using in your code (like for db queries).
And I won't recommend storing ids in a base like any kind of encrypted "garbage", in my opinion its very inconvenient to hash your real ids. Keep it clean and pretty inside and encrypt for external display only.
Following your idea, you just need to cipher your IDs before writing the URL to HTML page and decipher them when processing those URLs.
If you want just security by obscurity, which is sufficient for, maybe 99% of curious people out there who likes to iterate over IDs in URLs, you use something simple like base64 or rot13. Of course, you can also precalculate those "public IDs" and store in the database, not encrypting each time the URL is being shown to end user.
If you want true security you have to encrypt them with some serious asymmetric cypher, storing both keys at your side, as you essentially talking with yourself and don't want a man-in-the-middle attack. This you will not be able to precalculate as at each encrypting there'll be different cyphertext, which is good for this cause.
In any case, you need something two-way, so if I were you I'd forget about word "hash", hashes are for purposes different from yours.
EDIT:
But the solution which every blog out there uses for this task for several years already is just to utilize URL rewriting, converting, in your case, URLs like http://example.com/book/5 to URLs like http://example.com/rework-by-37signals. This will completely eradicate any sign of database ID from your URL.
Ideologically, you will need something which will uniquely map the request URL to your database content anyway. If you hide MySQL database IDs behind any layer of URL rewriting, you'll just make this rewritten URL a new ID for the same content. All you gain is protection from enumeration attacks and maybe SEF URLs.
I'm interested in your advice/opinion on this security problem.
I was thinking on doing something like this:
Get hash MAC (sha256) from string built from userId + expirationTime and as secret key string built from some secret string and $_SERVER['HTTP_USER_AGENT'].
Get hash MAC (sha256) from userId + expirationTime and as secret key previously made hash (from step 1).
Build string from userId|expiration| and previously made hash (from step 2).
Encrypt given string (from step 3) with 'rijndael-256' algo. (mcrypt family of functions).
Encode to base64.
Set cookie with given value.
What do you think. Is this ok?
What else could I implement with $_SERVER['HTTP_USER_AGENT'] check, to make sure that the cookie isn't stolen (except IP address)?
P.S. From sensitive data cookie would contain only userId.
EDIT:
Ok to clear some things.
I'm trying to make "safe" auth system that doesn't rely on sessions. The app in question is build more or less as pure restful api.
Step 2:
Problem:
"Fu’s protocol does not provide an answer to this
question. There is only one key involved in Fu’s proto-
col, namely the server key. One straightforward solu-
tion is to use this server key to encrypt the data field
of every cookie; however, this solution is not secure."
Solution:
"Our solution to this problem is simple and efficient.
We propose to use HMAC(user name|expiration time,
sk) as the encryption key. This solution has the fol-
lowing three good properties. First, the encryption key
is unique for each different cookie because of the user
name and expiration time. Note that whenever a new
cookie is created, a new expiration time is included in
the cookie. Second, the encryption key is unforgeable
because the server key is kept secret. Third, the encryp-
tion key of each cookie does not require any storage on
the server side or within the cookie, rather, it is com-
puted by a server dynamically.
"
From paper "A Secure Cookie Protocol" by Alex X. Liu1 , Jason M. Kovacs
Step 4:
Encrypts data (which would look something like this: 'marko#example.com|34234324234|324erfkh42fx34gc4fgcc423g4'), so that even client couldn't know exactly what's inside.
Step 5:
Base64 encode is there just to make final value pretty.
I'll bite.
In order to maintain any semblance of state you need to identify the user using a key of some type. That key is sent to the browser as a cookie OR through query string parameters.
Now, the validation of that key can occur inside the web server itself (session) or through checking some other storage mechanism, usually a database record.
The key itself should be obfuscated using some mechanism. The reason for the obfuscation is simply to make it harder to guess what values other keys might have if the originating user or someone else decides to inspect the value. For example, if the key is your user id (not recommended) and you are using incrementing ints then it's trivial to guess the other user keys. I want to stress that obfuscating ( or even downright encrypting ) the key provides absolutely no protection against a hijacked session. ALL it does is make it harder to guess other peoples session keys.
That said, I believe the key should have nothing at all to do with your user id and instead be some other near random value like a generated GUID. Quite frankly a base 64 encoded GUID is at the exact same level of security as encrypting user id + time. It's just that one is more computationally intensive on your server than the other.
Of course, this key could change upon each request. Browser posts something, you generate a new key and send it back. In the event the browser posts an out of date key then log it and kick them back to the login screen. This should prevent replay attacks .. to a degree. However, it introduces other challenges such as using the Back button on various browsers. So, you may not want to go down this path.
That said you can't depend on the client IP address because the same user might send follow up requests using a different IP. You can't depend on browser fingerprinting because any decent hacking tool will capture that and submit the same values regardless of whatever they are using.
Now, if you really want to do this right you should have SSL turned on. Otherwise you're wasting your time. The entire conversation (from the login screen on) needs to be encrypted. If it's not then someone could simply listen for that cookie, replay it immediately and hijack the session. Point is that they don't need to know the values contained therein to use them. So all of that hashing, etc you have is just fluff that will increase your server load.
Did I say use SSL? ;) This will encrypt the traffic from the beginning of the conversation and an attacker cannot replay the same packets as they would have to negotiate their own handshake with the server. Which means all you have to do is ensure that whatever session id you use is non-guessable so that one logged in user can't take over another's session.
So, to sum up: the method you posted is a waste of time.
You are much better off just getting a $10 SSL certificate and using a base 64 encoded GUID as the session ID. How you store that session info on your server doesn't really matter... except in load balanced situations. At which point it needs to be out-of-process and backed by a database server.. but that's another question.
#Marko A few comments about how secure this kind of "session in a cookie" approach is:
First of all, as said by others as well, you need a secure connection. There is no realiable way around this requirement. It is a must.
Other than that, there are quite a few pitfalls regarding to implement a secure encryption/authentication system. For example you need to make the MAC verification "constant-time", you need to pay attention how do you implement the encryption/authentication (mode of operation, IV creation etc.). And so on.
If you are unsure about such issues, I recommend you to take a look at TCrypto (which I maintain):
TCrypto
It is a small PHP 5.3+ key-value storage library (cookies will be used as a storage backend by default). Designed exactly for (scalable) "session in a cookie" usage. Feel free to use it :) Also, if you are interested about the low-level implementation, take a look at the code. The codebase is not that huge, I guess it would do quite well, demonstrating encryption related code usage in PHP applications.
I've had another developer pose the possibility of combining and encrypting/obsfucating all the parameters to pages for php, as a security measure against manipulations via crafted urls and to prevent interior knowledge of the database (e.g. knowing the id in the database of a specific entry).
In other words, instead of single or multiple public query parameters like ids, there would be a single encrypted blob that would be decrypted server-side, and re-encrypted when links are crafted.
Are there problems with this approach? Are there substantial advantages that make it worthwhile? Is this approach used in the wild to good effect?
You should design your system to prevent unauthorized access. Obsfucating (useful encryption on data the client generates is not a possibility) is not a worthwhile defense.
For instead, instead of giving the user a database ID, given them a hash (with perhaps a session seed) of the ID. The 128bit+ search space of the hash and (for reasonable DB sizes) low probability of collisions would be a much better approach. You could also encrypt the ID on the server for values the client never needs to manipulate (with a seed) but make sure it has the same properties as the hash I mentioned—namely that the search space is very large compared to the possible value space.
If you want to prevent users from messing around with the GET arguments, i would recommend the following:
Add a hidden form to all of your pages. Clicking anywhere on the page, would fill-in some data into the form and submit it securely through POST / SSL. Along the submission details, pass the URL where you want to direct user to.
On the server side, collect arguments, put them into session either globally or under some sort of identifier which you append to the destination URL. Send redirect back. This way if user refreshes page, he's not nagged about POST data. Also if he starts messing with going back and sideways in the application, kill that session cache and send him to starting page.
I have seen this technique in some on-line banking softwares. Another benefit is that user can't open new window.
In my opinion it can add some degree of security, but would severely change development approach and give you more work. I never used this approach myself and I think that ID's are safe to pass around as long as you have a proper ORM system in place which under no circumstances won't let user A access data by user B regardless of what kind of code your developers will write.
There may be some cases when this type of URL encryption (or Obsfucating) is useful. Let's say you build a pretty robust security in your application and all your hosts are safe and sound.
Now if your operations staff happens to be external and you don't want them to know/see these sensitive data (IDs) by changing log levels on the fly then it is better to encrypt them and decrypt them on demand by individual module.
As a general practice one should not pass any sensitive data in URL parameters and care should also be taken to NOT to log them even at higher level.
I am producing a script that others will put in their websites. It is designed for people with limited knowledge of PHP so that all they have to do is include() the script and set a few configuration variables. This means headers will probably have been sent already and so using sessions may not work. I recommend they call session_start in their own scripts, but I want a fallback option as well.
I already have a hidden input to deal with forms, but I also need links to have a query string appended to their URIs to identify the session. But if the hash is based only on the password+salt then there is a security risk: the logged-in user could click an external link and the owner of the external site could see the URI with the hash in their referrer logs. All they'd have to do is used that hash and they'd be logged in.
Therefore I want to salt the hash in a time-sensitive manner, limiting a session to 10 minutes. I can't figure out how to do this. Of course I can use time() to salt it, but how do I check how old the session is based only on the hash?
Expiring sessions after 10 minutes does not protect your users against session hijacking attacks. It only succeeds in annoying your users by forcing a login every 10 minutes. Your proposed scheme still has all kinds of vulnerabilities. Your salted hashed passwords can still leak to the outside world through many other channels; packet sniffing, intermediate proxies, users' emailing each other links to pages or even the saved html, just to name a few. I advise you not to homegrow you're own security framework without being an expert in the area. Even then, this is a solved problem. Just go with a known trusted solution. There are many subtleties in web security that are easy to mess up.
This sounds like a real bad idea. And complicated too. I would definitely not recommend it. You probably can use something like this:
if (session_id() == "") session_start();
The above will basically check if session has been started or not, and otherwise start session.
Since you planning to distribute to users, the whole approach seems a bit off to me. Am not sure what you are looking to achieve out of this, but you could try using a JS which calls your PHP file per page instead. This will make it easier for you. If you could elaborate on what kind of application you are developing, then I could probably help you out better. I have a lot of experience in mass consumer software apps similar to what you are doing.
This is not necessarily a bad idea, but it is dangerous if not done correctly. In fact, it is a fairly common implementation of multi-domain single sign-on using Hash-based Message Authentication Codes. Some ground rules:
Never ever include the password as part of the hash (even as the salt)
Require a timestamp generation as part of the hash that must be passed ALONG with the hash.
Each site to use this hash should have their own 32 or 64 byte guid to be used as a unique salt.
Pass specific data in the query string such as username, timestamp, anything else, and the HMAC. So it would look something like ?user=steve×tamp=66343532233&otherdata=otherdata&HMAC=AB3445-1234144-AFBBDEDD (you get the idea)
When site authentication is made cross-site, the HTTP_REFERER should be used (if possible) to get the key to generate the comparing HMAC.
Use a solid hashing algorithm (SHA1 is preferred) for generating the HMAC. Generate the the private site keys as randomly as possible. Do not use a standard derivation method, simply make sure that the end result is large enough/unique enough.