2

I am working on enhancing the security of an existing web application which currently has not implemented the use of anti-CSRF tokens, so it is up to me to generate one, add the hidden fields and checks, etc. A limitation right now is that neither the openssl or mcrypt modules are currently configured on the server.

Here is my token-generating code:

$csrfToken = base64_encode(uniqid(mt_rand(100, 999) . microtime(), true));

I have a feeling that this would provide an adequate token, but not a particularly strong one, but that's my own personal instinct. Does anybody have any suggestions as to how I can strengthen the security of this token without the use of mcrypt or openssl functions? Thank you!

EDIT: This is currently how I am verifying the token after the form is submitted, where $token is the token passed along with the request:

function validate_token($token) {
    return isset($_SESSION['csrfToken']) && $token === $_SESSION['csrfToken'];
}
4
  • You also should talk about how you are verifying this token. Lots of better secure schemes exist but generating a random number and throwing it in your session is plenty secure enough
    – user11869
    Dec 24, 2014 at 22:52
  • ^ I edited my original post to show how I am verifying the token.
    – hRdCoder
    Dec 26, 2014 at 17:34
  • with your scheme you can just generate random bytes, store it in the session, and use that....You don't need any advanced schemes. Lots of sites use advanced schemes when storing and looking up CSRF tokens causes significant overhead (think billions of state changing requests).
    – user11869
    Dec 28, 2014 at 3:11
  • In this case, it is for a members area for a site. So the number will only be in the thousands at any given moment. Is this a good reason to have less concern for overhead?
    – hRdCoder
    Dec 28, 2014 at 18:53

4 Answers 4

4

I would say no. Here is an interesting article on a similar topic.

