환경 구축

나만의 CTF사이트 만들기(feat. whale plugin)

zkvlkat /ᐠ。ꞈ。ᐟ\ 2024. 8. 25. 13:40

프로그래밍에서는 Online Judge가 있었다면, 보안에서는 CTFd라는 framework가 존재한다.

https://github.com/CTFd/CTFd

 

GitHub - CTFd/CTFd: CTFs as you need them

CTFs as you need them. Contribute to CTFd/CTFd development by creating an account on GitHub.

github.com

 

Online Judge와 마찬가지로 깃허브에서 git clone으로 파일을 다운로드 받은 뒤, 설치하면 된다.

플러그인 설치 없이 단순 CTFd framework만 사용하려면 sudo docker-compose up -d만 입력해도 구축이 끝난다.

 


0. 시작에 앞서

하지만 필자는 CTFd에서 사용하는 플러그인 중 Whale 이라는 플러그인 구축을 아직까지 해결못하고 있으며, 이를 따라하려는 경우 굉장히 복잡해진다..

GPT에게 도움받으면서도 Whale plugin만 구축 못하고 있고,

이 글을 본 당신도 try해보고 같이 고민해본다면 여러가지 시도끝에 성공할 수 있지 않을까.. 한다.

CTF에 참여해보면 종종 이렇게 launch 버튼을 눌러서 개별 docker container를 구축하여 사용자에게 페이지를 제공해주는 경우가 있다.

CTFd를 이용하여 플랫폼을 구축해본다면 이건 반드시 해보고 싶었었다.

그래서 검색을 해보니 이를 사용할 수 있도록 해주는 Whale이라는 대륙의 따거분들이 만들어놓은 플러그인이 존재했다.

 

하지만 지금 48시간 넘게 삽질을 해봤지만, 약간의 진전만 있을 뿐, 아직까지 성공하지 못했고 시도중이다.

대륙의 따거분들이 만들어서 그런가, 검색해봐도 setup, guide에 대한 글은 98%가 중국사이트에 존재하며, 스페인 사람이 작성한 게시글을 딱 하나 발견했다. 한글로된 setup 글은 아무것도 없다.

https://blog.toadsec.io/2023/07/24/ctfd-whale.html << 스페인 사람이 작성한 글

 

혹시라도 이 글을 보고 Whale plugin 구축에 관심이 생긴다면, 구축해서 한글 가이드좀 만들어주셨으면 좋겠습니다.

아니면 Whale plugin말고 다른 방법을 알고 계신분이 있다면.. 가이드 부탁드립니다..

 

Reference

1.

https://gitee.com/chise123/CTFd-Whale#https://gitee.com/link?target=https%3A%2F%2Fblog.csdn.net%2Ffjh1997%2Farticle%2Fdetails%2F100850756

2.

https://err0r.top/article/CTFD/

3. 

https://www.anyiblog.top/2023/04/13/20230413/

4.

https://blog.csdn.net/zxj993853579/article/details/129895497

5.

https://giters.com/sqxssss/CTFd-Whale

6.

https://blog.51cto.com/u_14499/9195686

7. 그나마 자세히 나와있다고 생각하는 글

https://blog.hz2016.com/2022/02/%E3%80%90ctfd%E3%80%91%E9%9D%B6%E5%9C%BA%E5%AE%89%E8%A3%85%E4%B8%8E%E9%85%8D%E7%BD%AE/

8. 그나마 자세히 나와있다고 생각하는 글

https://blog.csdn.net/zwy3327078581/article/details/134516634

9. 그나마 자세히 나와있다고 생각하는 글

https://type.dayiyi.top/index.php/archives/92/

10. 

https://mcsog.top/posts/65baeb4c.html

11.  그나마 제일 자세하다고 생각하는 글 

https://blog.csdn.net/fjh1997/article/details/100850756

 

 


 

1. 삽질 내용

시작에 앞서 반드시 숙지해야할 점이 있다.

위 Reference 링크에서 글을 어느정도 숙지하면 확인할 수 있는 점이, 사용자마다 시기에 따라 빌드한 환경, 버전이 다르고 그에 맞는 버전들을 따라가는 것도 중요하다.

그래서 글 여러개에서 이것저것 다운로드 받으면 완전히 꼬여버리는 수가 있으니, 참조할 메인 게시글을 하나 결정하고, 다른 게시글은 방법만 참고하면 좋을 것 같다.


 

