December 12, 2007

AIP 1.0.0 Bypassed

Somebody from websecurity.com.ua has contacted me regarding a vulnerability of Auto-Input Protection (AIP).  I was able to confirm that reusing an older web form to bypass the AIP web control, even if the CAPTCHA image is no longer valid, is in fact a vulnerability of AIP 1.0.0.

See the original article here:  http://www.websecurity.com.ua/1568/

Although I believe that Security Through Obscurity [1] is not really security at all, one of the purposes of this control is to prevent unwanted SPAM such as automated comments in blogs, and in my experience it works as intended even though this vulnerability has always existed.  In most cases I think the potential risk of the control being bypassed is outweighed by the fact that its use is not related to security.  I think it's probably used mostly to prevent the nuisance of SPAM in blogs (that's how I use it) and forums, and to prevent unwanted user accounts from being generated, which may still be an appropriate use even with its current vulnerability as long as it works for now.

However, I certainly don't recommend AIP 1.0.0 to be used for the authorization of banking transactions or the arming of a nuclear weapon :)

The Attacks

Other than the obvious uses, AIP should be able to counter the following threats to itself (probably not an exhaustive list):

  1. Attempts to sequester many auto-generated images for pattern analysis by the bot's owner.
  2. Attempts to validate the same challenge repeatedly to learn, in an automated way, about the particular CAPTCHA algorithm being used.
  3. Attempts to repeatedly use the same challenge to bypass security.

AIP 1.0.0 does not provide protection for all of the points above.  If you can think of anything else that should make this list please let me know.

The Problem

In an attempt to remedy the situation I realized that it doesn't seem possible to have a 100% secure image-based CAPTCHA implementation because, at the very least, an implementation must be able to verify the submitted text and therefore must keep track of it either on the client or the server:

