Flask应用Subprocess在systemd/cron启动时失效问题排查与解决
CTFd Webshell功能自动启动时Docker用户创建失败问题解决
问题场景
部署CTFd实例并开发Webshell功能,编写对应Flask页面代码实现自动为用户创建Docker容器内账户。通过flask run或手动执行gunicorn命令启动应用时,功能完全正常,Docker容器内用户账户可按预期创建,Webshell能正常使用。但配置自动启动(systemd服务或crontab)后,CTFd其他功能正常,唯独Webshell页面的subprocess调用无法工作,容器内账户无法创建。
Flask页面代码
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'))
手动启动gunicorn命令
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
systemd服务文件
[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
crontab自动启动配置
@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
排查过程
排查gunicorn的error.log未发现报错信息,进一步检查后定位到Docker报错:"The input device is not a TTY"。原因是systemd或crontab启动应用时,运行环境没有TTY终端,而Docker命令中使用了-it参数(该参数需要交互式TTY环境)。
解决方案
将Flask代码中useradd_cmd里的docker exec -it替换为docker exec -i,去掉强制分配TTY的t参数,修改后的命令如下:
useradd_cmd = F"/usr/bin/docker exec -i {container_name} /usr/sbin/useradd -m -s /bin/bash -p $(openssl passwd -1 {password}) {username}"
修改后重新部署,自动启动场景下Docker容器内的用户账户可正常创建,Webshell功能恢复正常。
内容的提问来源于stack exchange,提问作者Kptainflintt
相关产品推荐
相关产品推荐