Whale plugin에 대한 github repository에서 구축 가이드를 알려주고 있긴 하다 하지만 그대로 따라해도 뭔가 하나씩 안맞고 에러가 난다.

https://github.com/frankli0324/ctfd-whale/

 

GitHub - frankli0324/ctfd-whale: CTFd plugin that allows your users to have exclusive environments for each challenge, with dyna

CTFd plugin that allows your users to have exclusive environments for each challenge, with dynamic flags. - frankli0324/ctfd-whale

github.com

아래 내용은 GPT와 함께 내가 구축하면서 사용한 Ubuntu에서 구축과정에서 사용한 명령어 및 스크린샷이다.

아래 명령어 그대로 입력하기 전에 우선 한번 쭉 읽고 나서 어떻게 할지 계획한 뒤에 실행해보길 추천한다.

 

 

1. Docker 설치 스크립트 다운로드 및 실행

curl: 웹에서 파일을 다운로드하는 도구입니다.
-fsSL: 옵션으로, -f는 실패 시 조용히, -s는 진행 상황을 숨기고, -L은 리다이렉션을 따르도록 합니다.
-o get-docker.sh: 다운로드한 파일을 get-docker.sh로 저장합니다.
sh get-docker.sh: 다운로드한 스크립트를 실행하여 Docker를 설치합니다.

#Ubuntu docker 설치
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh

 

2. Docker Swarm 초기화

 Docker Swarm 모드로 클러스터를 초기화합니다. 이는 여러 Docker 엔진을 클러스터로 묶어 관리할 수 있게 해줍니다. 

docker swarm init

 

3. 노드에 레이블 추가

docker node update: Swarm의 특정 노드를 업데이트합니다.
--label-add: 노드에 레이블을 추가합니다. 여기서는 모든 노드에 name=linux-1 레이블을 추가합니다.
$(docker node ls -q): 현재 Swarm에 있는 모든 노드의 ID를 가져옵니다.

docker node update --label-add='name=linux-1' $(docker node ls -q)

 

4. CTFD 소스 코드와 플러그인 클론

git clone: Git을 사용하여 원격 저장소에서 코드를 복제합니다.
--depth=1: 최신 버전만 복제하여 용량을 줄입니다.
첫 번째 명령은 CTFD 프레임워크를 클론하고, 두 번째 명령은 ctfd-whale 플러그인을 설치합니다.

git clone https://github.com/CTFd/CTFd --depth=1
git clone https://github.com/frankli0324/ctfd-whale CTFd/CTFd/plugins/ctfd-whale --depth=1

 

5. Docker Compose 파일 다운로드

curl을 사용하여 docker-compose.yml 파일을 다운로드하고, CTFD 폴더에 저장합니다. 이 파일은 CTFD 서비스를 구성하는데 사용됩니다.

curl -fsSL https://cdn.jsdelivr.net/gh/frankli0324/ctfd-whale/docker-compose.example.yml -o CTFd/docker-compose.yml

 

6. 필요한 패키지 설치

Python의 패키지 관리자인 pip를 사용하여 docker-compose 패키지를 설치합니다. Docker Compose는 Docker 컨테이너를 정의하고 실행하는 tool입니다.

pip3 install docker-compose

 

7. Docker Compose로 CTFD 시작

docker-compose up -d: 지정한 docker-compose.yml 파일을 사용하여 컨테이너를 백그라운드에서 실행합니다. -d는 분리 모드로 실행하라는 의미입니다.

docker-compose -f CTFd/docker-compose.yml up -d

 

8. Docker Compose 정지 및 삭제

docker-compose.yml과 같은 파일을 수정한 뒤 적용하려면 컨테이너를 다시 compose해줘야 적용된다.

docker-compose down

 

기본적인 CTFd 구축 명령어만 이정도이다.

 

 

이제 구축한 서버ip로 들어가면 Admin Panel 페이지에서 Whale plugin 탭이 존재하며 아래와 같이 Unable to access frpc admin api라는 문구가 발생한다.

우선 결론부터 말하면 기본적으로 저 Unable to access frpc admin api를 없애야 한다.

frp라는 것을 이용하여 frps (server), frpc(clinet) 사이에서 중계를 하는 중계기 서버와 같은 용도로 사용한다.

 

- Whale 페이지의 Docker, Router 탭에서 내용을 입력해주고, frpc.ini, frps.ini를 설정해주면 사라진다.

