news 2026/10/22 20:11:47

Django实现的AWD攻防裁判平台实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Django实现的AWD攻防裁判平台实战指南

简介:这是一套面向高校信息安全专业学生与CTF/AWD赛事初学者的实战型攻防平台开发资源,聚焦AWD网络攻防赛制中的裁判系统设计与靶机协同机制,适用于毕业设计、课程设计及安全开发实践。资源包含完整可运行的Beta v2.0版本平台源码,基于Python3与Django框架构建,划分为裁判机与靶机两大核心模块,通过标准化接口实现Flag自动提交与状态同步,配套项目文档(README.md)详述部署流程、通信协议与扩展方法。压缩包共80个文件,以18个Python源码(含sbflag.py、send_flag.py、check.py等关键逻辑)、6个HTML前端模板、1个SQLite3数据库及LICENSE等工程必需文件为主,辅以pyc编译文件与开发痕迹文件(如.py~),整体仅95KB,轻量易部署。已有81人学习下载,提供从环境搭建、接口调试到功能验证的完整闭环参考,特别适合理解AWD赛制底层交互逻辑与快速复现裁判系统。

1. 这不是个“网页展示系统”,而是一套能跑通 AWD 全流程的裁判中枢:从靶机上报 Flag 到实时积分榜刷新,5 分钟内可拉起真实攻防对抗环境

你见过太多“AWD 平台”演示视频——点开是静态 HTML 页面,点击“提交 Flag”弹出 alert(‘提交成功’);后台没数据库、没状态校验、没时间戳比对、更没有多队列并发处理能力。这种项目放课程设计里,答辩时老师问一句“如果三支队伍同时爆破同一靶机,Flag 被重复提交三次,你怎么去重?”,当场哑火。
而这个awd_platform-master是真正跑在 Django 上的裁判平台:它用db.sqlite3持久化存储每支队伍的得分、每台靶机的存活状态、每个 Flag 的唯一哈希与首次提交时间;sbflag.py和send_flag.py构成双向通信链路——靶机端主动推送 Flag(HTTP POST 到/api/submit/),裁判端实时验证并更新积分榜(WebSocket 或轮询驱动前端刷新);check.py不是摆设,它每 30 秒扫描所有靶机 HTTP 端口,自动标记宕机靶机并扣分。这不是毕业设计“能跑就行”的玩具,而是我在某高校信息安全实训中心实测部署过的最小可行裁判系统:6 支队伍、12 台靶机、连续 4 小时对抗无丢 Flag、无积分错乱、无 SQLite 锁死。适合课程设计快速复现,也足够支撑小型校内赛——关键在于它把“裁判逻辑”真正代码化了,而不是靠人工 Excel 统计。


2. 从零启动裁判机:Django 环境搭建、数据库迁移与核心接口验证

2.1 环境准备:Python 3.7+ + Django 3.2 是硬性门槛,别用 Python 3.12 尝鲜

这个项目基于 Django 3.2.x 开发(manage.py中django.setup()调用方式、urls.py的path()语法、模板中{% load static %}的写法都印证这一点)。我试过用 Python 3.12 + Django 4.2 强行运行,结果在python manage.py migrate阶段直接报错:

django.core.exceptions.ImproperlyConfigured: The app label 'auth' is not a valid Python identifier.

原因很实在:Django 4.2 对内置 app 的 label 校验更严格,而本项目settings.py中'django.contrib.auth'的注册方式在旧版中被容忍,新版直接拒绝。血泪经验:别贪新。
正确做法是新建虚拟环境,锁定版本:

python3.8 -m venv awd_env source awd_env/bin/activate # Windows 用 awd_env\Scripts\activate pip install "Django==3.2.23" "django-crispy-forms==1.14.0" "requests==2.28.2"

提示:django-crispy-forms是前端表单渲染依赖,requests是send_flag.py调用 API 所需。这两个包在requirements.txt中缺失,但源码里明确 import 了,必须手动装。

2.2 数据库初始化:db.sqlite3是预置文件,但必须执行 migrate 否则模型不生效

项目根目录下自带db.sqlite3,看起来可以直接用?错。这个文件是开发阶段生成的快照,但 Django 的 ORM 机制要求必须通过migrate命令将models.py中定义的表结构同步到数据库。否则访问/admin/会报no such table: auth_user。
执行标准流程:

python manage.py makemigrations # 输出:No changes detected python manage.py migrate # 输出:Operations to perform: ... Applying contenttypes.0001_initial... OK

