记一次CTF出题

什么是CTF?

CTF(Capture The Flag)中文一般译作夺旗赛,在网络安全领域中指的是网络安全技术人员之间进行技术竞技的一种比赛形式。CTF起源于1996年DEFCON全球黑客大会,以代替之前黑客们通过互相发起真实攻击进行技术比拼的方式。已经成为全球范围网络安全圈流行的竞赛形式,2013年全球举办了超过五十场国际性CTF赛事。而DEFCON作为CTF赛制的发源地,DEFCON CTF也成为了全球最高技术水平和影响力的CTF竞赛,类似于CTF赛场中的“世界杯” 。

部署篇

CTFd

CTFd 是一个 Capture The Flag 框架,专注于易用性和可定制性。它配备了运行 CTF 所需的一切,并且很容易使用 plugins 和主题进行自定义。

GZ::CTF

项目地址:https://github.com/GZTimeWalker/GZCTF
GZ::CTF 是一个基于 ASP.NET Core 的开源 CTF 平台,采用 Docker 或 K8s 作为容器部署后端,提供了可自定义的题目类型、动态容器和动态分值功能。

为什么选择GZ::CTF

对于我个人而言来说,拉取Docker镜像比较方便部署

当然官方也给出了其特色

简介 - GZ::CTF

特性 🛠️
  • 创建高度可自定义的题目

    • 题目类型:静态附件、动态附件、静态容器、动态容器

      • 静态附件:共用附件,任意添加的 flag 均可提交。
      • 动态附件:需要至少满足队伍数量的 flag 和附件,附件及 flag 按照队伍进行分发。
      • 静态容器:共用容器模版,不下发 flag,任意添加的 flag 均可提交。
      • 动态容器:自动生成并通过容器环境变量进行 flag 下发,每个队伍 flag 唯一。
    • 动态分值

      • 分值曲线:

        f(S,r,d,x)=⌊S×[r+(1−r)×exp⁡(1−xd)]⌋f(S,r,d,x)=⌊S×[r+(1−r)×exp(d1−x)]⌋

        其中 SS 为原始分值、 rr 为最低分值比例、 dd 为难度系数、 xx 为提交次数。前三个参数可通过自定义实现绝大部分的动态分值需求。

      • 三血奖励: 平台对一二三血分别奖励 5%、3%、1% 的当前题目分值

    • 比赛进行中可启用、禁用题目,可多次放题

    • 动态 flag 中启用作弊检测,可选的 flag 模版,leet flag 功能

  • 分组队伍得分时间线、分组积分榜

  • 基于 Docker 或 K8s 的动态容器分发、管理、多种端口映射方式

  • 基于 SignalR 的实时比赛通知、比赛事件和 flag 提交监控及日志监控

  • SMTP 邮件验证功能、基于 Google ReCaptchav3 的恶意注册防护

  • 用户封禁、用户三级权限管理

  • 可选的队伍审核、邀请码、注册邮箱限制

  • 平台内 Writeup 收集、查阅、批量下载

  • 可下载导出积分榜、可下载全部提交记录

  • 比赛期间裁判监控、提交和主要事件日志

  • 题目流量 TCP over WebSocket 代理转发、可配置流量捕获

  • 基于 Redis 的集群缓存、基于 PGSQL 的数据库存储后端

  • 支持本地文件系统和 对象存储 (MinIO, S3 等) 作为文件存储后端

  • 支持暗黑模式、多国语言、自定义主题颜色

  • 支持 网站页脚网站图标HTML 描述 的自定义,以提升 SEO

  • 全局配置项自定义、平台标题、备案信息

  • 支持测量和分布式追踪

  • 以及更多……

GZ::CTF平台部署过程

  1. vim appsettings.json

    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
    48
    {
    "AllowedHosts": "*",
    "ConnectionStrings": {
    "Database": "Host=db:5432;Database=gzctf;Username=postgres;Password=<Your POSTGRES_PASSWORD>" //Your POSTGRES_PASSWORD> 数据库密码
    },
    "EmailConfig": {
    "SenderAddress": "",
    "SenderName": "",
    "UserName": "",
    "Password": "",
    "Smtp": {
    "Host": "localhost",
    "Port": 587
    }
    },
    "XorKey": "<Your XOR_KEY>",//用于加密比赛私钥的随机字符串
    "ContainerProvider": {
    "Type": "Docker", // or "Kubernetes"
    "PortMappingType": "Default", // or "PlatformProxy"
    "EnableTrafficCapture": false,
    "PublicEntry": "<Your PUBLIC_ENTRY>", // 外部访问地址,可以是 IP 或域名
    // optional
    "DockerConfig": {
    "SwarmMode": false,
    "Uri": "unix:///var/run/docker.sock"
    }
    },
    "RegistryConfig": {
    "UserName": "",
    "Password": "",
    "ServerAddress": ""
    },
    "CaptchaConfig": {
    "Provider": "None", // or "CloudflareTurnstile" or "GoogleRecaptcha"
    "SiteKey": "<Your SITE_KEY>",
    "SecretKey": "<Your SECRET_KEY>",
    // optional
    "GoogleRecaptcha": {
    "VerifyAPIAddress": "https://www.recaptcha.net/recaptcha/api/siteverify",
    "RecaptchaThreshold": "0.5"
    }
    },
    "ForwardedOptions": {
    "ForwardedHeaders": 7,
    "ForwardLimit": 1,
    "TrustedNetworks": ["192.168.12.0/8"]
    }
    }

    对于appsettings.json的其他配置可以查看官方文档https://gzctf.gzti.me/zh/config/appsettings.html

  2. vim docker-compose.yml

    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
    services:
    gzctf:
    image: registry.cn-shanghai.aliyuncs.com/gztime/gzctf:develop
    restart: always
    environment:
    - "GZCTF_ADMIN_PASSWORD=<Your GZCTF_ADMIN_PASSWORD>"#admin密码设置
    # choose your backend language `en_US` / `zh_CN` / `ja_JP`
    - "LC_ALL=zh_CN.UTF-8"
    ports:
    - "80:8080"#Docker映射端口,可以根据实际情况修改
    volumes:
    - "./data/files:/app/files"
    - "./appsettings.json:/app/appsettings.json:ro"
    # - "./kube-config.yaml:/app/kube-config.yaml:ro" # this is required for k8s deployment
    - "/var/run/docker.sock:/var/run/docker.sock" # this is required for docker deployment
    depends_on:
    - db

    db:
    image: postgres:alpine
    restart: always
    environment:
    - "POSTGRES_PASSWORD=<Your POSTGRES_PASSWORD>"#你的数据库密码和appsettings.json一致
    volumes:
    - "./data/db:/var/lib/postgresql/data"

    在这里我使用了kengwang修改后的GZCTF’s images
    新增功能参考https://github.com/kengwang/GZCTF
    根据个人使用情况选择

