올해 저는 교내 보안동아리 K.Knock의 부회장으로 활동 중입니다.
저희 동아리의 연례행사인 웹 사이트 공방전이 8월에 이루어졌습니다.
웹 사이트 공방전은 준회원들이 직접 개발한 웹 사이트를 서로 여러 공격 기법을 활용하여 정해진 시간 동안 모의 해킹하고 쉬는 시간 동안 여러 보호 기법을 적용하여 공격을 방어하는 구조로 이루어집니다.
올해 대회는 08-24T 12:00 ~ 08-26T 15:00까지 51시간 진행되었습니다.
운영 방안을 두고 고민을 많이했습니다.
우선 대회 시작 전 준비 얘기부터 시작해보겠습니다.
예상 참여인원이 작년보다 많아서 이 트래픽에 대해 먼저 고민하였습니다.
서버 관리담당 인원과 1차 회의 결과 AWS Free-tier를 활용해서 팀마다 서버를 돌리기로 하였습니다.
팀 내 유저마다 격리를 위해 docker를 활용하기로 해서 제가 기초적인 환경이 설정된 이미지를 만들기로 하였습니다.
제가 처음 생각한 이상적인 구조는 httpd 이미지나 php 이미지를 베이스로 해서 여러 가지 프로그램 설치 구문을 추가하여 이미지로 만들고 해당 이미지와 mysql 이미지를 활용해서 docker-compose로 손쉽게 구성하도록 하는 것이었습니다.
근데 여기서 첫 번째 고민이 생깁니다.
docker container에 대한 운영주체가 준회원이라는 점이었는데 뛰어난 실력을 가진 분들도 많지만 대부분 새로운 환경 적응을 어려워하기에 처음 생각한 구조를 바꿀 필요가 있었습니다.
그리하여 가장 많이 사용해봤을 우분투 이미지를 기반으로 해당 이미지 내에 Apache2 + Php + Mysql8을 구축하는 방향으로 생각을 바꾸었습니다.
초기 환경 테스트를 위해 참여 인원들에게 환경 구축을 해보도록 했는데 문제가 생깁니다.
mysql 연결이 계속 에러가 나는 겁니다.
free-tier에 container 여러 개를 돌리다 보니 문제가 생긴 것 같은데 제 테스트 환경에선 문제가 없었어서 더 난감했습니다 ㅠㅠ...
고민 끝에 낸 해결방안은 supervisor 데몬을 활용하여 mysqld가 꺼져도 다시 켜질 수 있는 구조를 구축하는 것이었습니다.
[program:apache2]
command=/usr/sbin/apache2ctl -D FOREGROUD
autostart=true
autorestart=true
[program:mysql]
command=/usr/bin/pidproxy /var/run/mysqld/mysqld.pid /usr/bin/mysqld_safe
autostart=true
autorestart=true
[program:sshd]
command=/usr/sbin/sshd
autostart=true
autorestart=true
superviosr는 다음과 같이 설정하였습니다.
autostart는 초기에 자동으로 키는 것이고 autorestart는 꺼졌을 때 자동으로 재시작을 활성화하는 옵션입니다.
FROM ubuntu:20.04
ARG DEBIAN_FRONTEND=noninteractive
ENV TZ Asia/Seoul
RUN sed -i 's/archive.ubuntu.com/mirror.kakao.com/g' /etc/apt/sources.list \
&& apt-get update
# apache2 + basic tools
RUN apt-get install -y apache2 wget vim net-tools tmux curl lrzsz \
cron python3-distutils tzdata rdate openssh-server supervisor
# SET TIMEZONE
RUN ln -fs /usr/share/zoneinfo/Asia/Seoul /etc/localtime
# php7.4
RUN apt-get install -y php7.4
RUN apt-get install -y php7.4-bz2 php7.4-cgi php7.4-cli php7.4-common \
php7.4-curl php7.4-dev php7.4-fpm php7.4-gd php7.4-json php7.4-ldap \
php7.4-mbstring php7.4-mysql php7.4-odbc php7.4-opcache php7.4-readline \
php7.4-snmp php7.4-soap php7.4-tidy php7.4-xml php7.4-xmlrpc php7.4-xsl php7.4-zip
# apache-php
RUN a2enmod proxy_fcgi setenvif
RUN a2enconf php7.4-fpm
# mysql8 - not yet
RUN apt-get -q -y install mysql-server-8.0
RUN wget -q https://gist.githubusercontent.com/l0vey0u/9c55d839c0a8774722569b8e5e07e263/raw/9cfcf119fcabb9e510bf83745bb8e0a6e1660658/mysql_secure.sh -O mysql_secure.sh
RUN chmod +x mysql_secure.sh && service mysql restart && ./mysql_secure.sh "toor" \
&& mysql -uroot -ptoor -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'toor'; flush privileges;"
# install pip
RUN curl -s https://bootstrap.pypa.io/get-pip.py -o get-pip.py \
&& ln -s /usr/bin/python3 /usr/bin/python \
&& python get-pip.py \
&& pip install pymysql
# ssh
RUN sed 's/#PermitRootLogin.*/PermitRootLogin yes/g' -i /etc/ssh/sshd_config && mkdir /var/run/sshd
# SET Supervisor
WORKDIR /etc/supervisor/conf.d
RUN rm -f supervisord.conf \
&& wget -q https://gist.githubusercontent.com/l0vey0u/986f18ac8c868078721660538654390a/raw/307a8d7e1ea41db70ccb260767e0b4fec3969830/supervisord.conf_v2 -O custom.conf
# Setting docker-entrypoint.sh
WORKDIR /
RUN wget -q https://gist.githubusercontent.com/l0vey0u/d11a02fdff3756f736e60d817ea80385/raw/547b352060386cf69d988eaf00fa24b33ec830bf/docker-entrypoint.sh_v2 -O docker-entrypoint.sh \
&& chmod +x docker-entrypoint.sh \
&& mv docker-entrypoint.sh /usr/local/bin/
# Time Sync
# cron m h d ~ ~
RUN touch /var/log/cron_error.log \
&& echo "0 */3 * * * root rdate -s time.bora.net >> /var/log/cron_error.log 2>&1" >> /etc/crontab
ENTRYPOINT ["/bin/sh", "-c" , "/etc/init.d/cron start && docker-entrypoint.sh"]
최종적으로 다음과 같이 구축하였습니다.
맨 마지막 cron 부분은 공방전이 3시간마다 flag를 변경해주는 구조로 이루어져 시간 동기화가 필요했습니다.
이렇게 열심히 해보았지만 성능 이슈가 해결안 돼서 결국 2차 회의를 거쳐 준회원 개인이 aws 서버를 파서 진행하는 것으로 바뀝니다.
다음부터는 공방전 플랫폼을 미리 만들어두는 것이 좋을 것 같습니다.
물론 준회원의 웹사이트 개발 진도가 빠르지 않은 것도 있었지만 공방전 플랫폼 구축 문제가 겹쳐서 예정보다 공방전이 지연됐고 이 문제로 일정이 맞지 않아 제대로 참여하지 못한 분이 있는 것이 안타까웠습니다.
대회 동안에 큰 이슈는 없었습니다.
사소한 이슈지만 새벽에 개인 pc를 서버로 사용하고 있던 분이 pc를 꺼버리셔서 간밤에 접속이 안 됐었습니다.
정말 예상된 상황이지만 예상을 벗어나진 않았습니다 ㅋㅋ
공방전에서 flag는 서버 내부와 DB내에 저장되는데 이는 RCE ( or lfi )와 SQLi를 유도한 구조입니다.
초반에는 적용된 보호 기법이 허술하여 all solve가 많이 나왔는데
대회 동안 공격 로그를 확인하면서 보호기법이 계속 발전해서 후반부에는 몇몇 준회원은 rce vector가 나오지 않았습니다.
제일 흥미로웠던 부분은 Maria DB를 사용했던 분이 있었는데
select load_file()
해당 구문으로 파일을 불러올 수 있었다는 점입니다.
최근엔 보통 지정된 디렉터리만 허용되게 설정돼있는데 이전 버전으로 설치한 것인지 아니면 개인의 필요에 의해서 설정을 바꾼 것인지는 확인해보지 못했습니다.
작년 기수 + 올해 기수로 팀이 이루어지는데 이는 선 후배 간에 소통을 유도하면서 준회원들에게 조금 더 의욕을 주고자 함입니다.
총 3팀이었는데 한 팀은 이 소통이 매우 잘 이루어져서 뿌듯했지만 두 팀은 선배 기수가 너무 바쁜 나머지 이루어지지 못한 게 아쉬웠습니다.
항상 느끼는 점이지만 공방전과 CTF에서는 생각해보지 못한 고수가 발견되는 것 같습니다.
한 팀을 캐리 하는 준회원 분이 있었는데 혼자서 작년 기수보다도 많은 점수를 획득하여 결국 MVP로 선정되었습니다.
공방전 이전, 이후로 바빠서 정신이 없어서 좀 지나고 나서 쓰다 보니 기억이 가물가물하네요
더 생각나는 스토리가 있으면 추가해보겠습니다.
보안 동아리에서 공방전은 꽤 의미 있는 행사인 것 같습니다.
꼭 웹이 아니여도 어떤 분야에서도 구축하고, 공격해보고 로그를 확인하고 보호 조치하는 법은 중요하기에 적극 추천드립니다.
'server' 카테고리의 다른 글
2021 인코그니토 CTF 운영 후기 (공개용) (0) | 2021.08.29 |
---|---|
2021 인코그니토 CTF 운영 후기 (비공개용) (0) | 2021.08.29 |
letsencrypt 인증서 wildcard 적용 후 보안 경고 피하는 법 (0) | 2021.08.19 |