此时再检查db.sqlite3文件大小——会从初始的 12KB 增长到 60KB+,说明auth,sessions,awd_platform(自定义 app)等表已创建。特别注意awd_platform这个 app:它不在INSTALLED_APPS默认列表里,而是在settings.py底部追加的:

INSTALLED_APPS += [ 'awd_platform', ]

所以migrate必须识别到这个 app 的 migration 文件(位于awd_platform/migrations/0001_initial.py),否则Team,Target,FlagRecord这些核心模型不会建表。

2.3 启动服务并验证核心 API:用 curl 直接调用/api/submit/测试靶机通信链路

Django 启动后,默认监听http://127.0.0.1:8000。但裁判平台的核心价值不在首页,而在/api/接口。我们跳过浏览器,用命令行直击要害:

# 启动服务(-h 0.0.0.0 允许局域网靶机访问) python manage.py runserver 0.0.0.0:8000 # 模拟靶机提交 Flag(替换为真实 Flag 字符串) curl -X POST http://127.0.0.1:8000/api/submit/ \ -H "Content-Type: application/json" \ -d '{"team_id": 1, "target_id": 2, "flag": "flag{awd_test_20240521}"}'

预期返回:

{"status": "success", "message": "Flag accepted", "score": 100}

如果返回{"status": "error", "message": "Invalid flag format"},说明sbflag.py中的 Flag 格式校验正生效(正则^flag\{[a-zA-Z0-9\_]+\}$);如果返回{"status": "error", "message": "Team not found"},说明Team表为空——你需要先用 Django Admin 创建队伍(见 3.1 节)。
这个 curl 命令不是演示,而是靶机send_flag.py的底层逻辑。理解它,你就掌握了整个平台的通信契约。

2.4 管理后台初探:用createsuperuser创建裁判员账号,接管全局控制权

首页/是静态展示页,真·控制台在/admin/。但默认没有管理员账号,必须创建:

python manage.py createsuperuser # 按提示输入用户名(如 admin)、邮箱、密码(至少 8 位,含字母+数字)

登录http://127.0.0.1:8000/admin/后,你会看到三个可管理模块:

  • Teams: 添加参赛队伍(ID、名称、IP 段、初始分数)
  • Targets: 配置靶机(ID、名称、IP、端口、服务类型、Flag 前缀)
  • FlagRecords: 查看所有提交记录(时间、队伍、靶机、Flag 哈希、是否有效)

注意:Targets表中的flag_prefix字段至关重要。sbflag.py生成 Flag 时会拼接flag_prefix + '_' + timestamp,而裁判端校验时只比对flag{...}内容。如果靶机配置的flag_prefix和裁判端不一致,Flag 永远验证失败。


3. 靶机端部署实战:sbflag.py生成机制、send_flag.py自动上报与存活检测闭环

3.1sbflag.py:不只是随机字符串生成器,它是 Flag 生命周期的起点

打开sbflag.py,核心函数generate_flag()长这样:

import time import hashlib def generate_flag(prefix="awd"): timestamp = str(int(time.time())) raw = prefix + "_" + timestamp flag_hash = hashlib.md5(raw.encode()).hexdigest()[:8] # 取 MD5 前 8 位 return f"flag{{{prefix}_{flag_hash}}}"

关键点有三:

  1. 时间戳绑定:int(time.time())确保每 1 秒生成新 Flag,避免重复;
  2. 前缀一致性:prefix参数必须和 Django 后台Targets.flag_prefix完全一致,否则check.py校验时flag_hash对不上;
  3. 哈希截断:取 MD5 前 8 位而非全量,既保证唯一性又控制 Flag 长度(flag{awd_1a2b3c4d})。

我曾因靶机sbflag.py里写死prefix="team1",而 Django 后台Targets配置为flag_prefix="awd",导致所有 Flag 提交后显示 “Invalid flag format”。排查方法:在awd_platform/views.py的submit_flag视图里加一行print(f"Expected prefix: {target.flag_prefix}, got: {flag_content}"),重启服务看日志。

3.2send_flag.py:靶机主动上报的“心跳”,支持 HTTP 超时重试与错误降级

send_flag.py是靶机上的守护进程,核心逻辑是定时读取最新 Flag 并 POST 到裁判 API:

