给CTFd加上ACM评测功能

闲得蛋疼.jpg

去年参加西电办的中学生CTF嫖了五千块钱,今年就能来西电办中学生CTF.jpg,真实的一批

去年的ACM题是直接起了个hustoj(我记得),这样就需要工作人员不断地检查榜单并且手动发放flag,工作量大且效率低。于是我就来整个CTFdOJ,交的代码AC了自动加分。

前置

CTFd plugin

与其说是“魔改CTFd”不如说“给CTFd整个插件”
CTFd自带的动态积分题目就是一个典型的插件。这个插件给CTFd添加了一种题目。
那么添加“ACM题目类型”理所当然也应该写成一个插件

沙箱的选择

评测沙箱需要用来控制程序的行为,而且是OJ的核心部件,要精确统计程序的运行时间/空间占用信息。
由于有很多现成的,那我就找一个拿来用吧
综合功能和LICENSE等多种因素,最终选择了QDOJ的Judger

一些决定

  • 将评测机与CTFd分离开来。
    • 考虑到CTFd只是一个题目平台,并不应该负责繁重的计算任务
    • 将评测机和平台放在一起有修改成绩的隐患
  • 做安全的同学大概对Python更加熟悉,于是支持对Python程序的评测
  • 通过配置文件能随时添加新的语言支持

撸代码

plugin

负责添加/展示/设置题目,并在评测正确时为相应队伍加上对应的分数

折腾了半天。。。推翻了以前越写越复杂还要改CTFd自己的数据库的写法以后重做了这个东西:ICPC Plugin

translation layer

主要负责接受来自CTFd plugin的评测请求
缓存测试用例,避免每次都要把40多M的input/output重新发一遍

由于需要不同的功能,还是用flask方便一点。通过不同的URL来定位不同的功能。
于是有了JudgeServer
对于不同的语言支持,可以在worker.json中配置。其中可以配置编译命令,执行命令,并且通过向命令中注入变量来控制细节。
解释型语言不写编译命令就是了。

translation layer::权限控制

ACM沙箱最头疼的就是权限。
首先,用了别人的沙箱,可以丢过给别人。
其次,这货跑在docker里头,断了外网。希望可以一劳永逸。
还有,具体的权限控制假定都能通过命令行参数完成。比如
java的-Djava.security.manager选项
Python可以跑在venv里头

总体流程

1
2
3
4
5
6
7
8
9
10
11
12
13
                   +--------+            +--------+
+-----------+ | | |
| R | | 1 | |
v | +------------> |
+------+---+ | | | |
| | | <------------+ |
|contestant| | CTFd | 2 | Judger |
| | | Plugin | | |
+------+---+ | | | |
| | | 3 | |
| S | +------------> |
+---------->+ | | |
+--------+ +--------+

上图中1、2、3分别表示CTFd在接收到一次submission(S)后与Judger可能的三种行为

code action
1 发送代码+题目id+语言,进行评测
2 返回评测结果与评测过的最后一组数据的运行情况
3 发送题目测试用例的URLtodo+设定的资源限制,缓存题目评测信息

在收到一次Submission(S)后,Plugin首先尝试进行1
如果Judger此时并没有缓存过这个题目,则会返回评测错误,此时Plugin会尝试进行3,Judger则会缓存当前题目
Plugin执行完3后会再次尝试1

真是憨憨,自嗨行为