- 또는 docker-compose.yml에 frps, frpc에 대한 설정을 전부 마친 뒤, compose-up 하고 Whale 페이지에 접속하면 저 문구가 사라져 있을 것으로 예상된다.

 

 

(Ubuntu 22.04)기준 frps, frpc 세팅

frp에서 frps와 frpc를 세팅하는 글이 중국게시글마다 전부 다르게 나온다.

어떤 글에서는 frp를 /etc/frp에서 세팅하고 /opt/frp에서 세팅하기도 하는데, 여러 시행착오 결과, compose up 빌드한 뒤, Admin Panel - Whale 페이지에서 설정해도 이게 docker-compose up에서 /opt/frp/frpc.ini가 설정되게 되어있다면 브라우저에서 작성해서 Submit하더라도 /opt/frp/frpc.ini, frps.ini에 적용되는 것으로 보인다.

https://type.dayiyi.top/index.php/archives/92/

 

ctfd-whale 实现动态flag - dayi的小键盘

 

type.dayiyi.top

위 사이트를  참고하여 frpcadmin을 생성하여 frpcadmin, frpc_1로 통신하는 식으로 연결했다.

하지만 아직 네트워크 구성이 어떻게 되는지 완벽하게 이해하진 못했다.

 

sudo apt-get install wget git wget https://github.com/fatedier/frp/releases/download/v0.44.0/frp_0.44.0_linux_amd64.tar.gz