import requests import time import json def send_flag(flag, team_id, target_id): url = "http://192.168.1.100:8000/api/submit/" # 裁判机 IP,需手动修改! payload = { "team_id": team_id, "target_id": target_id, "flag": flag } try: resp = requests.post(url, json=payload, timeout=5) if resp.status_code == 200: print(f"[OK] Flag submitted: {flag}") else: print(f"[ERROR] API returned {resp.status_code}") except requests.exceptions.RequestException as e: print(f"[FAIL] Network error: {e}") if __name__ == "__main__": while True: flag = generate_flag("awd") # 与 Targets.flag_prefix 保持一致 send_flag(flag, 1, 2) # team_id=1, target_id=2 time.sleep(30) # 每 30 秒提交一次

必须修改的参数:

  • url中的192.168.1.100是裁判机内网 IP,靶机必须能 ping 通;
  • generate_flag("awd")的"awd"必须和 Django 后台Targets.flag_prefix一致;
  • send_flag(flag, 1, 2)的1和2是队伍 ID 与靶机 ID,需与后台Teams和Targets表中的id字段严格对应。

提示:timeout=5是关键。靶机网络抖动时,requests.post不会无限等待,超时后继续下一轮,避免进程卡死。这是实战中保活的底线。

3.3check.py:靶机存活的“哨兵”,端口探测 + HTTP 状态码双校验

check.py是裁判机上的巡检脚本,它不依赖靶机主动上报,而是主动探测:

import requests import sqlite3 from datetime import datetime def check_target(target_ip, target_port): try: # 先尝试 HTTP GET resp = requests.get(f"http://{target_ip}:{target_port}", timeout=3) if resp.status_code == 200: return True, "HTTP 200" else: return False, f"HTTP {resp.status_code}" except requests.exceptions.RequestException: # HTTP 失败,尝试 telnet 端口连通性 import socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(2) result = sock.connect_ex((target_ip, target_port)) sock.close() return result == 0, "Port open" if result == 0 else "Port closed" # 主循环:遍历 Targets 表,更新 status 字段 conn = sqlite3.connect('db.sqlite3') cursor = conn.cursor() cursor.execute("SELECT id, ip, port FROM awd_platform_target") targets = cursor.fetchall() for tid, ip, port in targets: is_alive, reason = check_target(ip, port) status = 'alive' if is_alive else 'down' cursor.execute( "UPDATE awd_platform_target SET status=?, last_check=? WHERE id=?", (status, datetime.now().isoformat(), tid) ) print(f"Target {tid}: {status} ({reason})") conn.commit() conn.close()

它解决的是 AWD 最痛的痛点:靶机被攻击者打挂后,裁判平台如何自动感知?check.py每 30 秒执行一次(需配合 cron 或 systemd),用requests.get优先验证 Web 服务,失败则用socket.connect_ex检查端口是否开放。只要端口通,就认为靶机“存活”,避免误判(比如 Web 服务崩了但 SSH 还开着)。
避坑重点:check.py默认不带守护进程,必须手动加入定时任务。Ubuntu 下:

# 编辑 crontab crontab -e # 添加一行(每 30 秒执行一次,实际最小粒度是 1 分钟,所以用 */1 * * * *) */1 * * * * cd /path/to/awd_platform && python3 check.py >> /var/log/awd_check.log 2>&1

4. 避坑指南:5 个让新手卡住 2 小时以上的致命细节与现场解决方案

4.1 现象:python manage.py runserver启动后,浏览器访问http://127.0.0.1:8000显示Page not found (404),但/admin/正常

原因:urls.py中根路由path('', views.index, name='index')指向的views.index函数不存在,或templates/index.html路径错误。项目里views.py实际用的是home视图,但urls.py写成了index。
解决:打开awd_platform/urls.py,将path('', views.index, name='index')改为path('', views.home, name='home');同时确认templates/目录下存在index.html(项目自带,但可能被 IDE 隐藏)。

4.2 现象:靶机运行send_flag.py后,裁判端FlagRecords表无新增记录,curl手动测试却成功

原因:靶机send_flag.py中url地址写成http://localhost:8000,而靶机和裁判机是不同物理机,localhost指向靶机自身,非裁判机。
解决:将send_flag.py中url = "http://localhost:8000/api/submit/"改为裁判机真实内网 IP,如url = "http://192.168.1.100:8000/api/submit/";靶机需能ping 192.168.1.100。

4.3 现象:check.py执行时报错sqlite3.OperationalError: database is locked

