I have a CTFd instance and I try to use a webshell.
For this, I have a page like this :
from flask import render_template, session, redirect, url_for
from CTFd.models import db, Users, Teams
from CTFd.utils.decorators import admins_only, is_admin
from CTFd import utils
from uuid import uuid4 #used for random hex string generation
import subprocess #for executing commands to docker
container_name = "ctfd_wettytest_1" #CHANGE ME. Use whatever name you provided to your container
#useradd_cmd = F"docker exec -it {container_name} /usr/sbin/useradd -m -s /bin/bash -p $(openssl passwd -1 {password}) {username}"
return_info = "Pour acceder a votre instance Kali, cliquez <a href=http://192.168.192.134:8080/>ICI</a><br>Username: %s<br>Password: %s" #CHANGE ME. The URL needs to be yours.
login_info = {} #this stores login info. If CTFd webapp is restarted, the information is lost. New
def load(app):
@app.route('/docker', methods=['GET'])
def view_docker():
#if utils.authed(): #make sure the user is logged in
player = Users.query.filter_by(id=session['id']).first() #get user/team id.
if player.id in login_info: #if there is already login info for the user
return_data = return_info%(login_info[player.id][0],login_info[player.id][1])
return render_template('page.html', content=return_data) #provide the login info to the user
else: #if there is no login info for the user
randomvalue = uuid4().hex[0:16] #generate random hex string
username = randomvalue[:8]
password = randomvalue[8:]
login_info[player.id] = [username,password] #add login info into our dictionary
useradd_cmd = F"/usr/bin/docker exec -it {container_name} /usr/sbin/useradd -m -s /bin/bash -p $(openssl passwd -1 {password}) {username}"
subprocess.call(useradd_cmd, shell=True) #run a command to add a user to the container
return_data = return_info%(login_info[player.id][0],login_info[player.id][1])
return render_template('page.html', content=return_data) #provide the login info to the user
#else: #if user isn't logged in, send them to the login page
#return redirect(url_for('auth.login'))
When I launch it via flask run or gunicorn with this command :
gunicorn --bind unix:app.sock --keep-alive 2 --chdir /CTFd/ --workers 3 --worker-class gevent 'CTFd:create_app()' --access-logfile '/CTFd/CTFd/logs/access.log' --error-logfile '/CTFd/CTFd/logs/error.log' --bind 127.0.0.1:8000
No problem, account is created as expected in the docker's container and the webshell can be used.
BUT, I want this app to start automatically, when I try to launch it via systemd, everithing is working expected this page, the subprocess doesn't work and acount is not created
Make this systemd service file :
[Unit]
Description=Gunicorn instance to serve ctfd
After=network.target
[Service]
User=ctfd
Group=docker
WorkingDirectory=/CTFd
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin"
ExecStart=gunicorn --bind unix:app.sock --keep-alive 2 --workers 3 --worker-class gevent 'CTFd:create_app()' --access-logfile '/CTFd/CTFd/logs/access.log' --error-logfile '/CTFd/CTFd/logs/error.log' --bind 127.0.0.1:8000
[Install]
WantedBy=multi-user.target
Not working
So, I've tried to use crontab of ctfd user (those who own the directory and member of docker's group) :
@reboot PATH=/usr/local/sbin:/usr/local/bin:/sur/sbin:/usr/bin gunicorn --bind unix:app.sock --chdir /CTFd --keep-alive 2 --workers 3 --worker-class gevent 'CTFd:create_app()' --access-logfile '/CTFd/CTFd/logs/access.log' --error-logfile '/CTFd/CTFd/logs/error.log' --bind 127.0.0.1:8000
No message in error.log
EDIT : @erik258 put me on the right way. After is comment, I started to read EVERY logs. And found it... And it's so... stupid!
It's not a gunicorn error but a docker error :
"The input device is not a TTY"
So, using '-i" instead of "-it" because there is not any TTY when launched via systemd solve the problem.