views:

249

answers:

1

My company's CRM system utilizes a captcha system at each login and in order to utilize certain administrative functions. The original implementation stored the current captcha value for in a server-side session variable.

We're now required to redevelop this to store all necessary captcha verification information in a hashed client-side cookie. This is due to a parent IT policy which is intended to reduce overhead by disallowing use of sessions for users who are not already authenticated to the application. Thus, the authentication process itself is disallowed from using server-side storage or sessions.

The design was a bit of a group effort, and I have my doubts as to its overall efficacy. My question is, can anyone see any obvious security issues with the implementation shown below, and is it overkill or insufficient in any way?

EDIT: Further discussion has led to an updated implementation, so I've replaced the original code with the new version and edited the description to talk to this revision.

(The code below is a kind of pseudo-code; the original uses some idiosyncratic legacy libraries and structure which make it difficult to read. Hopefully this style is easy enough to understand.)

// Generate a "session" cookie unique to a particular machine and timeframe
String generateSessionHash(timestamp) {
    return sha256( "" 
        + (int)(timestamp / CAPTCHA_VALIDITY_SECONDS)
        + "|" + request.getRemoteAddr() 
        + "|" + request.getUserAgent() 
        + "|" + BASE64_8192BIT_SECRET_A
    );
}

// Generate a hash of the captcha, salted with secret key and session id
String generateCaptchaHash(captchaValue, session_hash) {
    return sha256( ""
        + captchaValue
        + "|" + BASE64_8192BIT_SECRET_B
        + "|" + session_hash
    );
}

// Set cookie with hash matching the provided captcha image
void setCaptchaCookie(CaptchaGenerator captcha) {
    String session_hash = generateSessionHash(time());
    String captcha_hash = generateCaptchaHash(captcha.getValue(), session_hash);
    response.setCookie(CAPTCHA_COOKIE, captcha_hash + session_hash);
}

// Return true if user's input matches the cookie captcha hash
boolean isCaptchaValid(userInputValue) {
    String cookie = request.getCookie(CAPTCHA_COOKIE);
    String cookie_captcha_hash = substring(cookie, 0, 64);
    String cookie_session_hash = substring(cookie, 64, 64);
    String session_hash = generateSessionHash(time());
    if (!session_hash.equals(cookie_session_hash)) {
        session_hash = generateSessionHash(time() - CAPTCHA_VALIDITY_SECONDS);
    }
    String captcha_hash = generateCaptchaHash(userInputValue, session_hash);
    return captcha_hash.equals(cookie_captcha_hash);
}

Concept:

  1. The "session_hash" is intended to prevent the same cookie from being used on multiple machines, and enforces a time period after which it becomes invalid.
  2. Both the "session_hash" and "captcha_hash" have their own secret salt keys.
  3. These BASE64_8192BIT_SECRET_A and _B salt keys are portions of an RSA private key stored on the server.
  4. The "captcha_hash" is salted with both the secret and the "session_hash".
  5. Delimiters are added where client-provided data is used, to avoid splicing attacks.
  6. The "captcha_hash" and "session_hash" are both stored in the client-side cookie.

EDIT: re:Kobi Thanks for the feedback!

(I would reply in comments, but it doesn't seem to accept the formatting that works in questions?)

Each time they access the login page, the captcha is replaced; This does however assume that they don't simply resubmit without reloading the login form page. The session-based implementation uses expiration times to avoid this problem. We could also add a nonce to the login page, but we would need server-side session storage for that as well.

Per Kobi's suggestion, an expiration timeframe is now included in the hashed data, but consensus is to add it to the session_hash instead, since it's intuitive for a session to have a timeout.

This idea of hashing some data and including another hash in that data seems suspect to me. Is there really any benefit, or are we better off with a single hash containing all of the relevant data (time, IP, User-agent, Captcha value, and secret key). In this implementation we are basically telling the user part of the hashed plaintext.

Questions:

  1. Are there any obvious deficiencies?
  2. Are there any subtle deficiencies?
  3. Is there a more robust approach?
  4. Is salting the hash with another hash helping anything?
  5. Is there a simpler and equally robust approach?

New question:

I personally think that we're better off leaving it as a server-side session; can anybody point me to any papers or articles proving or disproving the inherent risk of sending all verification data to the client side only?

+2  A: 

Assuming no other security than stated here:
It seems an attacker can solve the captcha once, and save the cookie.
She then has her constant session_hash and captcha_hash. Nothing prevents her from submitting the same cookie with the same hashed data - possibly breaking your system.
This can be avoided by using time as part of captcha_hash (you'll need to round it to an even time, possibly a few minutes - and checking for two options - the current time and the previous)

To calrifiy, you said:

The "session_hash" is intended to prevent the same cookie from being used on multiple machines.

Is that true?
On isCaptchaValid you're doing String session_hash = substring(cookie, 64, 64); - that is: you're relying on data in the cookie. How can you tell it wasn't copied from another computer? - you're not hashing the client data again to confirm it (in fact, you have a random number there, so it may not be possible). How can you tell it's new request, and hadn't been used?

I realize the captcha is replaced with each login, but how can you know that when a request was made? You aren't checking the new captcha on isCaptchaValid - your code will still validate the request, even if it doesn't match the displayed captcha.
Consider the following scenario (can be automated):

  1. Eve open the login page.
  2. Gets a new cookie and a new captcha.
  3. Replaces it with her old cookie, with hashed data of her old cptcha.
  4. Submits the old cookie, and userInputValue with the old captcha word.
  5. With this input, isCaptchaValid validates the request - captcha_hash, session_hash, userInputValue and BASE64_8192BIT_SECRET are all the same as they were on the first request.

by the way, in most systems you'll need a nonce anyway, to avoid XSS, and having one also solves your problem.

Kobi
See reply in edits.
ryandenki
You're very helpful.But, in this case aren't we more worried about Mallory than Eve?
ryandenki
It's too bad I can't upvote the same answer again after editing.
ryandenki
Indeed. When I studied we used Eve all the time, I'll take note of http://en.wikipedia.org/wiki/Alice_and_Bob
Kobi