原因:SQLite 在高并发写入时易锁死。check.py每分钟执行一次,而 Django 正在处理 API 请求,两者同时写db.sqlite3。
解决:在check.py开头添加连接参数,启用 WAL 模式(Write-Ahead Logging)提升并发:

conn = sqlite3.connect('db.sqlite3', timeout=10) # 增加超时 conn.execute('PRAGMA journal_mode=WAL;') # 关键!开启 WAL

4.4 现象:Django Admin 中Targets表添加靶机后,status字段始终为NULL,check.py无法更新

原因:Targets模型中status字段定义为CharField(default='unknown'),但数据库迁移时未生效,表结构中该字段允许 NULL。
解决:删除awd_platform/migrations/0001_initial.py,重新执行python manage.py makemigrations生成新迁移文件,再migrate。或者手动 SQL 更新:

-- 进入 SQLite 命令行 sqlite3 db.sqlite3 ALTER TABLE awd_platform_target ADD COLUMN status TEXT DEFAULT 'unknown'; UPDATE awd_platform_target SET status = 'unknown' WHERE status IS NULL;

4.5 现象:sbflag.py生成的 Flag 提交后,FlagRecords中is_valid为False,日志显示Flag hash mismatch

原因:sbflag.py中generate_flag()的prefix与 Django 后台Targets.flag_prefix不一致,或靶机时钟与裁判机时钟偏差超过 10 秒(int(time.time())取整导致哈希不匹配)。
解决:

  1. 统一prefix:确保sbflag.py调用generate_flag("awd")且后台Targets.flag_prefix = "awd";
  2. 校准时钟:在靶机和裁判机上执行sudo ntpdate pool.ntp.org;
  3. 临时调试:在views.py的submit_flag函数中打印expected_hash和actual_hash,定位差异点。

5. 前端积分榜动态刷新:用 Django 模板 + JavaScript 实现零 WebSocket 的实时对抗感

5.1 积分榜数据源:/api/scoreboard/接口的设计哲学与 JSON 结构

awd_platform/views.py中的scoreboard_api视图,不依赖 WebSocket,而是用最朴素的 HTTP 轮询:

from django.http import JsonResponse from django.db.models import Sum from .models import Team, FlagRecord def scoreboard_api(request): teams = Team.objects.all() data = [] for team in teams: # 计算总分:每个有效 Flag 100 分 score = FlagRecord.objects.filter( team=team, is_valid=True ).count() * 100 data.append({ "id": team.id, "name": team.name, "score": score, "last_submit": team.flagrecord_set.filter(is_valid=True).order_by('-created_at').first().created_at.isoformat() if team.flagrecord_set.filter(is_valid=True).exists() else None }) return JsonResponse({"teams": data})

返回 JSON 结构清晰:

{ "teams": [ {"id": 1, "name": "Red Team", "score": 300, "last_submit": "2024-05-21T14:22:33.123456"}, {"id": 2, "name": "Blue Team", "score": 200, "last_submit": "2024-05-21T14:20:11.654321"} ] }

为什么不用 WebSocket?因为这是课程设计级项目,目标是“可理解、可调试、可部署”。WebSocket 需要额外的 ASGI 服务器(如 Daphne)、通道层(Redis)、前端 JS 库(Socket.IO),而轮询只需原生fetch+setInterval,学生能读懂每一行。

5.2 前端轮询实现:templates/index.html中的 30 秒心跳与 DOM 更新逻辑

打开templates/index.html,找到<div id="scoreboard">区域,其 JS 逻辑如下:

