asjdf

一只在杭电摸鱼的小火鸡

关于CTF平台的一些大致构想
Jun 24, 2022
One minute read

0x00 前言

因为最近稍微闲下来一点了,所以重新开始发掘需求,然后就盯上了 CTF 平台。

一方面是因为自己目前还没遇到相对稳定且通用的 CTF 平台,另一方面也是觉得 CTF 平台是一个很好的学习机会。因为CTF平台需要处理诸如高并发、竞争、流控等问题,当中涉及到不少我未曾接触的技术问题。

0x01 目标功能

  • 动态 flag(按用户信息或按用户的特有id)
  • 同时支持多场比赛
  • 支持申请举行比赛
  • 赛题列表支持自主排版
  • 赛题可由主办方创建

0x02 功能相关实现细节

动态flag

即同一道题,不同用户解题后能获得不同的 flag

2021年hctf-game比赛所使用的平台在验证用户提交的 flag 的有效性上花费了较多的时间(随着越来越多的含有动态 flag 的题加入平台),导致不得不在反馈的及时性上做出了一定的妥协,还引发了条件竞争问题,出现了利用脚本快速提交多次 flag 能够刷分的漏洞。

为了避免重蹈覆辙,我认为应当如下设计 flag 验证及积分统计的相关流程。

  1. 记录用户原始提交记录,即将用户的提交记录存在一张表内,表字段包含:记录 id(雪花)、提交时间、用户 id、比赛 id、是否已验证、是否有效。单独起队列对表内字段进行验证,仅计算flag的有效性,不计算具体的积分数,这样就能避免 2 中的操作对前几血积分的影响。
  2. 记录人工操作记录,即将管理员对于flag有效性的操作记录进行记录,表字段包含:记录 id(雪花)、操作时间、管理员 id、比赛 id、被改动的用户原始提交记录 id、是否有效
  3. 输出有效 flag 记录时,首先应取管理员最后一次的操作,然后用管理员的操作覆盖取出的用户原始提交记录,即可得最终的有效 flag 列表
  4. 事实上在比赛中,积分排名的变动并不需要做到实时,可容许一定的延迟,因此排名交给独立的线程进行刷新并丢给redis缓存
  5. 另外补充在计算flag的有效性的时候,应优先在正常范围验证,即先验证属于该用户应得到的 flag,再验证是否为其他用户的有效 flag。
  6. 另外为了增强积分更新效率,需要做到断点更新,即积分榜在更新时需要记录最后使用的3中的记录的id,后续更新积分榜的时候从断点位置继续往后更新。包括管理员操作后从管理员操作处进行更新。

但如果仔细想一下这个业务的实现方式就会发现,当一个用户在 flag 的提交高峰期进行提交的话无法立即得出flag的有效性的结果,因此应当有两个 api 用来处理 flag 的提交问题。第一个接收 POST 请求,只要成功提交进入数据库即可返回,且应当提交记录的 id,也就是 1 中的记录 id;第二个接收 GET 请求,用于后续获取提交的 flag 的验证状态(使用轮询或其他方式)。前端在显示的时候应当给“验证中”之类的提示即可。

同时支持多场比赛

即平台中可设置多个子比赛

这个业务功能倒相对容易实现,只需要专门新建一张表用于存储比赛信息即可。

支持申请举行比赛

普通用户可通过发起专门页面发起举办比赛的申请

鉴于这种申请表单只给平台方审查,一共也就一张表,因此并不需要做过多的设计,只需要实现简单的增删改查,平台管理员可在后台直接通过或拒绝申请即可。

赛题列表支持自主排版

支持分页+分栏目即可

感觉主要还是前端的活,后端这边用表来存一下比赛的题目的栏目分级,大概画个树之类的吧。

赛题可由主办方创建

这个就是上面支持申请举行比赛的扩充即可。

0x03 选型

前端:

  • react
  • antd

后端:

  • flamego
  • gorm
  • redis (排名、操作锁)

0x04 更多细节

flag计算相关

为优化 flag 有效性的计算速度,可以考虑使用 redis 进行一定的缓存。有几种缓存方案:缓存用户已提交flag的有效性、缓存有效的flag。在这个专案中,我认为为降低资源占用,仅用布隆过滤或布谷鸟过滤器将非正确flag进行一个筛是较优的解。

分布式

本专案中提出的排名、积分更新算法能够省去大量的锁,就目前所需的功能来看,只需要 MySQL 的锁就能 cover 绝大部分需求了。


Back to posts