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.
- Setup port forwarding on our router
- 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
- We can specify the source IP if we want more granular control, like:
- Create a user which will host the app
# New user sudo adduser flaskuser # No password passwd -d flaskuser # create app dir /home/flaskuser/flask - Create the Python virtual environment
cd /home/flaskuser/flask python3 -m venv venv source venv/bin/activate pip install Flask - 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 - Allow our
flaskuserto restart the service;sudo visudo:flaskuser ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart flask.service - 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 - Setup new cronjob on user;
crontab -e0 0 * * * /home/flaskuser/flask/update.sh
Configuring Flask
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)
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. ![]()
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. 