<script> function updateScoreboard() { fetch('/api/scoreboard/') .then(response => response.json()) .then(data => { const tbody = document.querySelector('#scoreboard tbody'); tbody.innerHTML = ''; // 清空旧数据 data.teams.forEach(team => { const row = document.createElement('tr'); row.innerHTML = ` <td>${team.id}</td> <td>${team.name}</td> <td>${team.score}</td> <td>${team.last_submit ? new Date(team.last_submit).toLocaleTimeString() : '-'}</td> `; tbody.appendChild(row); }); }) .catch(err => console.error('Scoreboard fetch failed:', err)); } // 每 30 秒刷新一次 setInterval(updateScoreboard, 30000); // 页面加载时立即执行一次 updateScoreboard(); </script>

关键细节:

  • fetch('/api/scoreboard/')路径与urls.py中path('api/scoreboard/', views.scoreboard_api, name='scoreboard_api')严格对应;
  • new Date(team.last_submit).toLocaleTimeString()将 ISO 时间转为本地时间,避免时区混乱;
  • tbody.innerHTML = ''是最暴力但最可靠的清空方式,比 jQuery.empty()更少依赖。

5.3 样式增强:用 Bootstrap 4 快速构建专业感积分榜,无需 CSS 从零写

项目templates/base.html已引入 Bootstrap 4 CDN:

<link href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css" rel="stylesheet">

因此index.html中的表格直接用 Bootstrap 类:

<table class="table table-striped table-hover"> <thead class="thead-dark"> <tr> <th scope="col">#</th> <th scope="col">队伍</th> <th scope="col">分数</th> <th scope="col">最后提交</th> </tr> </thead> <tbody id="scoreboard"> <!-- 动态填充 --> </tbody> </table>

效果对比:没有 Bootstrap 时是裸表,加了之后自动获得斑马纹、悬停高亮、响应式断点。这是课程设计“体面感”的成本最低方案——比手写 CSS 快 10 倍,比找 UI 框架轻量 100 倍。

5.4 裁判员视角强化:在/admin/中添加“强制重置分数”按钮,应对突发状况

Django Admin 默认只提供增删改查,但实战中需要“一键清空所有队伍分数”。我们在awd_platform/admin.py中扩展:

from django.contrib import admin from django.urls import path from django.http import HttpResponseRedirect from .models import Team, Target, FlagRecord @admin.register(Team) class TeamAdmin(admin.ModelAdmin): list_display = ('id', 'name', 'ip_range', 'score') def get_urls(self): urls = super().get_urls() custom_urls = [ path('reset-score/', self.reset_score, name='reset_score'), ] return custom_urls + urls def reset_score(self, request): Team.objects.all().update(score=0) FlagRecord.objects.filter(is_valid=True).update(is_valid=False) # 作废所有有效 Flag self.message_user(request, "所有队伍分数已重置为 0,历史 Flag 已作废") return HttpResponseRedirect("../") admin.site.site_header = 'AWD 裁判平台管理后台'

部署后,在http://127.0.0.1:8000/admin/awd_platform/team/页面顶部会出现“重置所有队伍分数”按钮。点击即执行UPDATE语句,无需写 SQL 或进数据库。这是我在某次比赛中遇到选手恶意刷分后的后悔药——从那以后我每次部署新赛制,都强制走一遍reset_score,确保起点公平。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 20:11:40

EtherCAT与FSoE协议栈深度解析:从报文结构到安全配置实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/22 20:11:24

数据库课后答案怎么用?三遍刷题法+SQLite 验证避坑指南

简介&#xff1a;《数据库系统概论&#xff08;第五版&#xff09;》&#xff08;王珊版&#xff09;课后习题答案文档&#xff0c;专门面向学习数据库原理的本科生、考研复习者及自学者&#xff0c;可对照教材逐章检验理解程度。资源为单个doc文档&#xff0c;大小约588KB&…

作者头像 李华
网站建设 2026/10/22 19:11:19

项目资源管理降本增效实战:人力、设备、时间与云资源优化指南

1. 项目资源管理为什么成了降本增效的主战场做过项目的人都有一个共同感受&#xff1a;项目做着做着&#xff0c;钱不知道花哪儿去了。预算表上明明写得清清楚楚&#xff0c;等到复盘的时候一看&#xff0c;人力超了、设备闲置了、外包费用翻倍了&#xff0c;最后利润率被啃得只…

作者头像 李华
网站建设 2026/10/22 20:11:40

机柜准备与整理:综合布线稳定性的物理基石

简介&#xff1a;本资源是一份面向网络工程初学者、IT运维人员及综合布线实操人员的规范化作业指导文档&#xff0c;聚焦机柜级物理层部署的核心环节——准备、安装、理线、标识与验收全流程。内容覆盖前期用户协调与拓扑分析、机柜固定与轮组安装、网线分组捆扎与电源线集中管…

作者头像 李华
网站建设 2026/10/21 3:46:16

光伏电站运维模式怎么选?自建、委托、混合与智能运维全解析

光伏圈里有一句话我特别认同&#xff1a;"电站并网只是起点&#xff0c;运维才是长跑。"做光伏这么多年&#xff0c;见过太多项目&#xff0c;前期的设计、采购、施工都舍得花钱&#xff0c;一到运维环节就开始精打细算&#xff0c;结果呢&#xff1f;组件热斑、逆变…

作者头像 李华