SUCTF五校联合招新赛已经过去几天了。现在从主办方的角度来写写我第一次办比赛整个过程吧…
首先本次比赛是江苏高校信息安全联盟SU内东南大学、南京航空航天大学、南京理工大学、三江学院、金陵科技学院五校联合举办的队内招新赛,原本计划是出题给萌新做的,结果到后面是题目越来越难2333…感觉内部出题也形成了一定的竞争,然后结果就不提了,变成了大佬屠榜赛。
数据
本次比赛由于最后放的调查问卷,而且收集到的调查问卷远远达到签到题得分人数,姑且用来做个大概的分析吧。
首先是本次参赛人数,注册人数达到了463个队,最后调查问卷填写人数为81个队。
五个学校的学校数据就不分析了,其中其他学校占到了242个队,基本变成了半公开赛…但是由于还是想保护萌新,就没有变成完全的公开赛。否则我担心会被日穿…
第一天开赛的情况,由于docker环境没配置好,导致有一题pwn
直接被打穿了,还被改了flag
,大师傅们真的tql…
年级所占比例:
比赛难度调查统计:
时间安排调查统计:
比赛题目调查统计:
比赛评分调查统计(满分5分):
以上数据均由比赛调查问卷数据统计得出。
整个比赛历时7天,从2018/11/7 10:00:00 CST – 2018/11/14 10:00:00 CST,比赛期间无暂停,总共放出题目40道题,其中pwn
类7道,web
类14道,rev
类8道,misc
类11道。并没有无人做出的题目。
Preparation
起初要搞SUCTF联合招新其实在开学初貌似就在SU内部群就提出来了,因为之前2016年也有过SUCTF类似的联合招新赛,当时徐院长还是作为美女客服的时候,当时我还对着徐院长的头像一脸懵逼的时候。
结果拖到了10月底这样然后才真正搞起来,拉了小群然后开始计划出题,我也就才去拉赞助。于是乎,长亭、赛宁双方这边我谈的都不是很顺利,主要也是由于第一次拉赞助,没有写proposal,进度就一直拖慢了挺多的,而且跟白师傅那边要机器也没沟通顺利,平台自己也并没有很及时地搭建起来。然后在这一切的原因之下,导致了比赛只能往后推了两天,由原计划的11/5推到了11/7,由于自己也是第一次改CTFd,改的也比较慢,于是乎我记得直到11/7日凌晨我依然还在改平台。
然后11/5晚上这样整合题目的时候,由于各个师傅用出题模版理解不太一样,也怪自己没有让各位师傅开个小会啥的强调一下出题模板,导致整合题目的时候还有一堆格式不对的题目模板,也统统全部都让出题人规范了,也耗费了白师傅不少精力去改这些题目。
这里主要记录下我这边改CTFd
的经历吧,我看网络上也并没有详细的更改CTFd
的流程。
CTFd
首先明确一下自己的需求:
- 在注册界面提供学校的下拉框选择
- 在得分榜显示学校
- 更改主页添加赞助商信息
虽然只是这简简单单的几个需求,也把我这个不太熟悉flask
的人折腾了好一阵子。
我的操作环境在Ubuntu 16.04下完成的。
首先参照CTFd官方仓库文档进行安装,这几步也比较简单。
接着我们可以直接现在CTFd/themes/core/templates/register.html
中加入对应的片段,对应的option
对应的value
可以是中文。
1
2
3
| <select class="form-control input-filled-valid" name="school" id="school-input">
<option value="xx大学">xx大学</option>
</select>
|
其次我们需要在models.py
中找到对应的Class Teams
,这里初始化了队伍的一些信息,所以我们这里再加上一个school
成员就好
1
| school = db.Column(db.String(128))
|
之后初始化的时候,我一直对于C的多重构造一直耿耿于怀,想着这里初始化Teams的时候也根据参数的个数不同使用不同的构造方法,这样可以不用修改原来是用的构造方法,查阅资料发现可以这么写。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| import time
class Date:
"""方法一:使用类方法"""
# Primary constructor
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
# Alternate constructor
@classmethod
def today(cls):
t = time.localtime()
return cls(t.tm_year, t.tm_mon, t.tm_mday)
|
虽然可以这么写,但是我在弄排行榜的时候,第一次访问有新队伍的排行榜总会报错。
1
| 'unicode' object has no attribute 'label'
|
注册队伍老是不能把学校写进sqlite
,这就很烦了。而且第一次注册的时候老是报错,结果最后询问了东大的师傅,他们的写法是在models.py
给class Teams
加入school
成员后,在auth.py
下的register
方法中,这么写
1
2
3
4
5
6
7
8
9
10
11
| school = request.form['school'] #获取school参数
...
if len(errors) > 0:
return render_template('register.html',errors=errors,name=request.form['name'],email=request.form['email'],password=request.form['password'])
else:
with app.app_context():
team = Teams(name, email.lower(), password)
team.school = shcool #直接让school参数赋值给school成员变量
db.session.add(team)
db.session.commit()
db.session.flush()
|
使用原来的构造方法,这个问题就解决了…2333
解决了注册的问题,排行榜的显示也就比较简单了,首先改CTFd/themes/core/templates/scoreboard.html
中的模版代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| <div id="scoreboard" class="row">
<div class="col-md-12">
<table class="table table-striped">
<thead>
<tr>
<td scope="col" width="10px"><b>Place</b></td>
<td scope="col"><b>Team</b></td>
<td scope="col"><b>School</b></td> //加入School表头
<td scope="col"><b>Score</b></td>
</tr>
</thead>
<tbody>
{% for team in teams %}
<tr>
<th scope="row" class="text-center">{{ loop.index }}</th>
<td><a href="{{ request.script_root }}/team/{{ team.teamid }}">{{ team.name | truncate(50) }}</a></td>
<td>{{ team.school }}</td> //加入team.school内容
<td>{{ team.score }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
|
接下来把就去scoreboard.py
获取team.school
,通过大概的猜测以及推断,我们可以带改知道从这里修改:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| if admin:
standings_query = db.session.query(
Teams.id.label('teamid'),
Teams.name.label('name'),
Teams.school.label('school'),
Teams.banned, sumscores.columns.score
)\
.join(sumscores, Teams.id == sumscores.columns.teamid) \
.order_by(sumscores.columns.score.desc(), sumscores.columns.id)
else:
standings_query = db.session.query(
Teams.id.label('teamid'),
Teams.name.label('name'),
Teams.school.label('school'),
sumscores.columns.score
)\
.join(sumscores, Teams.id == sumscores.columns.teamid) \
.filter(Teams.banned == False) \
.order_by(sumscores.columns.score.desc(), sumscores.columns.id)
|
这样scoreboard
基本就改完了。接着我们再去管理员的Teams
界面加入学校名方便查队伍的时候知道是哪个学校的。同样,先改CTFd/themes/admin/templates/teams.html
1
2
3
4
5
| <td class="team-id" value="{{ team.id }}">{{ team.id }}</td>
<td class="team-name" value="{{ team.name }}"><a href="{{ request.script_root }}/admin/team/{{ team.id }}">{{ team.name | truncate(32) }}</a>
</td>
<td class="team-email d-none d-md-table-cell d-lg-table-cell" value="{{ team.email }}">{{ team.email | truncate(32) }}</td>
<td class="team-id d-md-table-cell d-lg-table-cell" value="{{ team.school }}">{{ team.school }}</td>
|
这样基本就完成了,接下来我们修改首页,直接配置好之后去Admin
面板通过编辑Pages
来操作会更加方便。(一开始我直接在views.py
中修改index,这个是要被写进数据库的pages
中的,我一边改一边update
数据库,现在想起来简直蠢爆。
到这里我们自定义CTFd基本就完成了。(有些功能仍然还会报错,例如导出比赛功能,因为Teams
多了一个字段school
,所以很大一部分其他报错,虽然目前我只遇到这个功能会报错,基本都是因为自定义增加了这个字段导致的,去看看源码把相应缺少的加上基本就能解决了。
Enviroment
本次采用的配置是使用nginx
、mysql
、uwsgi
来配置ctfd,配置环境在ubuntu serve 18.04
下,安装过程就不提了。网上教程很多。
这里说一下nginx
的配置:
1
2
3
4
5
6
7
8
| server {
listen 80;
server_name Your domain;
location / {
include uwsgi_params;
uwsgi_pass unix:/tmp/uwsgi.sock;
}
}
|
然后需要把CTFd连接数据库的方法改成mysql
,我们可以看到CTFd/config.py
中
1
2
| DATABASE_URL = os.environ.get(
'DATABASE_URL') or 'sqlite:///{}/ctfd.db'.format(os.path.dirname(os.path.abspath(__file__)))
|
所以我们需要在环境中定义DATABASE_URL
,参考它给出的格式
1
| e.g. mysql+pymysql://root:<YOUR_PASSWORD_HERE>@localhost/ctfd
|
但是我们最好不要用root
使用mysql
,而且如果我当时用root
用户的话,直接报错了
1
| ERROR 1698 (28000): Access denied for user 'root'@'localhost'
|
参考MySQL ERROR 1698 (28000) 错误,是因为我的root
用户的plugin没有配置好,而且用root
用户连接mysql
不太安全,所以我们这里创建了一个ctfd
的数据库用户,并给予它ctfd
数据库的所有权力。
1
2
3
4
5
6
7
| CREATE USER 'ctfd'@'localhost' IDENTIFIED BY 'Your password'; //创建用户
select user, plugin from mysql.user; //查看当前所有数据库用户的plugin
update mysql.user set plugin='mysql_native_password' where user='ctfd'; //更改ctfd用户的plugin,可以使用密码登录ctfd用户
GRANT ALL ON ctfd.* TO 'ctfd'@'localhost'; //给予ctfd用户ctfd数据库的权力
|
虽然我们可以直接在bash
中
1
| export DATABASE_URL=mysql+pymysql://ctfd:Your password@localhost/ctfd
|
但是这样不太优雅,我们可以新建一个uwsgi.ini
中写入
1
2
3
4
5
6
7
| [uwsgi]
# Where you've put CTFD
chdir = /your/dir/CTFd
env = "DATABASE_URL=mysql+pymysql://ctfd:Your password@localhost/ctfd"
|
这样会比较好。这样,连接数据库的操作就基本完成了。
然后为了方便运维,可以使用Supervisor
对uwsgi
进行管理操作。贴一下当时的配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
| [unix_http_server]
file = /var/run/supervisor.sock
;chmod=0700 ; socket file mode (default 0700)
;chown=nobody:nogroup ; socket file uid:gid owner
;username=user ; (default is no username (open server))
;password=123 ; (default is no password (open server))
[inet_http_server] ; inet (TCP) server disabled by default
port=9001 ; (ip_address:port specifier, *:port for ;all iface)
username=admin ; (default is no username (open server))
password=suctf_new_2018 ; (default is no password (open server))
[supervisord]
;logfile=/tmp/supervisord.log ; (main log file;default $CWD/supervisord.log)
;修改为 /var/log 目录,避免被系统删除
logfile=/var/log/supervisord/supervisord.log ; (main log file;default $CWD/supervisord.log)
; 日志文件多大时进行分割
logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB)
; 最多保留多少份日志文件
logfile_backups=10 ; (num of main logfile rotation backups;default 10)
loglevel=info ; (log level;default info; others: debug,warn,trace)
pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
;设置启动supervisord的用户,一般情况下不要轻易用root用户来启动,除非你真的确定要这么做
;user=chrism ; (default is current user, required if root)
minfds=1024 ; (min. avail startup file descriptors;default 1024)
minprocs=200 ; (min. avail process descriptors;default 200)
childlogdir=/var/log/supervisord/ ; ('AUTO' child log dir, default $TEMP)
[supervisorctl]
; 必须和'unix_http_server'里面的设定匹配
;serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket
;修改为 /home/supervisor 目录,避免被系统删除
serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket
username=admin ; should be same as http_username if set
password=suctf_new_2018 ; should be same as http_password if set
[program:uwsgi]
command=uwsgi -s /tmp/uwsgi.sock --chmod-socket=666 -w 'CTFd:create_app()'
directory=/xxx/CTFd
autostart = true
startsecs = 5
redirect_stderr = true
;这对这个program的log的配置,上面的logfile_maxbytes是supervisord本身的log配置
stdout_logfile_maxbytes = 20MB
stdoiut_logfile_backups = 20
stdout_logfile = /var/log/supervisord/uwsgi.log
|
具体可以参考:
Python 进程管理工具 Supervisor 使用教程
用Supervisord管理Python进程
Python 进程管理工具 Supervisor 使用教程
【已解决】supervisor去启动gunicorn的Flask出错:supervisor couldn’t setuid to 0 Can’t drop privilege as nonroot user
最后白师傅通过做成了系统服务来管理的uwsgi
,这里留到NUAACTF再来更吧。
PS:今天中午12:28刚刚发现CTFd更新到了2.0,可能稍有不同。后续做完NUAACTF我会再更一下。