部署在IIS 10上的Flask SSL扫描应用执行异常求助
概述
我开发了一款名为SSL Scanner的Python Flask Web应用,后端基于Github上Dirk Wetter开发的testssl.sh脚本。应用计划部署在IIS上,网页提供输入框让用户输入主机名(可带端口)或IP地址,点击「Begin Scan」按钮后,app.py会从HTML表单提取主机名,作为参数传入testssl.sh命令,扫描目标主机的服务器配置。
我已经提取ubuntu.exe并添加到环境变量和系统变量路径中,只需在命令前加bash就能运行shell命令(比如testssl.sh脚本)。Flask应用运行时,会先切换到testssl.sh所在目录,执行脚本后生成HTML报告文件,存储到指定目录;扫描过程中界面显示「正在扫描<输入的主机名>的服务器配置...」,完成后替换为扫描完成消息和下载链接。
问题
在Flask开发服务器(localhost:5000)上运行时,应用完全符合预期;但部署到IIS后,虽然Internal_scanner.html模板能正常渲染,但输入主机名点击「Begin Scan」按钮后,「正在扫描...」消息仅显示约1秒,随即显示扫描完成消息及下载链接,但实际扫描并未执行。
备注
之前app.log中出现过类似StackOverflow帖子里提到的错误(原帖为Django+IIS场景,我的是Flask+IIS),我已经应用了该帖子的解决方案,当前该错误不再出现在日志中,但问题仍存在。
我附上了相关代码文件,恳请协助解决该问题,使应用能在IIS上正常运行并可通过URL供其他设备访问。
Flask应用代码(app.py)
from flask import Flask, render_template, request, jsonify, url_for, send_file import os import datetime import subprocess app = Flask(__name__) # app: Test SSL Interface dateandtime = datetime.datetime.now().strftime("%H%M") def create_app(): @app.route("/") def index(): return render_template("Internal_Scanner.html") @app.route("/scan", methods=["POST"]) def scan(): # Get the hostname from the form data hostname = request.form["hostname"] if len(hostname) > 50: return jsonify({"return_output": "The hostname is too long."}) elif hostname.find(" ") != -1: return jsonify( { "return_output": "The entered hostname is not valid as it has a whitespace." } ) else: if hostname.find(":") == -1: htmlfilename = f"{hostname}_{dateandtime}.html" else: htmlfilename = f"{(hostname.split(':'))[0]}_{dateandtime}.html" # Call the backend code to scan the given hostname os.chdir(r"C:\inetpub\wwwroot\scanner\testssl.sh-3.1dev") # Execute the testssl.sh script with the URL and redirect the output to a folder subprocess.run(f"bash ./testssl.sh --htmlfile ../reports/scans/{htmlfilename} -p -s -f -P -S -h -H -I -T -BB -R -C -B -O -W -F -D -4 --openssl-timeout 5 {hostname}") download_link = f"/api/download/{htmlfilename}" return jsonify({"return_output": f"Scan completed for hostname: {hostname}.", "download_link": download_link}) @app.route("/api/download/<string:filename>", methods=["GET"]) def download_report(filename): # Serve the file from the reports directory # r"C:\WSL\reports\scans" file_path = os.path.join(r"C:\inetpub\wwwroot\scanner\reports\scans", filename) if os.path.isfile(file_path): return send_file(file_path, as_attachment=True) else: return jsonify({"error": "File not found"}) return app
模板代码(Internal_Scanner.html)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" /> <title>SSL Scanner</title> <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}" /> <link rel="shortcut icon" type="image/x-icon" href="{{ url_for('static', filename='images/icons.ico') }}" /> </head> <body> <div id="header"> <div id="logo"> <img src="{{ url_for('static', filename='images/image.jpeg') }}" width="150" height="50" alt="Logo" title="Scanner Logo" /> </div> <div> <a href="" id="documentation">Documentation</a> <a href="mailto:prateek406@gmail.com" id="contactUs">Contact Us</a> </div> </div> <div id="submitBox"> <form method="POST" action="/scan" style="color: rgb(255, 119, 1)"> <label for="hostname"><b>Hostname</b></label> <input type="text" name="hostname" id="hostname" required /> <button id="submit-btn" type="submit" style=" color: rgb(227, 216, 203); font-weight: bold; background-color: rgb(255, 119, 1); border-radius: 5px; padding: 8px 12px; border: none; cursor: pointer; "> Begin Scan </button> <label style="margin-left: 5px">Do not refresh or go back during an ongoing scan.</label> </form> </div> <div id="loading" style=" display: none; padding: 30px; text-align: center; color: rgb(255, 119, 1); "> Scanning the server configuration of <span id="hostname-text"></span>... </div> <div id="return_output" style="padding: 10px; text-align: center; color: rgb(255, 119, 1)"> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script> $("form").submit(function (event) { event.preventDefault(); var hostname = $("#hostname").val(); $("#hostname-text").text(hostname); $("#submit-btn").attr("disabled", true); $("#loading").show(); $("#return_output").empty(); // Remove previous output message and download link when a hostname is submitted $.ajax({ type: "POST", url: "/scan", data: $("form").serialize(), success: function (response) { // Display the output message $("#return_output").text(response.return_output); // Check if download link is available in response if (response.download_link) { // Create a download link element var downloadLink = $("<a>") .attr("href", response.download_link) .attr("download", "report.html") .text(" Download Scan Report"); // Append download link element to the return_output div $("#return_output").append(downloadLink); } }, error: function (error) { console.log(error); }, complete: function () { $("#submit-btn").attr("disabled", false); $("#loading").hide(); }, }); }); </script> </div> </body> </html>
IIS配置文件(web.config)
<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <handlers> <add name="TesterHttpPlatformHandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified" /> </handlers> <httpPlatform stdoutLogEnabled="true" stdoutLogFile="C:\inetpub\wwwroot\scanner\logs" processPath="C:\inetpub\wwwroot\scanner\env\Scripts\python.exe" arguments="-m flask run --port %HTTP_PLATFORM_PORT%"> <environmentVariables> <environmentVariable name="Ubuntu_Executable" value="C:\inetpub\wwwroot\Ubuntu\Ubuntu_2004.2021.825.0_x64" /> </environmentVariables> </httpPlatform> </system.webServer> </configuration>
内容的提问来源于stack exchange,提问作者PeaBee

