Introduction
寒假的时候学了一下Flask,开发了自己现在的博客,本来寒假的时候就已经要准备开发一个CTF平台给实验班考核的时候用的,但是一直没有规划好架构,所以一直延期了。
一直是知道CTFD这个平台的,但是一直没有仔细研究过,直到最近研读了一下源码和文档,发现他是真的香!所以就有了这篇文章的诞生,记录一下魔改的过程和遇到的坑点。
Dir Structure
CTFd
├── CTFd
│ ├── admin # 后台
│ ├── api # API
│ ├── cache # 缓存设置
│ ├── events # 全局event事件(弹窗)设置
│ ├── logs # 日志文件夹
│ ├── models # 数据库结构
│ ├── plugins # 插件(二次开发)
│ │ ├── challenges
│ │ │ └── assets
│ │ └── dynamic_challenges
│ │ └── assets
│ ├── schemas # 封装的Class
│ ├── themes
│ │ ├── admin # 后台前端主题
│ │ │ ├── static
│ │ │ └── templates
│ │ └── core # 前台前端主题
│ │ ├── static
│ │ └── templates
│ ├── uploads # 上传的文件夹
│ └── utils # 封装的各模块函数
│ ├── config
│ ├── countries
│ ├── crypto
│ ├── dates
│ ├── decorators # 这里访问速率设置
│ ├── email
│ ├── encoding
│ ├── events
│ ├── exports
│ ├── helpers
│ ├── initialization # 这里有CSRF设置
│ ├── logging
│ ├── migrations
│ ├── modes
│ ├── notifications
│ ├── plugins
│ ├── scores
│ ├── security
│ ├── sessions
│ ├── updates
│ ├── uploads
│ ├── user
│ └── validators
├── docs
├── migrations
│ └── versions
├── scripts
└── tests # 功能模块测试
├── admin
├── api
├── challenges
├── oauth
├── teams
├── users
└── utils
其实基本上能魔改的地方就前端和CTFD可供二次开发的Plugins,只需要大开脑洞,想好要加什么功能,就可以直接开干,因为是真的不难
Front-end Transformation
前端其实没啥可改的,就是换下配色 / 换下布局,其他地方都是jinja2
动态输出,照搬原样就可以了。
CTFD后台其实支持增加独立Page,但是如果不需要大改的话,直接用那个也能满足一些需求。
但是作为一个“爱折腾的”水瓶儿,肯定不满足原版CTFD的美工,就自己魔改了一个新的。
Notification
加上Timeline会稍微好看一些
Matrix
自制记分板,这个在下一Part的Plugins里面会仔细讲
Challenges
这里的配色一直没调好,但是还凑合
Plugins Transformation
看下面这一部分之前,建议先看看官方WIKI里关于Plugins的介绍
Dynamic Instance Challenge
这里分享一下2018级实验班考核时用到的动态下发题目容器的Plugins
其实就是自己二次开发一个新的Challenge Type,官方demo中也包含了一个编程(Programming)的新Challenge Type,感兴趣的可以也去研究一下。
因为动态下发题目的Challenge Type跟平时的static
类型没啥区别,只需要自己自定义一下
- 新Challenge额外的数据库字段(如部署方式、题目目录等)
attempt()
判断Flag对错 / 作弊与否
Dynamic Instance Distribution
上面的只是新的Challenge Type,虽然我们可以也可以把动态下发容器的代码放到上面一起,但是感觉分开不同的模块对后续的优化更便利,所以重新创建一个Plugin写我们动态下发容器。
这里新建Plugin的模版就可以不抄上面的了,只需要有一个load()
函数用于app启动时导入插件即可,剩下的就是一般的Python函数编写。
容器的下发有两种可选方案:
- docker
- docker-compose
两种方案其实差不多,第一种适合类AWD那种直接挂载源码就能跑那种,第二种兼容各种出题环境,给出题人更大的发挥空间。
但是都有一些Bug就是:
- 都需要调用
Docker API
,而且宿主机与API主机的Docker Volume目录要相同,不然无法挂载目录 - 出内网题时,容易有BUG(在同一网段,会打到隔壁老王的机子;不在同一网段,没有足够的内网IP分配导致不能支持多人同时做题)
最后我采用如下方案
Matrix Scoreboard
这个记分板的想法来源于一开始看到官方github的plugin库里面有大牛开发的记分板,但是发现他是老版本的,所以还是要做一个适配v2.*
版本的才能用,于是又自己魔改了一波,加了一二三血的Flag和适配一二三血的总分算法,改完还是很满意的,但是分页一直没弄,因为要加分页感觉后端代码改动有点大,所以就先不折腾了。先用着,等到后面有时间再改善一下。
Tricks
Nginx Reverse Proxy
Notification
如果需要反代CTFD的话,需要注意:Notification那个events
需要额外配置这个,因为他是服务端主动推送的,所以连接需要Keep-Alive
保持连接,否则管理员发全员弹窗的时候会无法成功发送。
location /events {
proxy_pass http://d0g3-ctfd:4000/events;
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding off;
proxy_buffering off;
proxy_cache off;
}
附上我的Nginx配置给大家参考
location / {
proxy_pass http://d0g3-ctfd:4000;
#ssl settings
proxy_set_header X-Forwarded-Port $server_port;
#settings
# 如果遇到前端https,后端http可以尝试下面注释里的设置
# proxy_redirect http:// https://;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_max_temp_file_size 0;
proxy_hide_header X-Frame-Options;
add_header X-Frame-Options SAMEORIGIN always;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_temp_file_write_size 64k;
}
Automatic register
自动注册用户脚本
# -*- coding: utf-8 -*-
# @Author: 0aKarmA_骅文
# @Date: 2019-06-27 14:19:25
# @Last Modified by: 0aKarmA
# @Last Modified time: 2019-06-27 23:46:49
import requests, re
def register_user(left, right):
for i in range(left,right):
name = "TEST" + str(i)
email = name + "@d0g3.cn"
url = 'https://domain/register'
s = requests.Session()
rq = s.get(url)
token = "".join(re.findall(r'nonce" value="(.*)"', rq.text))
data = {
"name": name,
"email": email,
"password": "password",
"nonce" : token
}
rqq = s.post(url, data)
print("Success Register " + name + " " + str(rqq.status_code))
if __name__ == '__main__':
register_user(0,10)
Summary
平台和题目经过80+同学同时在线并发测试,负载远低于我自己做100+的异步并发~~简直有点不敢相信。。
除了下发不同题目容器时需要清理浏览器缓存(如果不固定端口的话,应该不会存在这个问题),其他的体验应该还是不错的。