I’ve only had the time to make the API, but it should be working properly. Please make sure it’s secure. If you can read any file you want, I’ll make sure to reward you!
Analysis
Before each request, we must have a valid JWT cookie to authenticate which is signed with the APP_SECRET.
try: payload = jwt.decode(session, APP_SECRET, algorithms=['HS256']) if payload['userid'] != 0: abort(401) except: abort(Response(f'<h1>NOT AUTHORIZED</h1><br><br><br><br><br> This system has been up for {round(time.time()-time_started)} seconds fyi :wink:', status=403))
However, the APP_SECRET can be found as the time since the server was ran is given if we do not have a valid JWT in the except block.
After finding out the secret, we can request files through the /api/file route. The route checks for path traversal by checking if ‘../‘ is in the filename and recursively strips it. We are unable to read arbitrary files for now.
if filename is None: abort(Response('No filename provided', status=400))
# prevent directory traversal while '../' in filename: filename = filename.replace('../', '')
# get file contents return open(os.path.join('files/', filename),'rb').read()
The flag is stored in the home directory of the ctf user which is designed to be random, thus hinting on RCE or LFI.
1 2 3 4
ARG directory=/in_prod_this_is_random
RUN mkdir ${directory} RUN useradd -M -d ${directory} ctf
Solution
When os.path.join is used to return the current directory files, it can resolve an absolute path if given one such as:
1 2 3 4 5 6 7
Python 3.11.8 (main, Feb 7 2024, 21:52:08) [GCC 13.2.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import os >>> a = os.path.join("/files", "/etc/passwd") >>> print(a) /etc/passwd
We can use this to request /etc/passwd and find the home directory of the ctf user. Using the home directory, we can request ${directory}/flag.txt
# get stats @app.route('/api/stats/<string:id>', methods=['GET']) def get_stats(id): for stat in stats: if stat['id'] == id: return str(stat['data']) return '{"error": "Not found"}'
We can control the username parameter and make it return a XSS payload when requesting it in the /api/stats/string:id endpoint.
Solution
Write an XSS payload in the username and ask the bot to request our payload. The XSS will request the command injection endpoint and exfiltrate the flag.