简体   繁体   中英

How to make this login/redirect method more secure

I have an application which sends e-mails to users, each e-mail containing a link to a page the user must access.

The link is an md5 of an unique id + a random number.

The link looks like: www.domain.com/index.php?id_page=<the md5>

On the index page i save the $_GET["id_page"] within a session variable, $_SESSION["id_page"] and I redirect the user to the page which he must see (the user is redirected to that page only if the link contains id_page ).

How can I improve this method and make it more secure? How can I guarantee that the users enter the page designated to them - and cannot enter any other page?

You can add their email in the URL. the probability of someone guessing someone else's email and the associated hash is just about 0.

What you are concerned with here is a matter of time, rather than a matter of security :). If you allow anyone to guess an infinite number of id_page values, then given enough time, eventually someone will happen upon a random valid id_page value.

Your only real defense against this is to increase the length of your hash, causing it to take (on average) more time to happen upon a random valid id_page (on the order of months or years). This could be accomplished by using sha256 or sha512 rather than md5.

Another approach is to lock someone out for a period of time if they have, for example, 3 consecutive incorrect guesses at an id_page value. This will greatly decrease the number of values they can attempt in a given period of time.

Lastly, If the user is already logged in at the time of redirect then you could also store the hashes you generate in a database table. That way you can map a particular hash to one and only one userid in the table. If a user attempts to visit a hash page to which they don't correspond in the DB, then you could redirect them elsewhere.

One method that can work quite well for preventing guessing ID numbers is to add some sort of padding to the ID and then convert it to base32. Of course, this doesn't eliminate the ability to guess an ID entirely, but it does make it a little more time consuming for anyone who is snooping around.

If you have the URL www.domain.com/index.php?id_page=1, you could convert the id to something unique in your application, for example:

padded id = id x 9 - 2

7 = 1 x 9 - 2
16 = 2 x 9 -2
25 = 3 x 9 - 2

Then, you can convert the new padded ID to base 32, which would be

7 = G4
16 = GE3A
25 = GI2Q

The new url would then be (for an id of 1):

www.domain.com/index.php?id_page=G4

Using this method, if someone guesses the base 32 of 1-6, it would return a 404, because your ID of 1 is actually being padded out to become 7. Guessing 8-15 wouldn't return a parsed ID because the next id of 2 is padded to 16, and so on.

Not only does this keep the query string smaller in size, but it also doesn't use an obvious MD5 hash which can very easily be sequentially browsed using dictionaries.

If you want the page to be linked to a specific users, well there's no reason why you cannot append more values to the new padded_id (let's call it a hash).

For instance, if you have a user with an ID of 12, and you only want that user to be able to visit a page with an id of 10, you would need to create a hash that comprises of both these values:

page_id(10)-user_id(12), using the above example, this would produce:

(10 x 9 - 2) (12 x 9 - 2)
88-106
HA4A-GEYDM

You now have a nice small hashed link that can be secured to a single user ID. The padding method in the example above is rather simple, but it gives you the overall idea of how to approach the issue.

Don't bother hashing or creating unique ids or anything. Complexity is not going to help you.

Instead, simply generate a random token, and use that. A 256 bit random token should be sufficient for anything you need to do.

So, using mcrypt (a core extension):

$token = strtr(
    base64_encode(mcrypt_create_iv(256/8, MCRYPT_DEV_URANDOM)), 
    '+/', 
    '-_'
);

That will give you a 44 character result of the alphabet a-zA-Z0-9-_ which contains 256 bits of random entropy.

There's no need to hash the result or anything. The random data is enough.

To understand why, you need to understand The Birthday Problem .

Basically with a 256 bit random value, to have a 1% chance that 2 tokens collide you would need to generate 4.8e37 tokens (that's 48 followed by 36 0's).

To get a 10e-18 chance of collision (which is typically seen as secure) you'd need to generate 4.8e29 tokens.

Since those numbers are WAY more than you'd ever generate, the chance of 2 tokens colliding is infinitesimally small.

The other problem is people guessing the token. Well, MCRYPT_DEV_URANDOM uses the underlying operating system's random pool. Which means that people are way more likely to guess your "unique id and random number" than they are to guess the token generated here.

So, in short, just use a random token and be done :-)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM