Back

SUCTF五校联合招新赛小记

SUCTF五校联合招新赛已经过去几天了。现在从主办方的角度来写写我第一次办比赛整个过程吧…

首先本次比赛是江苏高校信息安全联盟SU内东南大学、南京航空航天大学、南京理工大学、三江学院、金陵科技学院五校联合举办的队内招新赛,原本计划是出题给萌新做的,结果到后面是题目越来越难2333…感觉内部出题也形成了一定的竞争,然后结果就不提了,变成了大佬屠榜赛。

数据

本次比赛由于最后放的调查问卷,而且收集到的调查问卷远远达到签到题得分人数,姑且用来做个大概的分析吧。

首先是本次参赛人数,注册人数达到了463个队,最后调查问卷填写人数为81个队。

izikeH.md.png
izikeH.md.png
五个学校的学校数据就不分析了,其中其他学校占到了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可以是中文。

<select class="form-control input-filled-valid" name="school" id="school-input">
	<option value="xx大学">xx大学</option>
</select>

其次我们需要在models.py中找到对应的Class Teams,这里初始化了队伍的一些信息,所以我们这里再加上一个school成员就好

school = db.Column(db.String(128))

之后初始化的时候,我一直对于C的多重构造一直耿耿于怀,想着这里初始化Teams的时候也根据参数的个数不同使用不同的构造方法,这样可以不用修改原来是用的构造方法,查阅资料发现可以这么写。

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)

虽然可以这么写,但是我在弄排行榜的时候,第一次访问有新队伍的排行榜总会报错。

'unicode' object has no attribute 'label'

注册队伍老是不能把学校写进sqlite,这就很烦了。而且第一次注册的时候老是报错,结果最后询问了东大的师傅,他们的写法是在models.pyclass Teams加入school成员后,在auth.py下的register方法中,这么写

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中的模版代码

<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,通过大概的猜测以及推断,我们可以带改知道从这里修改:

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

<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

本次采用的配置是使用nginxmysqluwsgi来配置ctfd,配置环境在ubuntu serve 18.04下,安装过程就不提了。网上教程很多。

这里说一下nginx的配置:

server  {
    listen 80;
    server_name Your domain;
    location / {
        include uwsgi_params;
        uwsgi_pass unix:/tmp/uwsgi.sock;
    }
}

然后需要把CTFd连接数据库的方法改成mysql,我们可以看到CTFd/config.py

DATABASE_URL = os.environ.get(
        'DATABASE_URL') or 'sqlite:///{}/ctfd.db'.format(os.path.dirname(os.path.abspath(__file__)))

所以我们需要在环境中定义DATABASE_URL,参考它给出的格式

e.g. mysql+pymysql://root:<YOUR_PASSWORD_HERE>@localhost/ctfd

但是我们最好不要用root使用mysql,而且如果我当时用root用户的话,直接报错了

ERROR 1698 (28000): Access denied for user 'root'@'localhost'

参考MySQL ERROR 1698 (28000) 错误,是因为我的root用户的plugin没有配置好,而且用root用户连接mysql不太安全,所以我们这里创建了一个ctfd的数据库用户,并给予它ctfd数据库的所有权力。

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

export DATABASE_URL=mysql+pymysql://ctfd:Your password@localhost/ctfd

但是这样不太优雅,我们可以新建一个uwsgi.ini中写入

[uwsgi]

# Where you've put CTFD

chdir = /your/dir/CTFd

env = "DATABASE_URL=mysql+pymysql://ctfd:Your password@localhost/ctfd"

这样会比较好。这样,连接数据库的操作就基本完成了。

然后为了方便运维,可以使用Supervisoruwsgi进行管理操作。贴一下当时的配置文件

[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我会再更一下。

Licensed under CC BY-NC-SA 4.0

I am looking for some guys who have a strong interest in CTFs to build a team focused on international CTFs that are on the ctftime.org, if anyone is interested in this idea you can take a look at here: Advertisements


想了解更多有意思的国际赛 CTF 中 Web 知识技巧,欢迎加入我的 知识星球 ; 另外我正在召集一群小伙伴组建一支专注国际 CTF 的队伍,如果有感兴趣的小伙伴也可在 International CTF Team 查看详情


comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy