[VY开源中国]零基础快速搭建一个VTF比赛平台

2 分钟阅读时长

发布时间:

我们希望能让大家更低门槛地实现CTF赛事…

现在想想, 自己还是挺能折腾的, 第一年什么都不懂入了网安圈, 学着拿ctfd在阿里云申请了免费服务器, 办理第一场比赛, 建立了sakana网络安全联队. 第二年总结了前年的痛点后, 我们也学习着自研了一套VTF比赛平台, 相比以前的工具, VTF平台有以下优势:

优势

  • VTF使用vlang编译到c, 有更高的效率,
  • 连接工具集成了自研的中间件, 具有高度可控性,
  • 严格规范了书写, 提供apifox进行扩展,
  • 平台本身以VY通用许可证进行开源, 便于版权转移,
  • 兼容范围广, 从电脑到手机, 支持随时随地打开.
  • 由sakana网络安全联队维护, 开发中更加强调安全性.

搭建平台

办理一场完整比赛的主要流程如下:

以下会以vtf为参考详细介绍各个环节中可能会遇到的问题, 欢迎指出问题或提出更通俗的解释.

获取开源工程

emmm, 本来我想跳过这里的, 不过考虑到即使有些天天比赛的师傅也会存在知识盲区, 还是决定写一下VTF项目的获取.

git clone

如果是熟悉开源项目的用户可能会选择直接使用:

git clone https://gitee.com/sakana_ctf/vtf

直接获取源码, 然后根据README.md文件进行配置, 恭喜你可以跳过这节了.

直接下载源码

几大git网站对普通用户的支持也非常友好, 即使如此我还是推荐使用git, 在服务器中可没有可视化交互界面, 我也会提供相应的解决手段, 例如scp:

scp [file] [user]@[ip地址]:[服务器上传位置]

官网提供了克隆/下载>下载zip为替代方案, 下载解压后获取VTF的源代码, 我们可以参考README.md文件进行配置.

解压

一般windows11自带了解压工具, 如果使用的系统足够老的话, 这里提供一份最基本的unzip以供解压.

搭建服务器

这里主要指的是云服务器, 也可以尝试自行部署一个服务器, 不过复杂度太高, 不符合快速搭建的需求, 暂不分析.

如果没有太高需求, 第一次注册我们可以在阿里云, 华为云, 腾讯云等厂家获取免费的云服务器, 这里也安利一下一直在支持CTF赛事的DK盾服务器厂商, 本次文章以DK盾的服务器为例.

配置(1h1g1m)

以1h1g1m为参考, 指服务器为1核CPU, 1GB内存, 宽带为1m.

1h1g可以直接参考家用电脑去理解:

  • 1h: 计算机执行程序需要CPU.
  • 1G: 运行中的数据会存放在内存(非硬盘空间)中.
  • 1m(1mbps): 用户访问时的每秒传输数据量, 同时访问数越多需要宽带越大.

针对以上概念(若有错误欢迎指出, 欢迎更通俗的表达方式), 我们在选择服务器时可以根据参赛人数与比赛平台选择适合自己的服务器, 以100名选手为例, 使用VTF平台在远程题目数量不多时可以考虑一台2h2G20M左右的服务器.

地名(例如宁波, 香港, 东京)

服务器越近在访问时速度会越快, 当然除了速度还要考虑政策等问题, 对于国内服务器, 如果要绑定域名需要满足以下几个条件:

  • 服务器时间在三个月以上

  • 办理赛事等职能不能以个人身份备案, 需要挂靠备案的单位或组织.

  • 域名需要出示证书, 信息需要与服务器的信息相同(例如在gen.xyz的域名是无法绑定上华为云的, 感觉除非满3个月后将域名转移到能提供证书的地方).

对于无法挂靠单位, 服务器时间短, 无法出示域名证书等情况推荐使用不需要备案的香港或国外服务器, 当然使用国外服务器也需要承担被墙之类的风险.

在服务器上布置环境

拿下厂商的云服务器后我们可以使用openssh连接上环境, 对于linux系统可以使用对应的包快速安装, win10以上系统也提供了集成的openssh以供使用, 以dk盾为例, 买下后在已开通产品找到服务器, 选择操作, 我们可以在终端(windows用户可以使用powershell)执行以下命令:

ssh [用户名]@[IP地址] -p [端口号]

接受协议后可以输入密码(注, 输入密码时终端不会显示), 登录进入服务器. 我们可以参考VTF的README.md文件进行部署, 如果想要自行编译可以先参考以下仓库安装vlang, 然后./main执行文件.

本地运行

在此之前我们可以本地拉取仓库测试一遍, 同样参考以上文件下载仓库代码进行编译后执行, 正常执行后我们看到以下信息:

暂不支持设置线程数: 3
[veb] Running app on http://localhost:8080/

我们可以在浏览器输入http://localhost:8080, 得到平台.

服务器运行

与本地相同进行处理, 但是执行后我们访问http://[IP地址]:8080, 但是可能无法显示平台, 这是因为默认没有没有设置安全组.

安全组设置

安全组可以在产品的安全组中进行设置, 我们可以新建一个自己的安全组, 然后新建策略, 每个端口可以传递一个服务, 有端口的服务属于应用层的服务, 计算机总共有TCP1-65535+UDP1-65535个端口, 在传递中由分为入方向和出方向, 以下是我的一些简单理解:

TCP, UDP, TLS

传输层上的两个传输协议, tcp协议需要交换3次信息进入稳定连接, udp协议则是直接进行不稳定发包. 与tcp相比, udp效率更高, 但是信息不完整, 适用于直播流等场景, 另外在应用层443端口的https安全协议中(就是常见的https://example.com), 传输层除了需要tcp的3次数据交换外还有tls的4次数据交换用于安全连接防止信息泄露.

IP策略

云服务器中在应用层通过端口进行服务时需要有安全组限制服务, 以防止非预期的访问形式: 包括非预期ip访问服务, 访问非预期端口服务, 过度进行数据交换. 办理一场简单的比赛可以考虑开放所有ip, 在安全组>新增策略>授权IP中我们可以填入0.0.0.0/0不限制ip的访问, 以下不详细解释.

入方向/出方向

规则方向中入方向表示客户端向服务器传入数据, 出方向表示从服务器向客户端传出数据, 在未进行重定向等操作前可以先简单理解规则方向是一一对应的, 如果需要让外部访问一套完整服务, 需要同时开放入方向和出方向, 例如同时设置入方向和出方向开放8080端口, 我们重新尝试访问http://[IP地址]:8080, 应该就成功了. 后面涉及到nginx会继续聊安全组, 现在可以暂时搁置一下.

解析域名

这里使用了genxyz的域名, 感谢gen.xyz提供的1年免费域名, 以gen.xyz为例, 购买好域名后在My Domains>Manage处选择Use default nameservers>Manage DNS, 进入后需要输入三组数据:

  • Host Name: 这不是服务器的计算机名, 以输入www为例, 最终服务器将解析到www.examples.com.
  • Record Type: 解析域名使用A(Address), 之前测试过在解析域名时Host Name不能选择*解析到二级域名. 如果绑定失败, 在非必要的情况下我们也可以考虑直接url重定向(URL Redirect)
  • Address: 解析域名只需要输入自己的ip地址.

完成后选择Save Changes, 过一会儿测试将ip地址换成域名, 能正常登录则表示解析成功.

tls证书配置

我喜欢用stl证书而不是ssl证书来表达, 主要与openssl工具进行区分, 我发现经常有人会混淆ssl与openssl, 导致沟通中出现问题, 在使用中stl可以近似理解成新的ssl, 不过严格来说stl和ssl不是完全相等的, 在这里提前声明.

这大概是最花时间的一步, 没有系统学习过计算机知识的我想要通过终端完成整套流程, 结果在一些奇奇怪怪的地方出了各种问题, 现在将总结到的经验分享给大家, 愿对大家有帮助:

需求

需要解决的问题如下:

  • 用户使用https://www.examples.xyz进行登录.
  • 不小心登入http://www.examples.xyz会跳转回https://wwww.examples.xyz
  • 登陆时可以缺省端口号
  • 避免出现进程的端口冲突
  • 保障用户交互数据不会被泄露

申请证书

第一步我们需要一份证明身份的证书和一个可以用来加密的私钥, 以在freessl申请证书为例, 感谢freessl为大家免费提供的证书. 找到需要的证书类型, 选择立即申请, 填写完申请域名和服务器后分别填写以下信息:

  • 证书类型:

    • RSA: 更主流的加密方式, 安全性和效率较底, 但兼容性高.

    • ECC: 在非欧空间进行加密, 能使用更少的资源完成更安全的加密, 不过考虑到非欧的复杂性, 可能存在一些特殊曲线导致易被攻击. 在pkcs中ECC不同于RSA, 一直以来很难完成一份规范.

  • 验证类型:

    • DNS: 通过添加dns验证域名所有权

    • 文件验证: 我采用的方法, 可以搭建一个简单网站完成验证, 过后我会提供搭建网站通过验证的源码, 只需简单操作即可.

  • CSR生成: 获取证书, 建议直接离线生成吧.

完成后官网会给出一个.key私钥文件, 一个挑战(一串随机字符与所需文件名)与服务器的对应下载地址, 我们可以按照需求把信息填入以下文件并运行:

from flask import Flask, send_file

app = Flask(__name__)

# 支持在`http://127.0.0.1/neko/test.py`中
# 下载内容为`12345`的文件
file_data = '12345'
file_name = './test.py'
route_path = '/neko'

with open(file_name, 'w') as f:
    f.write(file_data)

@app.route(f'{route_path}/<filename>')
def download(filename):
    return send_file(filename, as_attachment=True)


if __name__ == '__main__':
    app.run(host='0.0.0.0',port=80, debug=True)

访问通过后我们可以继续下载一份.pem证书, 现在拿到两份证书, 可以传递给服务器.

解决

stl加密在443端口, 我们需要启用证书服务, 并将运行的服务转发到443, 直接解决大概挺艰难的(不过应该不是无法解决), 所以我们使用nginx进行配置, 注意, nginx放置在80端口, 如果将服务端口设置为80端口会造成冲突, 所以现在我们至少需要3个端口:

  • 80: nginx将服务代理到443端口.
  • 443: stl协议端口
  • 8080(或其他端口): 服务(VTF)所在端口

安装nginx后在/etc/nginx/conf.d/nginx.conf如下配置新文件(改一下关键信息):

# HTTPS server
# 8080以stl重定向到443
server {
    listen       443 ssl;
    server_name  [域名地址];

    ssl_certificate     /home/ctf/.ssh/full_chain.pem;
    ssl_certificate_key /home/ctf/.ssh/private.key;

    ssl_session_timeout  5m;

    ssl_ciphers 'HIGH:!aNULL:!MD5:!kEDH';

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    ssl_prefer_server_ciphers  on;

    location / {
        proxy_pass http://[域名地址]:8080;
    }
}

完成后尝试nginx -t, 根据问题修改到正常后我们可以重启服务:

systemctl restart nginx

如果成功, 我们使用https://[域名]可以正常登录, 但是http://[域名]会显示nginx的默认网页. 我们需要做的有两件事:

  1. 不应该让nginx显示出来, 增加危害可能.
  2. http://[域名]应该重新定向到https://[域名]

重定向可以在nginx.conf增加以下内容:

# 80端口转发到443端口
server {
    listen 80;
    server_name ctf.vycma.xyz; # 替换为你的域名

    # 重定向所有 HTTP 请求到 HTTPS
    return 301 https://$host$request_uri;
}

当然问题还未解决, 我们无法保证非常规访问的情况, 我们注意到在安全组中, 80端口实际上只进行了代理, 并不需要用户向80端口交互, 故我们可以关闭80端口的入方向, 保证不会被访问. 以下是完整代码:

nginx配置

# HTTPS server
# 8080以stl重定向到443
server {
    listen       443 ssl;
    server_name  ctf.vycma.xyz;

    ssl_certificate     /home/ctf/.ssh/full_chain.pem;
    ssl_certificate_key /home/ctf/.ssh/private.key;

    ssl_session_timeout  5m;

    ssl_ciphers 'HIGH:!aNULL:!MD5:!kEDH';

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    ssl_prefer_server_ciphers  on;

    location / {
        proxy_pass http://ctf.vycma.xyz:8080;
    }
}

# 80端口转发到443端口
server {
    listen 80;
    server_name ctf.vycma.xyz; # 替换为你的域名

    # 重定向所有 HTTP 请求到 HTTPS
    return 301 https://$host$request_uri;
}