简介这是一套基于 Python3 与 Django 开发的 AWD 网络攻防比赛裁判平台源码版本为 beta v2.0面向计算机相关专业的毕业设计、课程设计及项目开发学习者也适合想理解 CTF 攻防赛制实现原理的开发者参考。平台整体分为裁判机与靶机两部分通过特定接口完成靶机 flag 与服务器之间的通信涵盖赛题下发、flag 提交与判分等核心流程。资源包共 80 个文件约 95KB以 18 个 py 源码文件与 44 个 pyc 编译文件为主体另含 6 个 html 模板、sqlite3 数据库、README 说明文档及 LICENSE 授权文件结构紧凑、便于快速部署与二次开发。目前已有 81 人学习浏览。借助完整源码与项目文档读者可掌握 Django 后端接口设计、前后端交互与攻防平台业务逻辑并在此基础上扩展计分规则、队伍管理或靶机镜像等功能是理解 AWD 赛制落地的实用参考。1. 从零搭一套 AWD 攻防平台Python 后端加 HTML 前端到底能跑多远很多人第一次接触 AWD 网络攻防比赛是在校内选拔或者 CTF 线下赛里。裁判一声令下每支队伍拿到一台靶机既要守住自己的 flag又要想办法打进别人的服务里拿分。真到要自己搭一套这样的比赛平台时问题就来了赛题怎么下发、flag 怎么轮换、分数怎么实时刷新、队伍之间怎么隔离。这套基于 Python 加 HTML 开发的 AWD 网络攻防比赛平台解决的正是这一整套流程的自动化问题。后端用 Python 处理靶机调度、flag 校验和计分逻辑前端用 HTML 配合少量 JavaScript 做实时排行榜和赛题面板。它适合做毕业设计、课程设计也适合作为安全社团内部训练的基础设施。下面我按实际落地的顺序把选型、搭建、核心逻辑和踩坑点讲清楚。2. 技术选型与整体架构为什么是 Python 加 HTML 而不是别的组合2.1 后端为什么选 Python 而不是 Go 或 JavaAWD 平台的核心压力不在高并发而在逻辑灵活性和开发速度。一场校内赛通常 10 到 30 支队伍每队一台靶机总连接数不过几百。这个量级下Python 的 Flask 或 FastAPI 完全扛得住。真正吃时间的是 flag 轮换、计分规则调整、靶机状态检测这些需要频繁改逻辑的部分。用 Python 写改一个计分公式就是改几行代码重启服务即可。用 Go 或 Java 当然性能更好但编译部署和框架配置会吃掉大量本可以花在赛题设计上的时间。我一般会选 Flask 作为 Web 框架原因是它的扩展点清晰蓝图机制适合把「队伍管理」「靶机管理」「计分板」拆成独立模块。数据库用 SQLite 起步比赛规模上去后再换 PostgreSQL。SQLite 的好处是零配置一个文件带走所有数据做毕业设计演示时特别省事。2.2 前端为什么用原生 HTML 而不是 React 或 Vue比赛平台的前端页面数量有限登录页、赛题面板、排行榜、管理后台加起来不超过十个页面。用原生 HTML 加 CSS 加 JavaScript配合 fetch 调后端接口完全够用。引入 React 或 Vue 会带来构建工具链、依赖管理、打包部署一整套额外工作对于课程设计来说属于过度工程。更重要的是AWD 比赛过程中排行榜需要高频刷新通常是每 5 到 10 秒拉一次数据。这个用原生 JavaScript 的 setInterval 加 fetch 就能实现代码量不超过 30 行。用框架反而要处理状态管理和组件生命周期调试成本更高。2.3 整体架构与数据流平台的数据流是这样的管理员在后台录入队伍信息和靶机 IP系统为每台靶机生成初始 flag 并写入靶机上的指定文件。比赛开始后计分服务按固定间隔检查每台靶机的 flag 是否被篡改同时接收各队提交的 flag 进行校验。前端排行榜通过轮询接口获取最新分数并渲染。靶机隔离是 AWD 的关键。常见做法是每队一台独立虚拟机或容器队伍只能访问自己的靶机。平台本身不负责网络隔离这部分交给 Docker 网络或 VLAN 配置。平台只记录靶机地址和对应的队伍编号在计分时按队伍维度汇总。注意靶机隔离如果没做好一支队伍可以直接访问另一支队伍的靶机比赛就失去意义了。建议在平台部署前先用 Docker 的 bridge 网络或自定义网络把每台靶机隔开只暴露必要的服务端口。3. 后端核心模块实现flag 轮换、计分与靶机状态检测3.1 用 Flask 搭建基础服务与数据库模型先建项目骨架。目录结构建议这样分app.py 作为入口models.py 放数据库模型scoring.py 放计分逻辑checker.py 放 flag 检测逻辑templates 放 HTML 模板static 放 CSS 和 JavaScript。# models.py from flask_sqlalchemy import SQLAlchemy db SQLAlchemy() class Team(db.Model): id db.Column(db.Integer, primary_keyTrue) name db.Column(db.String(64), uniqueTrue, nullableFalse) token db.Column(db.String(128), uniqueTrue, nullableFalse) score db.Column(db.Integer, default0) class Target(db.Model): id db.Column(db.Integer, primary_keyTrue) team_id db.Column(db.Integer, db.ForeignKey(team.id)) ip db.Column(db.String(64), nullableFalse) port db.Column(db.Integer, default80) current_flag db.Column(db.String(128)) flag_path db.Column(db.String(256), default/flag) alive db.Column(db.Boolean, defaultTrue)这段代码定义了队伍和靶机两张表。Team 表的 token 字段用于队伍登录认证避免用明文密码。Target 表的 flag_path 指定 flag 文件在靶机上的路径current_flag 存当前轮次的 flag 值。alive 字段标记靶机是否存活计分时只对存活靶机计分。参数说明name 字段长度 64 够用token 用 128 位随机字符串。ip 字段存靶机地址port 默认 80如果靶机服务跑在其他端口就改这个值。flag_path 默认 /flag实际部署时根据靶机镜像调整。3.2 flag 自动轮换与校验逻辑flag 轮换是 AWD 的核心机制。每隔一段时间系统为每台靶机生成新 flag 并写入靶机同时更新数据库。旧 flag 失效防止队伍提前拿到 flag 后一直提交。# checker.py import random import string import paramiko def generate_flag(): chars string.ascii_letters string.digits return flag{ .join(random.choices(chars, k32)) } def push_flag_to_target(target, new_flag): ssh paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: ssh.connect(target.ip, usernameroot, passwordtoor, timeout5) cmd fecho {new_flag} {target.flag_path} ssh.exec_command(cmd) return True except Exception as e: print(fpush flag failed: {target.ip}, {e}) return False finally: ssh.close()generate_flag 生成 32 位随机字符串并包在 flag{} 里这是 CTF 常见格式。push_flag_to_target 用 paramiko 通过 SSH 连到靶机写文件。这里用密码认证是为了演示方便实际部署建议换成密钥认证。参数说明timeout5 表示 SSH 连接超时 5 秒靶机多的时候这个值别设太大否则轮换一轮要等很久。flag_path 从数据库读不同靶机可以有不同的 flag 路径。如果靶机不支持 SSH可以改成用 HTTP 接口或共享存储写入。轮换调度用 APScheduler 或简单的 threading.Timer 实现。每轮开始前先检查靶机存活存活的才推送新 flag不存活的跳过并标记 aliveFalse。3.3 计分板接口与前端轮询计分逻辑要处理三种情况提交 flag 正确加分、flag 被其他队伍篡改扣分、靶机宕机扣分。这里先实现最核心的提交 flag 加分。# app.py 片段 from flask import Flask, request, jsonify from models import db, Team, Target from checker import generate_flag app Flask(__name__) app.config[SQLALCHEMY_DATABASE_URI] sqlite:///awd.db db.init_app(app) app.route(/api/submit, methods[POST]) def submit_flag(): data request.get_json() team_token data.get(token) submitted_flag data.get(flag) team Team.query.filter_by(tokenteam_token).first() if not team: return jsonify({status: error, msg: invalid token}), 403 target Target.query.filter_by(current_flagsubmitted_flag).first() if target and target.team_id ! team.id: team.score 10 db.session.commit() return jsonify({status: ok, score: team.score}) return jsonify({status: fail, msg: wrong flag}), 200 app.route(/api/scoreboard) def scoreboard(): teams Team.query.order_by(Team.score.desc()).all() return jsonify([{name: t.name, score: t.score} for t in teams])submit_flag 接收队伍 token 和提交的 flag。先验证 token 有效性再在 Target 表里查这个 flag 属于哪台靶机。如果 flag 存在且不属于提交者自己的队伍就给提交者加 10 分。这个逻辑防止队伍提交自己的 flag 刷分。scoreboard 接口按分数降序返回所有队伍前端直接渲染成排行榜。实际比赛中还可以加上「最后提交时间」「被攻破次数」等字段。前端轮询代码放在 static/scoreboard.js 里// static/scoreboard.js async function refreshScoreboard() { const resp await fetch(/api/scoreboard); const data await resp.json(); const tbody document.querySelector(#score-table tbody); tbody.innerHTML ; data.forEach((team, idx) { const row trtd${idx 1}/tdtd${team.name}/tdtd${team.score}/td/tr; tbody.insertAdjacentHTML(beforeend, row); }); } setInterval(refreshScoreboard, 5000); refreshScoreboard();这段代码每 5 秒拉一次排行榜数据并重绘表格。setInterval 的 5000 是毫秒改成 10000 就是 10 秒刷新一次。比赛前期可以刷快一点后期分数变化慢可以调大间隔减少服务器压力。提示轮询接口要做好缓存控制在响应头里加 Cache-Control: no-cache否则浏览器可能返回缓存的旧数据排行榜看起来像卡住了。4. 前端页面与靶机管理HTML 模板、管理后台与状态面板4.1 用 Jinja2 模板渲染赛题面板Flask 自带 Jinja2 模板引擎HTML 页面里可以直接嵌入后端变量。赛题面板需要展示当前队伍的靶机信息、flag 提交入口和实时分数。!-- templates/dashboard.html -- !DOCTYPE html html langzh-cn head meta charsetutf-8 titleAWD 赛题面板/title link relstylesheet href/static/style.css /head body h1队伍{{ team.name }}/h1 div classtarget-info p靶机地址{{ target.ip }}:{{ target.port }}/p p当前分数span idscore{{ team.score }}/span/p /div div classsubmit-area input typetext idflag-input placeholder输入 flag button onclicksubmitFlag()提交/button /div table idscore-table theadtrth排名/thth队伍/thth分数/th/tr/thead tbody/tbody /table script src/static/scoreboard.js/script script src/static/submit.js/script /body /html模板里 {{ team.name }} 和 {{ target.ip }} 由后端渲染时传入。score-table 的 tbody 留空由 JavaScript 轮询填充。submit.js 负责处理 flag 提交把输入框的值和队伍 token 一起 POST 到 /api/submit。参数说明langzh-cn 声明中文页面避免浏览器自动翻译。style.css 里建议把排行榜表格固定在页面右侧方便比赛时随时查看。4.2 管理后台批量导入队伍与靶机管理后台是给裁判用的需要支持批量导入队伍信息和靶机地址。常见做法是上传 CSV 文件后端解析后写入数据库。# admin.py 片段 import csv from io import StringIO app.route(/admin/import, methods[POST]) def import_teams(): file request.files[file] stream StringIO(file.stream.read().decode(utf-8)) reader csv.DictReader(stream) for row in reader: team Team(namerow[team_name], tokengenerate_token()) db.session.add(team) db.session.flush() target Target(team_idteam.id, iprow[target_ip], portint(row.get(port, 80))) db.session.add(target) db.session.commit() return jsonify({status: ok, count: reader.line_num - 1})CSV 格式要求表头包含 team_name、target_ip、port 三列。generate_token 生成随机 token可以用 secrets.token_hex(32)。db.session.flush() 先写入 Team 拿到 id再用来关联 Target。参数说明port 列可以缺省默认 80。如果靶机服务跑在 8080 或其他端口CSV 里要写清楚。导入完成后返回导入条数方便核对。4.3 靶机存活检测与状态面板比赛过程中靶机可能因为队伍操作失误宕机平台需要定期检测并展示状态。检测方式根据靶机服务类型定常见的是 TCP 端口探测或 HTTP 请求。# checker.py 补充 import socket def check_alive(ip, port, timeout3): try: sock socket.create_connection((ip, port), timeouttimeout) sock.close() return True except (socket.timeout, ConnectionRefusedError): return False def update_all_targets(): targets Target.query.all() for t in targets: t.alive check_alive(t.ip, t.port) db.session.commit()check_alive 尝试建立 TCP 连接成功则靶机存活。timeout3 表示 3 秒连不上就算宕机。update_all_targets 遍历所有靶机更新状态建议每 30 秒跑一次。状态面板在管理后台用颜色区分绿色表示存活红色表示宕机。前端根据 /api/targets 接口返回的 alive 字段渲染。注意TCP 探测只能判断端口是否开放不能判断服务是否正常响应。如果靶机上的 Web 服务挂了但端口还在探测会误判为存活。更严格的做法是发一个 HTTP 请求检查返回码。5. 避坑与常见问题部署 AWD 平台时最容易翻车的五个点5.1 flag 写入失败但平台显示成功现象轮换日志显示 flag 已推送但靶机上 flag 文件内容没变。原因通常是 SSH 连接成功但 exec_command 是异步的命令还没执行完 SSH 就关闭了。解决在 exec_command 后加 stdout.channel.recv_exit_status() 等待命令完成或者改用 SFTP 直接写文件。5.2 排行榜分数跳动或重复计分现象同一支队伍提交同一个 flag 多次分数一直涨。原因是 submit_flag 接口没有做去重。解决在数据库里加一张 submission 表记录每次提交的 flag 和队伍提交前先查是否已提交过。或者简单点提交成功后立即把该靶机的 current_flag 置空。5.3 靶机时间不同步导致 flag 轮换混乱现象部分靶机 flag 更新了部分没更新计分时对不上。原因是靶机系统时间和平台服务器时间偏差太大调度器触发时靶机还没到轮换时间点。解决所有靶机统一配置 NTP 时间同步平台调度器用服务器本地时间即可。5.4 前端轮询导致服务器 CPU 飙升现象比赛开始后服务器负载很高排行榜刷新变慢。原因是每支队伍的前端都在轮询30 支队伍每 5 秒一次就是每秒 6 个请求如果接口里做了复杂查询就会拖慢。解决给 scoreboard 接口加缓存用 Flask-Caching 缓存 3 秒或者把轮询间隔调到 10 秒。5.5 靶机隔离没做好导致跨队访问现象A 队可以直接访问 B 队的靶机服务。原因是所有靶机在同一网段且没有防火墙规则。解决用 Docker 自定义网络每队一个网络只把平台服务器加入所有网络。或者用 iptables 限制源 IP只允许对应队伍网段访问。6. 进阶技巧用 Docker 一键拉起整套 AWD 环境前面讲的都是平台本身的代码但实际部署时最花时间的往往是靶机环境。我后来习惯用 Docker Compose 把平台和靶机一起编排一条命令拉起整套环境。# docker-compose.yml version: 3 services: platform: build: ./platform ports: - 5000:5000 volumes: - ./data:/app/data networks: - awd-net target-1: image: vuln-web:latest networks: awd-net: ipv4_address: 172.20.0.11 target-2: image: vuln-web:latest networks: awd-net: ipv4_address: 172.20.0.12 networks: awd-net: driver: bridge ipam: config: - subnet: 172.20.0.0/24这个编排文件把平台和两台靶机放在同一个 bridge 网络里靶机用固定 IP。平台通过 172.20.0.11 和 172.20.0.12 访问靶机。实际比赛中每队一台靶机按这个模式往下加 target-3、target-4 即可。参数说明subnet 选 172.20.0.0/24 是为了避开常见的 172.17.0.0/16 Docker 默认网段。ipv4_address 手动指定方便平台数据库里直接写死 IP。volumes 把 data 目录挂出来SQLite 数据库文件不会随容器销毁丢失。验证方法启动后先访问 http://localhost:5000/admin 确认平台正常再用 docker exec 进入 target-1 检查 flag 文件是否存在。如果 flag 文件不存在检查靶机镜像的 Dockerfile 里有没有创建 /flag 文件。我自己的习惯是每次改完计分逻辑先用两台靶机跑一轮 5 分钟的模拟赛确认 flag 轮换、提交计分、排行榜刷新都正常再扩大到全量队伍。这个习惯帮我省了很多比赛现场翻车的机会。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
Excel宏入门教程:解决环境卡死,掌握性能优化实战 Excel宏入门教程:解决环境卡死,掌握性能优化实战 刚打开 Excel 准备写宏,结果 VBA 编辑器报错“未找到引用”或者干脆闪退,是不是让你抓狂?别急,90% 的新手都卡在 配置环境 这一步,导致后面学 性能优化 无从下手。今天这篇… · 2026/9/23 7:04:06
国产AI框架AiPy实战:中文NLP任务性能优化与部署指南 1. 项目概述"国产平替"这个概念在技术圈已经火了很久,但真正能打的工具并不多。今天要聊的这个AiPy工具,是我这半年来在多个AI项目中实际验证过的国产替代方案。它不仅完全兼容主流AI框架的API接口,更重要的是在中文NLP任务上的表现… · 2026/9/23 7:04:06
5个坑搞定hi文,附完整示例让新手少熬夜 5个坑搞定hi文,附完整示例让新手少熬夜 刚学完语法,看着满屏的代码却不知怎么搭项目?别慌。我见过太多人卡在“会写Hello World”到“能跑通业务逻辑”这一步。今天这篇 完整示例 ,不讲虚的,直接带你把 hi文 这套逻辑跑通。… · 2026/9/23 7:53:36
基于微服务的商城秒杀系统实战:从架构拆解到压测验证 简介:这是一份面向高校计算机专业毕业设计的Java微服务商城秒杀系统完整项目源码,适合正在准备毕设或希望深入理解高并发架构的开发者参考。项目以微服务方式拆分秒杀业务,涵盖Spring Boot、Spring Cloud Zuul网关、RabbitMQ消息队列、Docker… · 2026/9/23 7:53:36
新注册公司名称图解原理:3步搞定跨省转介性能瓶颈 新注册公司名称图解原理:3步搞定跨省转介性能瓶颈 官方文档翻了三遍,还是没搞懂新注册公司名称在跨省转介时的数据流转逻辑。别急,咱们直接上 图解原理 ,把那些晦涩的API调用链路和性能卡点一次性讲透。… · 2026/9/23 7:53:30
Yii2 资源管理实战:从资源包定义、发布到组合压缩的完整指南 Yii2 资源管理实战:从资源包定义、发布到组合压缩的完整指南 【免费下载链接】yii2 Yii 2: The Fast, Secure and Professional PHP Framework 项目地址: https://gitcode.com/gh_mirrors/yi/yii2
导读
本文以 Yii2 官方指南的「资源(Assets&… · 2026/9/23 7:53:30
OpenSpec实战:用规范驱动开发终结前后端联调之痛 OpenSpec 这个词,我在不少项目里见过它的影子:有人拿它当 API 规范,有人拿它当文档规范,还有人干脆把它当成一个装 Markdown 文件的文件夹,写完之后再也没人看。说实话,大部分团队都没把它的价值用出来。这… · 2026/9/23 7:53:30
3招搞定手机怎么下载微信面试难题实战项目解析 3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29