#압축해제
tar -zxvf frp_0.44.0_linux_amd64.tar.gz mkdir /opt/frp mv frp_0.44.0_linux_amd64/* /opt/frp/

#구성파일 수정
vim /opt/frp/frps.ini

 

- frps.ini

[common]
bind_port = 7000
bind_addr = 0.0.0.0
token=your_token #토큰은 password같은 용도로 자신만 알면 되는 용도인 것으로 보인다
vhost_http_port = 82 #8080으로 설정하는 사람도 있던데 뭔지 모르겠다. 나는 80포트 사용중이라 82로 설정
subdomain_host = copyc4t.com

 

- 시스템 서비스 설정

vim /usr/lib/systemd/system/frp.service


[Unit]
Description=Frp Server Service
After=network.target

[Service]
Type=simple
User=root
Restart=on-failure
RestartSec=5s
ExecStart=/opt/frp/frps -c /opt/frp/frps.ini

[Install]
WantedBy=multi-user.target

 

- 서비스 시작


systemctl start frp
systemctl restart frp
systemctl enable frp
systemctl status frp

 

- frpc 설치

sudo docker network create ctfd_frp-containers #FRP 네트워크 생성

sudo docker run -d -v /opt/frp/frpc.ini:/etc/frp/frpc.ini --name frpc_1 --network="ctfd_frp-containers" --restart=always "glzjin/frp"

sudo docker network create frpcadmin #ctfd <--> frpcadmin 를 위해서 생성하는 frpcadmin
sudo docker network connect frpcadmin frpc_1 #연결 명령어

 

- frpc.ini 구성 파일을 수정

/opt/frp/frpc.ini이 파일은 docker 컨테이너에 해당하는 frpc 구성 파일입니다.

[common]
token = your_token #토큰은 password같은 용도로 자신만 알면 되는 용도인 것으로 보인다

server_addr = 172.17.0.1  # ifconfig으로 docker0의 ip주소를 가져온다.
server_port = 7000
admin_addr = 172.27.0.3 #frpcadmin ip를 가져온다. 명령어는 sudo docker network inspect frpcadmin
admin_port = 7400 log_file = ./frpc.log ~

 

위 블로그의 게시글에서는 frpc, frps 세팅을 마친 뒤에 git clone으로 CTFd를 세팅한다.

git clone을 다 마치고 나서는 docker-compose.yml 파일을 손본다.

version: '2'

services:
  ctfd:
    build: .
    user: root
    restart: always
    ports:
      - "8000:8000"
    environment:
      - UPLOAD_FOLDER=/var/uploads
      - DATABASE_URL=mysql+pymysql://ctfd:ctfd@db/ctfd
      - REDIS_URL=redis://cache:6379
      - WORKERS=1 - LOG_FOLDER=/var/log/CTFd
      - ACCESS_LOG=- - ERROR_LOG=-
      - REVERSE_PROXY=true volumes:
      - .data/CTFd/logs:/var/log/CTFd
      - .data/CTFd/uploads:/var/uploads
      - .:/opt/CTFd:ro
      - /var/run/docker.sock:/var/run/docker.sock #이 부분만 별도로 추가하고, 기본적으로는 공식 코드를 그대로 사용하면 된다.
    depends_on:
      - db
    networks:
        default:
        internal:
  nginx:
    image: nginx:stable
    restart: always
    volumes:
      - ./conf/nginx/http.conf:/etc/nginx/nginx.conf
    ports:
      - 80:80
    depends_on:
      - ctfd

  db:
      image: mariadb:10.4.12
      restart: always
      environment:
        - MYSQL_ROOT_PASSWORD=ctfd
        - MYSQL_USER=ctfd
        - MYSQL_PASSWORD=ctfd
        - MYSQL_DATABASE=ctfd volumes:
        - .data/mysql:/var/lib/mysql networks:
            internal:
      # This command is required to set important mariadb defaults
      command: [mysqld, --character-set-server=utf8mb4, --collation-server=utf8mb4_unicode_ci, --wait_timeout=28800, --log-warnings=0]
  cache:
    image: redis:4
    restart: always
    volumes:
    - .data/redis:/data
    networks:
      internal:

networks:
    default:
        external:
          name: frpcadmin
    internal:
      internal: true

 

 

명칭 입력 내용 용도(설명)
[Docker] API URL to connect unix://var/run/docker.sock docker file
[Router] Frp API URL http://172.27.0.3:7400 FRP의 ip(frpcadmin, $ docker network ls)
[Router] Frp Http Domain Suffix copyc4t.com 도메인 이름 접미사(본인 소유 도메인 있는 경우)
[Router] Frp External Http Port 82 for http redirect
[Router] For direct redirect 192.xx.xx.xx 혹은 local확인하려면 127.0.0.1??
[Router] Frp Direct Minimum Port 10000 For direct redirect
[Router] Frp Direct Maximum Port 10100 일반적으로 docker-compose에 기본세팅 되어있다.
[Docker] Auto Connect Containers ctfd_frpc_1 (혹은 frpc_1, $ docker network ls, docker ps로 확인)
[Docker] Auto Connect Network ctfd_frp-containers  
[Docker] Multi-Container Network Subnet 172.26.0.0/16 sudo docker network inspect <id>로 ctfd_frp-containers의 Subnet 그대로 복사

 

결과

unable to access는 해결했는데.. 여전히 인스턴스 생성이 안된다.


Whale 페이지에 내가 기입한 내용

<1.Docker 탭>

Auto Connect Network
The network connected for single-containers. It is usually the same network as the frpc is in.
ctfd_frp-containers //<< frpc_1 연결되어있어서?


Dns Setting
Decide which dns will be used in container network.
0.0.0.0 << 모르겠는데 일단 됨. /opt/frp/fprs.ini에 bind_addr로 적용됨.

[[Grouped Containers
Designed for multi-container challenges]]
Auto Connect Containers
Decide which container will be connected to multi-container-network automatically. Separated by commas.
frpc_1  << docker create network로 ctfd_frp-containers 생성하고, 거기에 연결한 건데, 여기에 sudo docker inspect frpc_1로 
확인했을때 내 기준으로 들어있는 네트워크는 ctfd_frp-containers, frpcadmin였다.


Multi-Container Network Subnet
Subnet which will be used by auto created networks for multi-container challenges.
172.26.0.0/16 <<  bash에서 sudo docker network inspect ctfd_frp-containers입력해서 확인하여 subnet 입력한거
=======================================================
<2. Router 탭>
API URL
Frp API to connect to
http://172.27.0.3:7400 << /opt/frp/frpc.ini 파일의 admin_addr 값 넣기



Http Domain Suffix
Will be appended to the hash of a container
내가 가지고 있는 도메인 ex.(copyc4t.com) << frps.ini의 subdomain_host로 넣긴 했는데 이거 왜됨?

External Http Port
Keep in sync with frps:vhost_http_port
82 << frps의 vhost_http_port로 넣긴 함.


Direct IP Address
For direct redirect
ex. 3.10.10.1 << 이건 challenge에서 사용자가 컨테이너 생성할 때, 사용자에게 보여줄 http 주소의 ip인듯
혹은 ubuntu ip인 192.xx.xxx.xx?

[[[Direct Minimum Port
For direct redirect (pwn challenges)]]]
10000
[[[Direct Maximum Port
For direct redirect (pwn challenges)]]]
10100

Frpc config template
Frp config template, only need common section! << 아래 내용 
submit 하면 자동으로 frps를 입력해서 반환해주는 것 같음

[common]
server_addr = 172.17.0.1
server_port = 7000
token=your_token
admin_addr = 172.27.0.2
admin_port = 7400

추가 주제: Docker Swarm 개요

  • Docker Swarm은 Docker의 네이티브 클러스터링 도구로, 여러 Docker 호스트를 하나의 논리적인 호스트로 묶어 배포할 수 있게 해줍니다.
  • 주요 기능:
    • 서비스 관리: 여러 컨테이너를 쉽게 배포하고 관리할 수 있습니다.
    • 로드 밸런싱: 요청을 여러 컨테이너에 분산시켜 성능을 최적화 합니다.
    • 고가용성: 하나의 노드가 다운되더라도 자동으로 다른 노드에서 컨테이너가 실행됩니다.
    • 스케일링: 필요에 따라 컨테이너의 수를 수동 혹은 자동으로 조절할 수 있습니다.

이 코드와 Docker Swarm을 함께 사용하면, 유연하고 확장 가능한 CTF 플랫폼 환경을 만들 수 있습니다. 추가적인 질문이 있다면 언제든지 물어봐주세요! 😊


 

구축하면서 발생한 에러들 일부 모음

WARN[0000] /home/username/CTFd/CTFd/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion 
[+] Building 0.0s (1/1) FINISHED                                                                                                                                                    docker:default
 => [ctfd internal] load build definition from Dockerfile                                                                                                                                     0.0s
 => => transferring dockerfile: 2B                                                                                                                                                            0.0s
failed to solve: failed to read dockerfile: open Dockerfile: no such file or directory

 

이 에러는 docker-compose.yml에 version : "2"처럼 입력되어 있는게 존재하면 지워버리면 일단 해결된다.


그 밖에 사용해 본 명령어들

sudo docker --version
#결과 -> Docker version 27.1.2, build d01f264
sudo docker-compose --version
#결과 -> Docker Compose version v2.29.2

 

플러그인 별도로 다운받아서 집어넣으려고 한다면

git clone https://github.com/frankli0324/ctfd-whale.git

 

ERROR: This node is not a swarm manager. Use "docker swarm init" or "docker swarm join" to connect this node to swarm and try again.

이 명령어는 현재 노드에서 Swarm 모드를 비활성화합니다. 이후, 위의 Docker Compose 명령어를 다시 실행해 보세요.

sudo docker swarm leave --force

 

 

docker 컨테이너 종료

docker stop abc123 def456 ghi789

#전부 종료하려면
docker stop $(docker ps -q)

 

docker 컨테이너 삭제

docker rm abc123

#전부 삭제하려면
docker rm $(docker ps -aq)

 

docker image 삭제

docker rmi <image-name> # 특정 이미지 삭제

모든 이미지를 한 번에 삭제하려면 아래 명령어를 사용할 수 있습니다:
docker rmi $(docker images -q)

 

 

docker 컨테이너 네트워크, ip 확인

docker network connect frpcadmin ctfd-frpc-1

docker network ls

docker network inspect frpcadmin


service frp restart #frp 재시작
systemctl status docker

 

docker container 재시작

docker restart $(docker ps -aq)

 

docker container 에러 확인을 위해 log 확인

docker service logs (도커이름 혹은 ID)

 

Ubuntu 파일이 존재하는 디렉토리 삭제

rm -rf 폴더명

Docker container, image를 모두 제거한 뒤, 다시 빌드하는데 순식간에 빌드되고, create 시점이 지금이 아닌 이미 10분 이상 지난 시점일 때

Docker에서 사용하지 않는 데이터(사용되지 않는 이미지, 컨테이너, 볼륨 등)를 정리하려면 아래 명령어를 사용하세요:

-a: 모든 사용하지 않는 이미지도 삭제합니다.
--volumes: 사용하지 않는 모든 볼륨도 삭제합니다.

sudo docker system prune -a --volumes

docker builder prune #캐시된 데이터 삭제

볼륨까지 삭제해주면 처음부터 빌드할 수 있다.


여담

위에 Reference 글 중에, Docker 24.0.7 버전처럼 특정버전으로 해야 에러가 줄어든다는 글이 있다.

Docker 24.0.7은 Ubuntu 22버전에서 다운로드 가능하며 Ubuntu 24버전에서는 Docker 26,27 버전만 다운로드 가능하니 이 점을 참고한다.