I would suggest:

  1. Using a hash function (this does not add virtually any security on its own, but makes improvements mentioned below much more efficient), e.g. $csrfToken = base64_encode(hash("sha256", uniqid(mt_rand(100, 999) . microtime(), true), true));
  2. Adding a long, random secret constant (this adds security while the secret constant remains secret - this value may be very easy read in shared-hosting server), e.g. $csrfToken = base64_encode(hash("sha256", "nS7W7@IEPJ~6&&vp#r>ziW-SGOC?s>!,.(s" . uniqid(mt_rand(100, 999) . microtime(), true), true));
  3. Reading some bytes from /dev/urandom if this is available and adding them to value being hashed (this adds real security without use of PHP extensions mentioned in question post), e.g. $handle = fopen("/dev/urandom", "rb"); $urandom_data = fread($handle, 16); if(strlen($urandom_data)!==16) exit("Unable to read /dev/urandom"); fclose($handle); $csrfToken = base64_encode(hash("sha256", $urandom_data . "nS7W7@IEPJ~6&&vp#r>ziW-SGOC?s>!,.(s" . uniqid(mt_rand(100, 999) . microtime(), true), true));
  4. Add client IP and port (this improves security a bit, especially if client IP address is not known to attacker), e.g. $handle = fopen("/dev/urandom", "rb"); $urandom_data = fread($handle, 16); if(strlen($urandom_data)!==16) exit("Unable to read /dev/urandom"); fclose($handle); $csrfToken = base64_encode(hash("sha256", $urandom_data . $_SERVER["REMOTE_ADDR"] . ":" . $_SERVER["REMOTE_PORT"] . "nS7W7@IEPJ~6&&vp#r>ziW-SGOC?s>!,.(s" . uniqid(mt_rand(100, 999) . microtime(), true), true));
  5. This does not improve security, but makes the generation algorythm more understandable - remove uniqid() stuff and replace it with pure microtime(), e.g. $handle = fopen("/dev/urandom", "rb"); $urandom_data = fread($handle, 16); if(strlen($urandom_data)!==16) exit("Unable to read /dev/urandom"); fclose($handle); $csrfToken = base64_encode(hash("sha256", $urandom_data . $_SERVER["REMOTE_ADDR"] . ":" . $_SERVER["REMOTE_PORT"] . "nS7W7@IEPJ~6&&vp#r>ziW-SGOC?s>!,.(s" . microtime(), true));
  6. (Added later) Add HTTP Cookie request header to input of hash function. This has dual effect on security - good part is that CSRF token depends also on session cookie (so attacker must know/guess victim's session cookie to calculate CSRF token - in most cases session cookie is more critical); bad part is that attacker may try to "guess" session cookies based on any CSRF tokens saved in browser history. To make this guess computationally harder (at the expense of server resources) Cookie header can be hashed alone before adding it, e.g. $handle = fopen("/dev/urandom", "rb"); $urandom_data = fread($handle, 16); if(strlen($urandom_data)!==16) exit("Unable to read /dev/urandom"); fclose($handle); $csrfToken = base64_encode(hash("sha256", $urandom_data . $_SERVER["REMOTE_ADDR"] . ":" . $_SERVER["REMOTE_PORT"] . "nS7W7@IEPJ~6&&vp#r>ziW-SGOC?s>!,.(s" . microtime() . hash("sha256", $_SERVER["HTTP_COOKIE"], true), true));

P.S. Notice that variable-length digit strings are concatenated using non-digit separators (to avoid some non-equivalent string sets resulting in equivalent concatenated values).

P.S.2 Looks like there is a bracket (")") missing in question post code.

P.S.3 More on meaning of cryptographic hash function here. Hash function does the following:

  1. Adds NO entropy - has functions are deterministic (hash function will always produce the same output from each particular input). It is important to understand that no entropy is added (even if length of string is increased). SHA-256 from string "A" will be 32 bytes long, but it will still be a well known value (you can just Google this hash in hexadecimal to find out how popular it actually is).
  2. Mixes and obfuscates (this time obfuscate does not mean something avoidable and insecure) the input - the only way attacker can determine the input is by guessing it. Basically hash function introduces an all-or-nothing principle - either attacker knows entire input (and can verify it by computing the hash) or attacker does not know which parts of the input he has predicted are correct. As a result hash function allows to increase security by adding a long, random secret constant (step 2 mentioned above), without use of the hash function attacker would see the constant in all observed CSRF tokens (thus he would know what the constant is and that it must be added).
  3. (Very closely related to 2.) Makes attacker to spend some computational resources with each "guess" of the input of hash function. In article mentioned above the attacker uses GPU (video card), because he must do large number of "guesses" and that would take very long time on CPU. The more complex (less predictable) input of the hash function is, the more computational resources attacker must use to guess the input. If the attacker has enough computational resources for "guessing" the input of the hash function used to generate CSRF token (as said before, amount of computational resources needed depends on complexity/predictability of the input), he can sample the input and start to analyze how it is composed, identify constant parts etc.

For further reading on application of cryptographic hash functions the following Wikipedia articles are relevant: Cryptographic hash function, Hash-based message authentication code.

P.S.4 Partial off-topic: As mentioned earlier, it can be easy for an attacker to extract the constant and generation code by attacking shared hosting environment.

3
  • Thank you very much for your well thought out answer. I just have one question to ask. For #1, how does a hash function not provide any security on it's own? Again, your answer here is very informative and very helpful to me, so thank you.
    – hRdCoder
    Dec 26, 2014 at 17:42
  • The hash function adds no security on its own. See my answer below, but I'd encourage you to choose well accepted methods of generating secure random numbers. Not using well established methods of obtaining secure random numbers is a big problem in security, and we need to do a better job of it. Dec 26, 2014 at 21:13
  • Added P.S.3 and P.S.4 to answer as response to @hRdCoder comment.
    – DavisNT
    Dec 28, 2014 at 0:27
1

No. I'm not a PHP developer, but I found this on the uniqID function you're using:

UniqID manual page

Warning

This function does not create random nor unpredictable strings. This function must not be used for security purposes. Use a cryptographically secure random function/generator and cryptographically secure hash functions to create unpredictable secure IDs.

Please refer to this thread on generating secure random numbers in PHP, as it's beyond my scope and largely this is what your question boils down to. If you're getting 128 bits of entropy, the encoding or putting it through further hash functions is largely unimportant.

1
  • Interesting, the bold text is opposite of what I assumed the uniqid() function did. I think it's about time I start reading the warning blocks on the PHP pages. I'll read into this and let you know what I come up with. Thank you for your answer. P.S. I'm unable to vote up your answer as I don't have the rep.
    – hRdCoder
    Dec 28, 2014 at 18:21
0

Don't try to reinvent the wheel and don't try to reinvent a solution for unique random numbers. If your language or framework provide a function to generate a random UUID a, go with that. You may know what you are doing, but you have just you debugging and supporting. The language or framework has a company or organization behind it to make sure that they get it right.

1
  • Thanks for your answer, DTK. However, there isn't a framework installed that would provide the functionality that I need. I normally take the route of using a well established and recommended method of doing this, but for this particular instance I am unable to.
    – hRdCoder
    Dec 28, 2014 at 18:17
0

The accepted answer seems to be providing snake oil alongside good advice.

For the generation of CSRF tokens specifically, there are only a few requirements:

  • unpredictable
  • some resistance to brute force
  • uses printable characters that are compatible with web technology

All you need to do to achieve this is generate enough cryptographically secure randomness to prevent a modest brute force attempt (compared to, say, offline password cracking) and then base64 encode it.

You can see what is used widely by the Clojure community https://github.com/ring-clojure/ring-anti-forgery/blob/master/src/ring/middleware/anti_forgery.clj#L11 as part of the anti-forgery middleware (prevents CSRF).

(defn- new-token [] (random/base64 60))

The key is that the random/base64 function being called here uses the cryptographically secure Java SecureRandom class under the hood. The 60 refers to 60 bytes of random data to be base64 encoded.

That's it.

You can achieve the same outcome by sourcing random data from /dev/urandom.

Here are problems with the other suggestions for the token generation algorithm:

  • Usage of mt_rand - don't use this at all, it's not fit for purpose here and is exactly what the linked article in the accepted answer warns against (but then the accepted answer goes on to use mt_rand in code examples...)
  • Usage of uniq_id - does not create unpredictable or random strings, as per the PHP docs
  • Using a hash function - does reduce the character set, but doesn't add any security. Base64 encoding is clearer about the purpose and doesn't imply security functionality that simply isn't there
  • Using a secret key - In other contexts secret keys are critical, in this context it simply provides you with something you have to keep secret (not an easy thing to do), adding complexity and difficulty with no extra security benefit
  • adding client IP/port/etc. - Doesn't make the token more secure than crypto-random data, but does make the algorithm more complicated and less portable
  • Usage of microtime - Doesn't add any extra security, the current time is predictable enough to be brute forced. If we consider mt_rand insecure then surely a timestamp is too.

Essentially, anything you do that adds complexity without meeting the fundamental requirements just makes the algorithm:

  • Less maintainable
  • Harder to understand
  • More likely to have a critical bug
  • Less secure

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .