Py学习记录
Python 常规学习
Python学习笔记(一)
Python学习笔记(二)
Python学习笔记(三)
Python学习笔记(四)
Python学习笔记(五)
Python学习笔记(六)
Python学习笔记(七)
Python习题(一)
Python习题(二)
Python习题(三)
Python习题(四)
Python习题(五)
Python常见Bug
Python编程环境
Python-依赖安装(三方库)
Python-VS Code
pip-换源
py 程序转 exe
Python-打开选择文件对话框
Python 项目
Python-密码学
Python-与佛伦禅
Python-喵语翻译
Python-翻译服务器
Python-邮件发送
Python-自动签到
Python-自动签到(Post请求)
Python-自动签到(模拟操作)
Python-图片添加二维码
Python-数据可视化
Python-端口扫描器
Python-未测试项目
Python-虚拟环境
Python-临时环境
Python-venv虚拟环境
Python-Conda
Python-OpenCV
OpenCV-人脸识别
Python-PyTorch
本文档使用 MrDoc 发布
-
+
首页
Python-端口扫描器
基于 [Fscan(内网综合扫描工具)](/doc/729/) 的Web在线端口扫描器 使用 [Fscan](https://github.com/shadow1ng/fscan) 作为后端的扫描工具(速度快,支持解析WebTitle) Python 作为前端交互 # Python 基础使用 ```bash <项目路径> ├─app.py # Web主程序 ├─fscan # Fscan(后端) └─templates └─index.html # 主页面 ``` `app.py` ```python import subprocess # 导入 subprocess 模块 import shlex # 用于解析命令行参数 import sys # 导入 sys 模块 from flask import Flask, request, render_template # 导入 Flask 模块 import os # 导入 os 模块 import re # 导入 re 模块 def scan_ports(ip, ports): match = re.match(r'^(?:https?://)?(?:[^@/:]+@)?([^:/?#]+)', ip) # 使用正则表达式匹配域名或IP地址(自动提取链接中域名或IP,再也不要修剪URL) ip = match.group(1) # 截取域名或IP地址段 ports = ports.replace(",", ",") # 将所有","替换为","(解决误输出中文逗号) # 构建fscan命令 cmd = f"./fscan -h {ip} -p {ports}" try: log = "" # 执行fscan命令 result = subprocess.run(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) # 检查命令是否执行成功 if result.returncode != 0: print("错误:", result.stderr) return "错误: " + result.stderr # 解析输出结果 output = result.stdout # print(output) # 打印测试 open_ports = [] # 存储开放端口 web_titles = {} # 存储端口对应的WebTitle web_url = {} # 存储端口对应的URL for line in output.split('\n'): if "open" in line: # 提取端口号 port = line.split()[0].split(':')[1] open_ports.append(port) if "WebTitle" in line: # 提取WebTitle和端口 parts = line.split() # 分割字符串 if len(parts[2].split(':')) > 2: # 确保存在端口信息(防止索引报错) port = parts[2].split(':')[2] # 使用指定字符分割字符串(提取端口) title = line.split('title:')[1] # 提取标题 if not title == "None": # 判断是否为None web_titles[port] = title # 存储端口对应的WebTitle web_url[port] = parts[2] # 存储端口对应的URL # 指定的端口中未在 open_ports 列表中的,即为关闭的端口 all_ports = parse_ports(ports) unique_ports = list(set(all_ports) - set(open_ports)) # 端口去重(全部端口-关闭的端口) # 生成Markdown表格格式结果 log = "| 端口 | 状态 | WebTitle |\n|---|---|---|\n" for port in open_ports: if port in web_titles: log += f"| [{port}]({web_url[port]}) | ✅开放 | {web_titles[port]} |\n" else: log += f"| {port} | ✅开放 | - |\n" for port in unique_ports: log += f"| {port} | 🚫关闭 | - |\n" return log except FileNotFoundError: error_msg = "错误: 未找到 fscan 命令,请确保 fscan 已安装并在 PATH 中" return error_msg except Exception as e: error_msg = f"发生错误: {e}" return error_msg # 把端口字符串转换为列表 # 例如: '80,443-445,8080' 转换为 ['80', '443', '444', '445', '8080'] def parse_ports(ports): ports_all = [] for port_item in ports.split(','): if '-' in port_item: # 处理端口范围 start_port, end_port = map(int, port_item.split('-')) ports_all.extend([str(port) for port in range(start_port, end_port + 1)]) else: # 处理单个端口 ports_all.append(port_item) return ports_all # Flask 应用初始化 app = Flask(__name__) # 定义 Flask 路由和视图函数 @app.route('/', methods=['GET', 'POST']) def index(): if request.method == 'POST': ip = request.form['ip'] ports = request.form['ports'] scan_status = scan_ports(ip, ports) return render_template('index.html', content=scan_status) return render_template('index.html') if __name__ == "__main__": if len(sys.argv) == 1: # 作为Web服务器运行 # app.run(port=8080, host='0.0.0.0') app.run(debug=True, port=8080, host='0.0.0.0') # debug 可以试试更新 elif len(sys.argv) == 3: # 作为命令行工具运行 ip_address = sys.argv[1] ports = sys.argv[2] scan_ports(ip_address, ports) else: print("用法: python script.py <IP> <PORTS>") sys.exit(1) ``` `templates\index.html` ```html <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>端口扫描器_造物者W</title> <style> body { font-family: 'Roboto', sans-serif; margin: 0; padding: 0; background-color: #f4f4f4; line-height: 1.6; } .header { background: linear-gradient(135deg, #667eea, #764ba2); color: #fff; padding: 20px; text-align: center; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); position: relative; } .github-link { position: absolute; top: 10px; right: 20px; color: #fff; text-decoration: none; } .github-icon { width: 24px; height: 24px; vertical-align: middle; } .container { max-width: 800px; margin: 20px auto; padding: 20px; background-color: #fff; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); border-radius: 10px; } .content { margin-bottom: 20px; } .footer { text-align: center; padding: 20px 0; background-color: #333; color: #fff; } pre { background-color: #272822; color: #f8f8f2; padding: 15px; border-radius: 5px; overflow-x: auto; } code { font-family: 'Source Code Pro', monospace; } a { color: #4CAF50; text-decoration: none; } a:hover { text-decoration: underline; } label { display: block; margin-bottom: 5px; font-weight: bold; } input[type="text"] { width: calc(100% - 22px); padding: 10px; margin-bottom: 20px; border: 1px solid #ddd; border-radius: 5px; box-sizing: border-box; } input[type="submit"] { background-color: #4CAF50; color: #fff; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; font-size: 16px; } input[type="submit"]:hover { background-color: #45a049; } @media (max-width: 600px) { .container { margin: 20px; padding: 15px; } .header { padding: 15px; } input[type="text"], input[type="submit"] { width: 100%; } } /* Markdown 渲染样式 */ .markdown-body { box-sizing: border-box; min-width: 200px; max-width: 800px; margin: 0 auto; /* padding: 10px; */ background-color: #fff; border-radius: 10px; } .markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 { margin-top: 1em; margin-bottom: 0.5em; font-weight: bold; } .markdown-body p { margin: 0.5em 0; } .markdown-body ul, .markdown-body ol { padding-left: 1.5em; margin: 0.5em 0; } .markdown-body table { width: 100%; border-collapse: collapse; margin: 1rem 0; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); } .markdown-body th, .markdown-body td { border: 1px solid #ddd; padding: 12px; text-align: left; } .markdown-body th { background-color: #f2f2f2; } .markdown-body blockquote { border-left: 4px solid #667eea; padding-left: 1em; color: #666; margin: 0.5em 0; background-color: #f9f9f9; } .markdown-body pre { background-color: #272822; color: #f8f8f2; padding: 15px; border-radius: 5px; overflow-x: auto; } .markdown-body code { font-family: 'Source Code Pro', monospace; background-color: #f5f5f5; padding: 2px 4px; border-radius: 3px; } </style> <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&family=Source+Code+Pro:wght@400;700&display=swap" rel="stylesheet"> </head> <body> <div class="header"> <h1>端口扫描器</h1> </div> <div class="container"> <!--- 如果代理为子路径,这里也修改为子路径(如 /fscan/) ---> <form action="/" method="post" onsubmit="showLoadingMessage()"> <label for="ip">IP地址:</label> <input type="text" id="ip" name="ip" placeholder="扫描的域名或IP" required value="{{ request.form.get('ip', '') }}"> <label for="ports">端口范围:</label> <input type="text" id="ports" name="ports" placeholder="扫描的端口: 80,443-445,8080" required value="{{ request.form.get('ports', '') }}"> <input id= "submit" type="submit" value="开始扫描"> </form> <script> // 扫描中提示(自动加载和隐藏) function showLoadingMessage() { document.getElementById('submit').value = '扫描中,请稍后...'; document.getElementById('submit').disabled = true; // 禁用使其不可点击 } </script> </div> <!-- 如果有测试数据则显示到下方 --> {% if content %} <div class="container"> <label>扫描结果</label> <!-- 原始Markdown内容,将不会被显示 --> <pre id="markdown-content" style="display: none;">{{ content }}</pre> <!-- 转换后的Markdown内容将显示在这里 --> <div id="rendered-content" class="markdown-body"></div> </div> <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <script> // 确保DOM加载完毕后执行 document.addEventListener("DOMContentLoaded", function() { var markdownContent = document.getElementById("markdown-content").textContent; var renderedContent = marked.parse(markdownContent); // 使用parse方法转换Markdown到HTML // 将转换后的HTML内容设置到rendered-content div中 document.getElementById("rendered-content").innerHTML = renderedContent; // 由于我们不再需要预格式化的Markdown文本,可以将其从文档流中移除 document.getElementById("markdown-content").remove(); }); </script> {% endif %} <div class="footer"> <p>由 <a href="https://doc.918178.xyz" target="_blank">造物者W</a> 提供支持</p> </div> </body> </html> ``` ## docker 构建 ```bash <项目路径> ├─Dockerfile # docker构建文件 ├─app.py # Web主程序 ├─fscan # Fscan(后端) └─templates └─index.html # 主页面 # 就多个 Dockerfile ``` 构建并部署 ```bash docker build -t fscan_web:latest . # 构建镜像 docker run -d --name fscan_web -p 8080:8080 fscan_web:latest # 部署镜像 # 直接使用构建好的 docker run -d --name fscan_web -p 8080:8080 nas.918178.xyz:10088/library/fscan_web:latest # 如果使用反向代理到子路径,需要修改 index.html 中路径为子路径 ``` `Dockerfile` ```bash # 使用 python 作为底层镜像 FROM python:3.8-slim-buster # 设置工作目录 WORKDIR /app # 安装所需环境 RUN pip install flask -i https://pypi.tuna.tsinghua.edu.cn/simple # 复制当前目录所有文件到工作目录 COPY . . # 设置外部访问端口提示(需和内容一致) EXPOSE 8080 # 容器执行时运行命令 CMD ["python", "app.py"] ```
造物者W
2024年6月25日 20:30
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码