TheByteDungeon

TheByteDungeon is a personal tech blog where I document my thoughts, explore technical challenges, and reinforce my knowledge.

Home Posts Projects View on GitHub
12 December 2024

Flask as C2

In many projects and CTFs it is really handy to have a flexible and simple C2 which just receives data and which we can further modify to deliver payloads. We will use the “micro web framework” Flask with Python to achieve there objectives.


Preparing the host

We will host the Flask app publicly on our Raspberry Pi therefore we need to allow external access on port 12345.

  1. Setup port forwarding on our router
  2. Open the local firewall sudo ufw allow 12345/tcp
    • We can specify the source IP if we want more granular control, like: sudo ufw allow from 1.2.3.4 to any port 12345
  3. Create a user which will host the app
     # New user
     sudo adduser flaskuser
     # No password
     passwd -d flaskuser
     # create app dir
     /home/flaskuser/flask
    
  4. Create the Python virtual environment
     cd /home/flaskuser/flask
     python3 -m venv venv
     source venv/bin/activate
     pip install Flask
    
  5. Create a new service for Flask; sudo vi /etc/systemd/system/flask.service:
     [Unit]
     Description=My Flask App
     After=network.target
    
     [Service]
     User=flaskuser
     Group=flaskuser
     WorkingDirectory=/home/flaskuser/flask
     ExecStart=/home/flaskuser/flask/venv/bin/python /home/flaskuser/flask/app.py
     Restart=always
    
     [Install]
     WantedBy=multi-user.target
    
  6. Allow our flaskuser to restart the service; sudo visudo:
     flaskuser ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart flask.service
    
  7. Keep the Python dependencies up to date (only Flask as of now); vi /home/flaskuser/flask/update.sh:
     #!/bin/bash
     source /home/flaskuser/flask/venv/bin/activate
     pip install --upgrade Flask
     deactivate
     sudo systemctl restart flask.service
    
  8. Setup new cronjob on user; crontab -e
     0 0 * * *  /home/flaskuser/flask/update.sh
    

Configuring Flask :sake:

Our objective is to log all GET requests and POST data to a local file. We use /<path:path> to catch all URLs.

import logging
from flask import Flask, request

app = Flask(__name__)

format = '%(asctime)s - %(levelname)s - %(message)s'
logging.basicConfig(filename = 'flask.log', level=logging.INFO, format = format)

@app.route('/', defaults={'path': ''}, methods=['GET', 'POST'])

@app.route('/<path:path>', methods=['GET', 'POST'])
def log_requests(path):
    client_ip = request.remote_addr
    if request.method == 'GET':
        app.logger.info(f'[+] {client_ip}: Received GET request: "{path}"')
    elif request.method == 'POST':
        app.logger.info(f'[+] {client_ip}: Received POST request: "{path}" with payload:\n{request.data.decode()}')
    return f'ACK'

if __name__ == '__main__':
    app.run(debug=False, host='0.0.0.0', port=12345)

Source


Verifying operations

$ sudo ufw status:

Status: active

To                         Action      From
--                         ------      ----
12345/tcp (v6)             ALLOW       Anywhere (v6)

$ systemctl status flask.service:

● flask.service - My Flask App
     Loaded: loaded (/etc/systemd/system/flask.service; disabled; vendor preset: enabled)
     Active: active (running) since Thu 2024-12-12 08:59:30 CET; 3s ago
   Main PID: 26880 (python)
      Tasks: 1 (limit: 262)
        CPU: 3.455s
     CGroup: /system.slice/flask.service
             └─26880 /home/flaskuser/flask/venv/bin/python /home/flaskuser/flask/app.py

Dec 12 08:59:30 rp systemd[1]: Started My Flask App.

$ sudo netstat -tulp:

Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:12345           0.0.0.0:*               LISTEN      26880/python

$ tail /home/flaskuser/flask/flask.log:

2024-12-12 09:01:10,326 - INFO - [+] 1.2.3.4: Received POST request: "test" with payload:
{hello": "world"}
2024-12-12 09:01:10,338 - INFO - 1.2.3.4 - - [12/Dec/2024 09:01:10] "POST /test HTTP/1.1" 200 -

Yes! The data sent by an external client shows up in our logs!


Conclusion

There we are, we now have a simple “C2” up and running which we can use as a starting point to level up our callbacks or exfiltrate data. :phone:

We need to remember to keep our exteral attack surface protected by not broadly exposing out-of-date services and dependencies, and not writing bad code. smiley

tags: hacking - c2 - flask - raspberry