Client: Raw Text
When included in a response as raw text, such as in a query string or POST data, there is an obvious security flaw since bots can just find the "answer" in the response itself.  (AIP doesn't do this!)

Client: Encrypted Text
When encrypted in POST data, such as part of ASP.NET Control State (AIP does this in 1.0.0), there is an added level of security however it's still fundamentally flawed without taking extra precautions because the same page can be cached by a client and used repeatedly to bypass CAPTCHA protection, even using the same bitmap text and even when the CAPTCHA image has already been invalidated.

Server: Raw Text With Client Identity
When stored on the server, AIP must be able to identify the user agent posting the request as the same agent that requested the CAPTCHA challenge to begin with.  Regardless of whether the text is stored in-proc or out-of-proc, the easiest way to identify the user agent in ASP.NET is to use session state; however, this adds an additional requirement for a client: it must now respond with a session ID each time that it makes a request.  Commonly, a session ID is stored in a cookie on the client's machine; alternatively, it can be part of the request's query string instead.  But in both cases this can make the CAPTCHA susceptible to a Cross-site Request Forgery (CSRF) attack [2].  Though overall I think this can be the most secure solution with the help of absolute expirations.

The Solution

So here's what I've come up with to make AIP more secure.  The improved implementation will be available soon as part of the 2.0.0 Beta release.  Please let me know if you have any thoughts of your own about how security can be beefed up and I'll definitely consider it.

The main security points are:

  1. Configurable timeouts for requesting the image and responding with an answer.
  2. The same "user" that makes the original request for a page that contains the AIP web control must also be the one to respond with an answer.
  3. Only one CAPTCHA test can be active at any given time, per "user".
  4. Once an answer is given, the test is invalidated and cannot be used again (i.e., the current in-memory test cannot be used again, regardless of whether the answer was correct or not, but the actual text on the bitmap can still be used again if it happens to be generated, randomly, for a different request altogether).

Here's an outline of the implementation details, with the securest settings available all being enabled:

  1. Store the CAPTCHA text on the server instead of in the response and associate it with a value that uniquely identifies the user agent that made the request.
  2. Replace the last challenge, if one exists, so that only one at a time may be active.
  3. Invalidate the CAPTCHA image once it's requested for the first time after being generated (may not be appropriate in all situations).
  4. Minimize the amount of time in which a client has to request the image from the cache after it's been generated.
  5. Minimize the amount of time in which a client has to respond to a CAPTCHA challenge (may not be appropriate in all situations).
  6. Remove the CAPTCHA text from the server when an answer is submitted, regardless of whether it was correct or not.

1. Store the CAPTCHA text on the server and identify it by the user agent's ID.
The purpose is to ensure that only the same user that requested the CAPTCHA challenge can subsequently validate the CAPTCHA text.  However, this is not a RESTful [3] solution and it may not be 100% secure either since a CSRF attack [2] may be still possible due to the way Session state is implemented in ASP.NET (not to imply that there's a better alternative ;).

2. Replace existing CAPTCHA text.
The purpose of replacing the last challenge is to ensure that each user can only have one active challenge at a time, minimizing the chances that a bot will be able to scoop up multiple images to evaluate them asynchronously, without at least responding with an answer first.  It also cleans up server memory, with no reason to keep track of old tests.

3. Invalidate the CAPTCHA image after it's been requested once.
AIP 1.0.0 uses a sliding expiration for the image.  For added security, the expiration should not be sliding though - once the image is requested for the first time it should be removed from the cache.  But instead of assuming that this is the best approach for every case I've added a boolean property named, BitmapCacheKeepAlive that specifies whether the bitmap should be kept alive or expire immediately upon the first request.  The default value is false, meaning that once the first request is made the image is invalidated.  Requesting the image will result in the same message that appears when the sliding timeout has expired.  When the value is true, the sliding expiration will be used.  Note that if the value is false and an image is never requested then it will be removed from the cache automatically in the timeout specified by the BitmapCacheTimeoutSeconds property.

4. Minimize challenge request time.
This is already implemented in AIP 1.0.0 with the use of a property named, BitmapCacheTimeoutSeconds on the AIP web control. 

When a page with the AIP web control is requested an image is generated on the server and the timeout is started.  When the browser reads the page it will discover an HTML image tag and attempt to download it, but it must request the image from the server within the configured timeout period or else the image's key will be invalid.

This feature can be verified by using the same image URL twice in a row, after waiting for the sliding timeout to expire (the default is 15 seconds).  The first time a page is requested that contains the web control a URL will be generated and the image will be visible in the browser.  Copy the URL from the source and browse to it directly after 15 seconds; the result is an image containing the message: * AIP image not found in cache *.  If you're frequently receiving this message or if users report that your web pages take a while to load then increase the value of the control's BitmapCacheTimeoutSeconds property to give browsers more time to request the image.

5. Minimize challenge response time.
This has been implemented in AIP 2.0.0 as a timeout just like the bitmap request timeout, configurable via a property on the AIP web control named, BitmapTextTimeoutSeconds.  The default value is 30 seconds.  Another property on the AIP web control named, BitmapTextKeepAlive enables or disables the timeout.  The default value is false, indicating that the timeout will be used.  When true the challenge doesn't expire until an attempt is made.

Minimizing the response time may be appropriate for sign-in screens and pages with little information to read, but it's inappropriate in long documents and large data-input forms.  If you're using the control on a page that has a lot of reading material, such as a blog, or with many data entry fields, such as a long user membership form, then you should probably set BitmapTextKeepAlive to true; otherwise, increase the value of BitmapTextTimeoutSeconds to something that is more realistic than 30 seconds so that users will have time to read or enter data before submitting the form.

Alternatively, for a more secure solution, you can put the control on a separate web page by itself and show it after the form is submitted, but before the posted data is saved to your database.  This way you can keep the timeout short; maybe even shorter than 30 seconds, which should help to protect your site against the Human Solvers [4] attack and services such as the one provided by www.captchakiller.com.

6. One shot only.
Regardless of whether the first answer submitted is correct or not, the challenge is invalidated and cannot be used again.  If the value is wrong then a new challenge must be requested.  This feature, along with the new server-side storage, should fix the vulnerability in AIP 1.0.0 discovered by websecurity.com.ua.

How To Exploit AIP 2.0.0 Beta

I don't believe that the solution outlined above is 100% secure.  However, I don't think that any solution really is, but this new implementation should be enough to prevent hackers and spammers from bypassing the control's security when it's configured properly, depending upon the context in which it's being used.

To exploit any vulnerabilities in AIP 2.0.0 a malicious client can't just cache the page anymore with the new security measures in place since the session ID that was used to make the request is required in the response as well, in the form of a cookie or as a query string (depending upon how session state is configured in the site).  But even if the cookie value is obtained, somehow, it will eventually expire due to the session timing out.  If it's used before the timeout, only one challenge may be active at any time, per user, so it's possible that the test is no longer valid anyway.  And once the first answer is provided the challenge is invalidated automatically, regardless of whether the answer is correct or not.

In order for the AIP 2.0.0 web control to be breached, assuming that there aren't more vulnerabilities of which I'm unaware, a malicious client would have to:

  1. request the CAPTCHA image within the configured timeout period after the initial request is made for the page that contains the AIP web control.
  2. get the answer to the challenge.
  3. obtain a valid session cookie (or query string if the website is configured to use cookieless session state).
    1. The session ID must belong to the owner of the session that made the initial request for the page with the AIP web control (not the request for the image).  The owner could be the bot itself or it could possibly use a spoofed or hijacked session.
  4. submit the session information with POST data containing the correct answer to the challenge:
    1. within the configured challenge timeout period.
    2. before the user that actually owns the session submits their answer (assuming that the bot is using a spoofed or hijacked session).
    3. and before the user that actually owns the session requests a new challenge (again, assuming misrepresentation).

And finally, once an answer has been submitted the test cannot be used again.

Conclusion

This is still a work in progress.  I'll provide more information, probably in the form of a WIKI on CodePlex, around the time of deployment.  Also, I'd appreciate feedback from anybody that runs tests against the AIP web control to verify its security.  If you find a flaw, please let me know and I'll try to correct it.

References

[1] Security through obscurity. (2007, November 29). In Wikipedia, The Free Encyclopedia. Retrieved 20:40, December 12, 2007, from http://en.wikipedia.org/w/index.php?title=Security_through_obscurity&oldid=174670068

[2] Cross-site request forgery. (2007, December 11). In Wikipedia, The Free Encyclopedia. Retrieved 19:33, December 12, 2007, from http://en.wikipedia.org/w/index.php?title=Cross-site_request_forgery&oldid=177107398

[3] Representational State Transfer. (2007, December 12). In Wikipedia, The Free Encyclopedia. Retrieved 19:34, December 12, 2007, from http://en.wikipedia.org/w/index.php?title=Representational_State_Transfer&oldid=177439704

[4] CAPTCHA. (2007, December 11). In Wikipedia, The Free Encyclopedia. Retrieved 19:33, December 12, 2007, from http://en.wikipedia.org/w/index.php?title=CAPTCHA&oldid=177256271#Human_solvers