配置好后执行

1
docker-compose up -d

等到Docker build successful.

我的CTF平台示范

Acbug::CTF

首页

首页

题目页

题目页

出题页

出题

出题篇

GZCTF支持静态附件,动态附件,静态容器,动态容器。

WEB篇

动态容器

基本目录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
- webs
- qiandao
- html
- index.html
- Dockerfile
- init.sh
- web4
- html
- flag.php
- index.php
- Dockerfile
- init.sh
- docker-compose.yml

对于docker-compose.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
version: "3"
services:
qiandao:
build:
context: ./qiandao # 指定 Dockerfile 所在的构建上下文目录
dockerfile: Dockerfile # 指定 Dockerfile 的名称
image: First/qiandao:latest # 为构建的镜像指定名称和标签
restart: always

web4:
build:
context: ./web4 # 指定 Dockerfile 所在的构建上下文目录
dockerfile: Dockerfile # 指定 Dockerfile 的名称
image: First/web4:latest # 为构建的镜像指定名称和标签
restart: always

#后续·······
对于Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 指定基础镜像
FROM php:7.0-fpm-alpine
# 删除默认的 web 根目录中的所有内容
RUN rm -rf /var/www/html/*
# 创建空的 flag 文件并设置权限
RUN touch /flag && chmod 666 /flag #写入了/下的flag文件
# 将本地的 html 目录复制到容器中
COPY html /var/www/html
# 将初始化脚本复制到容器的 html 目录中
COPY init.sh /var/www/html/init.sh
# 设置权限(此时仍为 root 用户)
RUN chown -R www-data:www-data /var/www/html && chmod -R 755 /var/www/html
# 设置初始化脚本为可执行
RUN chmod +x /var/www/html/init.sh
# 暴露 web 服务器的端口
EXPOSE 80
# 以 root 用户启动脚本
ENTRYPOINT ["/var/www/html/init.sh"]
# 切换到 www-data 用户执行后续命令
USER www-data
对于init.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/sh
sed -i "s/Acbug{flag}/$GZCTF_FLAG/" /var/www/html/index.html #使用平台的动态flag替换

# 将环境变量 GZCTF_FLAG 的值写入 /flag 文件
echo "$GZCTF_FLAG" > /flag

# 清除 GZCTF_FLAG 环境变量
unset GZCTF_FLAG

# 启动 PHP 服务器并替换当前 shell 进程
exec php -S 0.0.0.0:80 -t /var/www/html & #默认暴露80端口

# 等待 PHP 服务器启动
sleep 5

# 删除 init.sh 脚本
rm -f "$0"

# 保持脚本运行
wait

[!WARNING]

flag在/下的flag文件中。如果你直接将flag写入flag.php文件中flag会直接暴露出来

对于一道RCE题目
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
header("Content-Type:text/html;charset=utf-8");
show_source(__FILE__);
include('flag.php');
$username = $_GET['username'];
$password = $_GET['password'];
if($username != $password){
if(md5($username) === md5($password)){
echo 'GET_FLAG:'.$flag;
}else{
echo 'md5校验出错...';
}
}else{
echo '用户名密码不能相等!';
}
?>

flag.php文件配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
// 定义一个函数来安全地读取 /flag 文件的内容
function get_flag_content() {
$file_path = '/flag';

// 检查文件是否存在并可读
if (file_exists($file_path) && is_readable($file_path)) {
// 读取文件内容
return file_get_contents($file_path);
} else {
return 'Flag file not found or not readable.';
}
}

// 获取 flag 内容
$flag = get_flag_content();
?>

部署题目

webs目录下执行

1
docker-compose up -d

等待Docker构建成功

构建成功后在GZCTF平台拉取镜像即可

例如 创建Web动态容器

容器镜像填First/qiandao:latest (在docker-compose.yml命名过)

qiandao

PWN篇

CTF-Xined

  1. 获取CTF-Xined项目

    1
    2
    #git获取
    git clone https://github.com/CTF-Archives/ctf-docker-template

动态容器

部署题目
  1. 使用GCC制作pwn题目

  2. 制作好的pwn题目拖入选定的ubuntu环境

    image-20241129183647867

  3. 将pwn文件命名为attachment(偷懒bushi 或者可以去修改./config/ctf.xinetd文件./dockerfile文件./service/docker-entrypoint.sh文件中的attachment为你制作的pwn题目名字 )

  4. 在ubuntu版本目录下,制作题目镜像

    1
    docker build -t nc .    #注意有个点.
  5. 查看题目镜像

    1
    docker images  
  6. 容器镜像填nc

    端口为9999

    nc