PPP팀의 suggestions-for-running-a-ctf 문서를 읽고 정리한 글
CTF에는 어떤 문제들이 출제되고(되어야 하고) 어떻게 운영되는지 담은 글 같아서 정리해둔다.
Introduction
이 문서는 CTF 대회 운영과 관련된 설계 및 기술적인 세부 사항을 설명한다.
CTF 커뮤니티들이 가지고 있는 의견을 요약하고, 문제를 설계할 때 피해야 할 몇 가지 구체적인 위험 사항들을 나열하고자한다.
General Design
CTF를 많이 참가하여 타겟 고객(참가자)를 파악하고 내려야 할 몇 가지 결정들에 익숙해지는 것이 좋다.
CTF를 준비하고 실행하는 것은 항상 예상보다 더 많은 시간과 노력이 필요하므로 작업할 시간을 충분히 가지도록 한다. :-)
Timing
다른 보안 행사와의 일정 충돌을 피하기 위해, ctftime.org같은 것을 주로 사용해보자. 미리 계획하고 이벤트를 일찍 발표해서 다른 사람들이 계획할 시간을 갖도록 한다.
또한, 과거에 열린 큰 규모의 대회 일정을 찾아볼 수 있다. 매년 같은 날짜에 열리는 경향이 있으며(연례 컨퍼런스와 관련됨) 이를 통해 CTF에 많은 인원을 수용하기 위해서 일정을 변경할 수 있다.
주중에는 CTF 일정을 잡지 않도록 한다. 이는 학생과 직업을 가진 사람들에게 패널티가 된다.
경쟁을 24시간 또는 48시간 열어두어야 한다. 이렇게 하면 전 세계 팀이 동일한 ‘일광’ 시간을 투자할 수 있다.
그러나 일반적으로 2일 이상 열어두는 것은 좋지 않다. 좋은 문제로는 그렇게 많은 시간을 채울 수 없거나, 할수 있다 하더라도 참가자들이 금방 지치게 된다.
Flag Format
플래그 형식이 사소한 것처럼 보일 수 있으나, 성가신 플래그 형식은 그럭저럭 괜찮은 CTF 경험을 짜증나는 경험으로 바꿀 수 있다.
가능한 한, 플래그를 참가자들이 해결하면서 발견하는 간단한 ASCII 문자열로 만들자. 그리고 다음과 같은 복잡한 형식으로는 만들지 않도록 한다.
<md5(대상 이름)><이벤트 날짜><함수 주소>
이러한 형식은 실수로 충분히 명시되지 않기 쉽다. 이름은 대문자로? 어떻게 표기하는가? 날짜에는 슬래시? 대시? 점으로 구분하는가? 시간도 포함인가? 어떤 시간대인가? 정확도는 얼마나 되고 초, 밀리초, 마이크로초인가? 함수 주소는 16진수인가? 8자리 또는 16자리 0으로 채워진 주소인가? 0x로시작하나 아니면 시작하지 않나?
또한, 복합 플래그 형식은 참가자들이 올바른 것을 찾았는 지 알기 어렵게 만든다. 새로운 추축이 있을 때마다, 여러 순열을 시도해야 하며 형식을 가지고 놀면서 가까운 지 궁금해하는 것은 보람이 없다.
(복잡한 형식을 절대적으로 필요하는 좋은 문제가 있을 수 있다. 조심스럽게 진행하고 모호함을 주의하도록 한다.)
참가자들에게 해시로 만들어 플래그로 보내도록 요청하지 않도록 한다. 해시에 정확히 무엇이 들어가야 하는지 명시하기 어렵고, 대소문자나 new line 문자에서 사소한 오류로 인해 잘못된 결과가 나올 수 있다.
해시 자체가 원래 플래그보다 추축하기 어렵지도 않다; 직접 제출하는게 낫다.
주최자 입장에서 힌트 필요 여부나 참가자들이 예상치 못한 장애물에 부딪혔는지 알기 위해 플래그 제출 로그를 보는 것도 중요하다. MD5 해시만 본다면 무슨 일이 일어나고 있는 지 알 수 없다.
플래그는 항상 제출할 플래그처럼 명확하게 보여야 한다. 플래그가 “축하합니다. 이겼습니다!” 또는 “1234"라면, 일부 참가자들은 문제를 해결했다는걸 깨닫지 못하고 계속 시간을 낭비할 수도 있다.
주최자들은 `MyCTF{663d63e8c755f1b4}` 또는 `funny_1337speaK_pHras3` 같은 일반적인 형식을 모든 대회의 플래그에 사용하는 것이 좋다.(일부 플래그 앞에 “flag is:” 를 포함하는 것도 도움이 된다.)
브루트 포스로 풀수 있는 플래그를 피해야 한다. 예를 들어 플래그가 도시 이름이라면, 일부 플레이어는 수천 개의 추측을 제출할 수 있다.
모든 키 제출 앞에 CAPCHA를 두지는 말자 이는 불필요하고 플레이어한테 성가신 일이다.
플래그 검사는 최대한 허용적이어야 한다. 대소문자를 구분하지 않고 합리적으로 유연해야 한다.
좋은 기능 중 하나는, “CTF{663d63e8c755f1b4}”, “663D63E8C755F1B4”, “flag is:663D63E8C755F1B4” 같은 것을 모두 받아들이는 것이다.
또 다른 좋은 기능은 웹 폼에 복사하여 붙여넣을 때 쉽게 추가될 수 있는 공백 문자를 플래그 제출에서 자르거나 삭제하는 것이다. -> .strip()
Mechanics
CTF는 재미를 위한 게임이지만, 보안 기술 연습이기도 하다. 도전 과제를 둘러싼 게임플레이 매커니즘은 재미와 흥미를 더할 수 있지만, 항상 보안 기술을 발휘하는 것은 아니다.
최고의 CTF 챌린지는 재미와 주제에 관한 것이다. 팀이 매커니즘에 대해 전략을 세우는 것은 나쁘지 않지만, CTF의 핵심은 항상 보안 문제이다.
따라서, 아무리 복잡하더라도 CTF 채점 시스템은 최고의 보안 기술을 보여주는 팀에 큰 보상을 제공해야 한다. 게임 매커니즘에 열심히 생각하고 그것이 악용되거나, 핵심 과제에서 너무 많은 방해를 받지 않도록 노력하자. (재미 없다는 뜻은 아니지만, 본 문서에서는 그러한 대회에 적용되는 내용이 거의 없다.)
Jeopardy 스타일 CTF의 몇 가지 일반적인 매커니즘은 다음과 같다. :
- 어려운 “역작” 문제를 더 높은 점수를 가지도록 하고, 쉽거나 더 하찮고 게싱하는 문제를 낮은 점수를 가지도록 한다. 이는 사람들이 어려운 문제를 살펴보고 하드코어한 보안 기술을 배우도록 장려한다. 포인트 값을 미세 조정하는 것에 대해서는 걱정하지 말자. “완벽함"은 없다. 문제를 테스트 하는 사람은 문제를 낸 사람보다 난이도를 더 잘 알고 있으므로 테스터는 일반적으로 적합한 점수 값을 할당한다.
- “돌파 점수"를 사용한다.: 각 문제를 처음으로 최초 몇 팀에게만 주어지는 추가 포인트(보통 동점자 처리를 위한 것)을 활용한다. 이는 팀이 자신들의 플래그를 쥐고 있다가 대회 끝에서 모두 제출하는 ‘플래그 키핑’을 방지하는데 도움이 된다 (플래그 키핑은 종종 유리한 경우가 있음).
자신이 생각한 만큼 잘하지 않는다는 사실을 깨달으면 사기가 떨어지기 때문에, 돌파 점수와 같은 키핑 방지 조치를 취하는 것이 좋다 - 문제의 열림/닫힘 상태는 모두 동일해야 한다. 즉, 한 팀에게 문제가 열려 있다면 모든 팀에게 열려 있어야 한다. 이는 모든 팀에게 모든 문제를 살펴볼 공정한 시간과 기회를 제공하고, 불운한 문제를 열어 손해를 보는 팀이 생기는 상황을 피하는 데 도움이 된다.
- 점수판에 있는 모든 팀에는 항상 해결해야 할 문제가 2~3개 이상 있는지 확인해야 한다. 특정 문제에 막히는 것은 어느 팀이게나 쉽게 일어날 수 있다 (자신들의 잘못이 아닐 수도 있음). 그리고 모든 참가자들이 자신들이 다루고 싶어하는 문제를 다루도록 하는게 모두를 만족시키는 최선의 방법이다.
- 열려있는 미해결 문제의 수를 제한하도록 한다. 이는 대규모 팀이 큰 이점을 얻는 것을 방지하며, 내년을 위해 열리지 않은 문제를 몇 개 절약할 수 있다.
Testing
테스팅은 끔찍한 문제와 훌륭한 문제의 차이가 된다. 누군가가 요청할 때마다 서비스가 작동하고 해결 가능한지 확인하기 위해 실행할 수 있는 참조 솔루션을 마련해야 한다.
원래 문제 작성자가 아닌 다른 사람에게 풀이를 작성하게 하여, 심령술사가 아니어도 실행 가능한지 확인해야 한다. 모든 것을 테스트할 시간이 없다면, ‘블랙박스’ 기반 문제에 우선 순위를 둔다.
Communication
주최자는 CTF 전체 기간 동안 연락이 가능하도록 해야 한다. IRC 채널을 만들고 CTF 전체에서 이를 모니터링 한다. 채널에서 주최자들에게 채널 운영자 역할을 부여하여 플레이어들이 문제에 대해 누구와 논의해야 하는지 알 수 있도록 해야한다.
이메일 주소를 공개하고 모니터링 한다.
사이트에 트위터 스트림이나 뉴스 페이지와 같은 최소한 하나의 다른 커뮤니케이션 채널을 가져, 누구도 중요한 게임 업데이트를 놓치지 않도록 해야한다.
Problem Updates
문제에 변경 사항이 있을 때마다 IRC 뿐만 아니라 영구적이고 눈에 띄는 장소에 이를 발표해야 한다.
문제 설명을 업데이트하여 변경 사항을 반영하도록 한다. 변경된 다운로드 파일이 있다면, 파일 이름을 업데이트 하여 변경 사항이 명백하게 드러나도록 한다.
해당되는 경우, 원래 버전의 문제도 계속 사용 가능하게 하고 운영한다. 예를 들어, 업데이트 하려는 문제에 대해 누군가가 정답에 가까이 다가간 경우가 있을 수 있다.
문제에 솔버(solver)가 있는 경우, 문제 변경에 신중해야 한다. (점수 가치, 문제 파일, 설명, 힌트를 포함) 이러한 문제들은 대부분 사례별로 처리해야 하므로 좋은 판단력을 발휘하기 바란다.
Problem Distribution
파일을 배포하는 가장 쉬운 방법은 스코어보드 서버에서 HTTP를 이용하는 것이다. Dropbox 및 Google Drive같은 서비스는 대역폭 제한에 도달할 수 있으므로 사용하지 말아야 한다.
철저한 준비가 되어 있고, 대용량 파일이 있는 경우 배포하는 가장 좋은 방법은 gpg 또는 openssl 같은 무료 도구를 사용하여 대칭 암호화하고 CTF 전에 토렌트를 배포하는 것이다. 그런 다음 CTF의 시작(또는 중간)에 암호화를 해제하여 사람들에게 파일에 대한 액세스 권한을 부여할 수 있다.
문제가 열릴 때까지 문제에 대한 파일에 접근할 수 없는 지 확인하라.
이를 수행하는 한 가지 간단한 방법은 파일 이름에 파일 내용의 해시를 추가하여(파일 이름을 추측할 수 없도록) 웹 서버에서 디렉토리 인덱싱을 비활성화 하는 것이다.
문제를 다운로드 하기 위해 로그인이 필요하지 않은 경우에도 좋다. 이렇게 하면 플레이어들이 wget이나 curl같은 도구를 명령 줄에서 쉽게 사용할 수 있고, 성가신 쿠키 설정이나 로그인 처리 스크립트를 준비할 필요가 없다. 파일명이 비밀로 유지된다면 이것과 관련된 문제가 없어야 한다.
Infrastructure
다른 모든 것과 마찬가지로 인프라의 모든 부분을 주의 깊게 테스트해야 한다.
많은 CTF 주최자들이 웹 사이트와 관련한 문제들을 클라우드에 호스팅하는 것이 유용하다고 발견했다. 이 방식은 필요에 따라 더 많은 인스턴스를 빠르고 쉽게 가동할 수 있다.
최종 배포 인프라에서 적절한 테스트를 수행해야 한다. 예를 들어, 첫 번째 pCTF에서 pwnable 문제들이 NX를 지원하지 않는 기계에서 실행된다면 이는 인프라가 철저히 테스트되지 않아 CTF 이후까지 내용이 누락되었다. (역주: PPP 팀 이야기로 보임)
배포 인프라에서 테스트할 항목의 부분적인 체크리스트:
- 팀 등록 및 키 제출에 대한 End to End Testing
- 키(flag) 제출에 중복 계산 race condition이 없는 지 확인해야 한다.
- 점수판 및 주요 제출에 대한 부하 테스트를 수행한다.
- pwnable 머신이 원하는 보호 기능을 제공하는 지 확인한다.
- 모든 문제에 대한 전체 솔루션을 테스트한다. 이는 솔루션 스크립트를 실행할 수 있고 올바른 키가 출력된다는 의미이다. “내 솔루션 스크립트에 eip=0x41414141이 표시된다"는 것으로는 충분하지 않을 수 있다.
- CTF 중에 문제 업데이트하는 것을 테스트해야 한다. 실수가 발생할 수 있으므로 준비하는 것이 좋다.
- 서비스에서 실행되는 바이너리/코드가 제공하는 파일과 일치하는 지 테스트해야 한다.
Problems
CTF의 목표는 플레이어가 배우고 즐기는 것임을 기억한다.
문제의 핵심은 해결되는 것이므로, 적어도 한 팀 이상이 모든 문제를 해결하는 것이 좋다. 창의력을 발휘하고 풀이자들이 문제에서 멋진 점을 배울 수 있도록 노력해야 한다.
플레이어가 고객이라는 점을 기억하고, 그들을 행복하게 만들자 :-)
아래에는 특정 문제 범주에 대한 좀 더 구체적인 권장 사항이 있다.
Pwnable
이 pwnable 섹션에서는 Linux 바이너리만 구체적으로 다룬다.
Local
로컬 pwnable에서는 일반적으로 시스템에 대한 ssh 연결과 거기에서 setuid/setgid 바이너리 활용이 포함된다.
이를 수행하는 가장 좋은 방법은 팀이 서로 간섭하거나 정보를 유출하지 않고 작업할 수 있도록 컴퓨터에 팀별 계정을 만드는 것이다.
다음은 머신에서 구성할 사항에 대한 간단한 체크리스트이다. :
- 컴퓨터가 완전히 패치되고 최신 상태인지 확인한다.
- limits.conf를 통해 forkbombing 또는 기타 리소스 고갈을 방지한다.
- `sysctl -w kernel.dmesg_restrict=1` # 그리고 `/etc/sysctl.conf`에 persist 를 설정한다.
- `mount -o remount,hidepid=2 /proc` # 사용자가 서로의 프로세스를 볼 수 없도록 한다.
- `chmod 1733 /tmp/var/ /dev/shm` # 작업 내용이 다른 사람에게 유출되는 것을 방지하기 위해 # 사용자별 홈 디렉토리가 있는 경우 `chmod 700` 하면 된다. :-)
- 문제에 대한 사용자를 생성하고 `/home/problemuser/problem`에 문제를 넣는다.
- `chown -R root:rioot /home/problemuser`
- `chown root:problemuser /home/problemuser/problem`
- `chmod 2755 /home/problemuser/problem`
- `touch /home/problemuser/flag`
- `chown root:problemuser /home/problemuser/flag`
- `chmod 440 /home/problemuser/flag`
모든 문제와 마찬가지로 완전히 설정된 후 완전히 테스트한다. 특히, 참조 솔루션이 CTF 사용자 중 하나로 작동하는 지, 플래그가 다른 수단을 통해 읽힐 수 없거나 루트 이외의 사용자가 쓸 수 없는 지 확인해야 한다.
Local Kernel
로컬 커널 익스플로잇 문제는 일반적으로 시스템에 ssh로 접속하고 사용자 정의 커널 드라이버를 활용하는 것과 관련된다. 이러한 유형의 문제는 안정적으로 호스팅하기 어려울 수 있고, 쉽게 확장할 수 없다. 실패한 exploit은 일반적으로 운영체제를 다운시키므로, 각 팀은 자신만의 고립된 vm을 가져야 한다.
커널 문제는 팀 수가 적고 충분한 시스템 자원이 할당될 수 있는 CTF ‘결승전’에 더 적합할 수 있다.
가능한 설정은 하나 또는 여러 ESXi(Type 1 hypervisor) 호스트를 운영하고 각 팀에게 별도의 VM을 지원하는 것이다. 그리고 각 팀에 SSH 자격 증명을 제공한다.
몇 가지 팁과 알림:
- 20개의 개별 VM을 만드는 대신 하나의 기본 VM을 만들고 20개의 연결된 클론을 만든다.
- 각 VM이 생성된 후 로그인하고 고유한 고정 IP 주소를 구성한다.
- 모든 공개 취약점에 대해 운영체제가 완전히 업데이트 되고 패치되었는지 확인한다.
- 사용자를 sudo 접근에서 제거한다.
- /root에 플래그를 생성하고: `chmod 400 -R /root; chown root:root -R /root`
- /root/.ssh에 member SSH key를 삭제하고 원격 루트 SSH 로그인을 활성화하면 잠재적인 문제를 해결하는 데 도움이 된다.
- 문제의 일부가 정보 유출이 아니라면, 모든 사용자가 /proc/kallsyms를 읽을 수 있도록 허용한다: `echo > /proc/sys/kernel/kptr_restrict`
- 오류(oopses)에 대한 커널 패닉을 비활성화 한다.: `echo 0 > /proc/sys/kernel/panic_on_oops`
- 원격 VM을 재부팅하기 위해 팀에서 호출할 수 있는 간단한(인증된) 스크립트를 개발한다. Exploit 시도 후, 게스트 OS가 응답하지 않을 수 있으므로 이는 게스트가 아닌 하이퍼바이저와 상호작용해야 한다. 그렇지 않으면 팀에서 재부팅을 자주 요청하게 된다.
- 작동하는 솔루션이 존재하고 예상되는 모든 완화 조치가 실제로 VM에서 작동하는지 확인한다. 여기에는 읽기 전용 메모리 SMEP/SMAP 등이 포함된다.
커널 챌린지는 재미있어야 한다!
단순히 오래된 OS를 설치하고 참가자들에게 공개적인 악용 코드를 컴파일하라고 하는 것이 아니라, 도전 과제를 창의적으로 만드는 여러 방법들이 있다.
또한 연결 시 qemu를 통해 실행될 수 있는 매우 최소한의 VM을 buildroot로 구축하는 것이 간편하다.
위의 참고 사항이 적용되지만 이러한 접근 방식에는 몇 가지 주의점이 있다.
몇 가지 팁:
- VM에 네트워킹이 되어있는 지 확인한다. 일부 초소형 VM에 16진수/셸코드를 붙여넣는 것은 재미가 없다.
- 실행 중인 vm의 디버깅을 비활성화 하기 위해 qemu 모니터에 접근을 비활성화 한다. : `-monitor /dev/null`
- 가능하면, Ctrl+C를 눌러도 연결이 끊기지 않도록 curses 모드로 qemu 인스턴스를 로그인 셸로 설정한다.
- 대규모 CTF라면 qemu를 시작하기 전에 어떤 종류의 캡챠 또는 작업 증명을 두는 것이 좋을 수 있다.
- 모든 연결이 “새로운” VM을 받도록 한다. 연결 시 디스크 이미지를 복사본으로 교체하는 것은 쉽고, 작은 VM의 경우에는 오버헤드가 많지 않아야 한다.
- KVM은 EC2/Azure같은 가상화된 환경에서 사용할 수 없다. 하드웨어 가상화 없이도 VM의 속도가 충분히 빠른 지 테스트하여 반응성을 확인한다.
Remote
Remote pwnable에는 취약한 네트워크 서비스 실행이 포함된다. 이를 해결하는 두 가지 인기있는 방법이 있다.
xinetd를 사용하는 것과 바이너리 자체에서 fork/accept를 하는 것이다.
각 연결마다 스레드를 사용하지 말아야 한다. 이는 보통 사용자들이 서로의 Exploit을 방해하게 하며(의도적으로나 우연히나), 문제를 매우 짜증나게 만들 수 있다.
문제가 libc leak에 의존하는 경우, 문제 바이너리와 함께 libc.so를 제공하는 것을 고려해야 한다. libc를 찾는 것은 CTF에서 테스트할 흥미로운 기술이 아니다.
xinetd 대신 자체 fork/accept 서버를 사용하는 경우, 서비스를 Exploit 하는 사람이 서비스를 종료하거나 점령할 수 없도록 특별한 주의를 기울여야 한다.
일반적인 방법은 서비스를 루트로 시작하고 forking 후 권한을 하락시키는 것이다. (그리고 소켓 fd를 유출하지 않도록 주의한다)
이 권장 사항을 따르는 샘플 fork/accept 서버는 fork_accept.c 를 참조하라.
xinetd 서비스에 대한 샘플 xinetd구성은 example.xinetd를 참조하라.
chroot 또는 제한된 환경에서 챌린지를 시작하기로 한 경우, /bin/sh, /bin/bash, /bin/cat 같은 기본 프로그램이 있는지 확인한다. 이것이 불가능하다면 문제 설명에서 그 사실을 명확히 해야한다.
서비스를 완전히 Exploit하고 제한된 chroot에서 실행되고 있다는 것을 깨닫기까지 한 시간을 낭비하는 것은 매우 짜증나는 일이다.
remote pwnalbe을 설정하기 위한 지침:
- 문제를 위한 사용자를 생성하고 문제를 /home/problemuser/problem에 둔다.
- `chown -R root:problemuser /home/problemuser`
- `chmod 750 /home/problemuser`
- `touch /home/problemuser/flag`
- `chown root:problemuser /home/problemuser/flag`
- `chmod 440 /home/problemuser/flag`
short read에 의존하지 않도록 한다. 원격으로 올바르게 처리하기 어려울 수 있다. 대신, 구분자가 나타날 때까지 한 바이트씩 읽거나 길이로 제한된 문자열 읽기를 고려한다. (e.g. 4바이트 리틀 엔디언 길이를 읽은 다음 데이터 길이만큼의 바이트를 읽음).
마찬가지로 read/recv 호출 시 반환 값이 확인되어 사용자 입력이 누락되지 않도록 한다.
명확히 하기 위해, 4096 바이트를 읽는 잘못된 예시는 다음과 같다:
char buf[4096];
recv(fd, buf, sizeof(buf)); // 이 부분이 잘못됨. recv는 < 4096을 반환할 수 있음
더 나은 방법은 fork_accept.c 의 recvlen함수를 참조하라.
/home/problemuser/flag 같이 예측 가능한 위치에 flag를 배치한다. 서비스를 성공적으로 exploit 한 후 플래그를 찾는데 시간을 낭비하는 것은 실망스럽다.
General notes
제대로 작동하는 pwnable 문제를 만드는 데 가장 중요한 부분 중 하나는 적절한 테스트이다 (이상적으로는 저자 이외의 최소 한 명이 테스트해야 한다).
누군가 pwnable이 작동하지 않는다고 불평할 때마다, 문제가 작동하는지 여부를 확인할 수 있는 완전한 참조 솔루션을 실시간 인스턴스에 대해 실행할 수 있어야 한다.
다음은 pwnable에서 일반적으로 짜증나는 몇 가지 사항들이다:
- 귀찮은 출력 파싱 출력은 간단하고 파싱하기 쉽게 유지해야한다.
파싱하기 가장 좋은 유형의 출력은 길이로 제한된 문자열이다.
귀찮은 출력 형식의 예:
ASCII 십진수 길이로 구분된 문자열 121A1B1C1D1E1F 이를 어떻게 구분 분석해야 하는가? (12, 1A1B1C1D1E1F)? (1, ‘2’), (1, ‘A’), (1, ‘C’), (1, ‘D’), (1, ‘E’), (1, ‘F’)? 또 다른 귀찮은 출력 형식은 ANSI 이스케이프 코드가 포함된 것이다. (자제 바람) - 바이너리 NX가 활성되어 있어도, 실행되는 기계가 이를 지원하지 않는 경우
- 말이 안 되는 코드와 ‘가짜’버그, 코드의 90%가 리버싱 시간 낭비를 위해 입력을 무작위 상수와 비교하는 것.
이는 재미있는 문제가 아니다. 버그가 무작위 제약 조건이 충족되었을 때, 프로그램이 아무 이유 없이 해당 버퍼로 점프하는 것이라면, 조금 더 창의적이어야 한다.
Compile time proetections
pwnable을 위해 특정 보호 기능들이 활성화되어야 하는 경우가 있다.
여기 gcc에서 이들을 강제로 켜거나 끄는 방법이 있다:
- -fstack-protector / -fno-stack-protector: stack canary
- -D_FORTIFY_SOURCE=2/ -D_FORTIFY_SOURCE=0(재 정의 경고를 무시하기 위해 U_FORTIFY_SOURCE를 앞에 추가): memcpy(), sprintf(), read() 등의 libc 함수 버전을 사용하여 버퍼 오버플로우를 감지할 때 중단한다.(감지가 완벽하지 않고 많은 경우에 작동하지 않는다는 점에 주의한다.)
- -fPIE -pie/ -fno-PIE: 위치 독립적 코드(ASLR을 확장하여 라이브러리 뿐만 아니라 기본 바이너리도 무작위화 함). PIE는 일반적으로 32비트에서는 효과적이지 않다. 즉, PIE를 인식하지 못하는 악용은 수백/수천 번에 한 번씩 성공할 것이다. -fPIC는 메인 실행 파일의 일부가 아닌 코드에 적용될 수 있도록 (특정 최적화를 피함으로써) -fPIE의 버전이며, -fpie와 -fpic도 있다.
- Wl, -z, relro -z, now/ ?: FULL RELRO (GOT와 PLT가 프로그램 로드 중에 쓰여지고 읽기 전용으로 매핑됨).
보안이 더욱 주요 이슈가 되면서, 더 많은 컴파일 시간 및 런타임 보호 기능들이 기본적으로 활성화되고 있다.
따라서, 예상치 못한 상황을 피하기 위해 문제를 최종 구성 및 설정에서 실제로 테스트하는 것이 중요하다.
Web Challenges
문제에 많은 수의 요청이나, 타이밍 측정을 필요로 한다면, 원격으로도 합리적으로 해결할 수 있도록 해야한다.
더 나아가, 같은 네트워크 상에 일반적인 스트립팅 언어와 라이브러리가 설치된 셸 서버(SSH 로그인이 가능한 pwnable 일 수 있음)을 두어 플레이얻르이 공격을 실행할 수 있게 하는 것이 좋다.
다음은 피해야 할 몇가지 사항들이다:
- 플레이어에게 URL 매개변수를 추측하도록 요구(e.g. ?debug=1)
- 플레이어에게 파일이나 디렉터리 이름을 추측하도록 요구(또는 서버에 dirbuster 사용)
- 플레이어에게 크리덴셜을 추측하도록 요구
(패턴을 알아채셨나요?)
웹 문제에서 훌륭한 유형 중 하나는 전체 소스 코드가 제공되는 것이다 (그럼에도 여전히 도전적인 경우).
Reversing
입력이 유효한지 확인하는 문제는 항상 정확히 하나의 정답을 받아들여야 하는 것이 매우 중요하다.
이는 리버싱 문제에서 가장 흔한 문제를 일으키는 실수이다. 이것이 불가능한 경우, 문제 설명에서 이를 명확히 하고, 어떤 입력 플래그도 받아들이고 점수를 받을 수 있는 플래그 출력 양식을 마련한다.
리버싱의 일반적인 리얼 월드 사례 중 하나는 악성 코드(malware)이지만, 악의적인 리버싱 문제를 배포하는 것은 나쁜 취향으로 간주된다.
이렇게 하려는 경우, 해당 프로그램이 악성이라는 점을 명확히 명시해야 한다.
Crypto
일반적으로, 참가자들에게 가능한 많은 정보를 제공하려고 노력해야 한다.
비밀 키가 명확하게 X로 표시된 소스코드와 함께 제공하는 것이 이상적이다.
문제가 암호문만 있는 경우, 다음을 수행해보자:
- 의미 있는 통계를 위해 충분한 암호문을 제공한다 (스무 개의 ASCII 문자는 거의 무엇이든 될 수 있다.)
- 추측 가능한 알고리즘을 사용한다. 고전 암호와 짧은 암호문을 사용할 때, 그것을 좁혀내기는 매우 어려울 수 있다. 도전은 암호 시스템을 깨는 것이지, 암호 시스템이 무엇인지 알아내는 것이 아니다.
- 19세기 이후에 만들어진 암호 시스템을 사용하는 경우, 참가자들에게 알고리즘이 무엇인지 반드시 알려주어야 한다. 아무도 암호문이 에니그마나 퍼플 머신에서 나온 것인지, 아니면 3DES나 GOST에서 나온 것인지 추측하고 싶어하지 않는다.
- 문제가 많은 로컬 계산을 요구하는 경우, 합리적인 소비자 하드웨어에서 테스트하여야 한다. 문제를 해결하는 올바른 방법이 있지만 종료되기 전에 계산을 완료할 수 없다는 것을 대회 후반에 깨닫는 것은, 기분이 좋지 않다. 일반적으로 현대적인 소비자 하드웨어에서 1시간 미만이면 괜찮다. 그 이상이라면 정당한 근거가 있어야 한다.
Forensics
포렌식은 다음과 같은 여러 요소가 혼합되어 포함될 수 있다.
- 네트워크(pcap 파일)
- Recon(CVE, 사람, 위치 식별)
- 안티 포렌식 기술
대부분 포렌식 도전과제는 사건 대응과 연계될 수 있으며, 학습자에게 주의를 기울여 처리하는 방법을 가르친다.
이는 파일을 실행하거나 부주의하게 다루면(도전 과제의) 데이터 손실이나 문제를 해결하는데 필요한 중요한 정보의 손실로 이어질 수 있다는 것을 의미한다.
예를 들어, 포렌식 전문가들은 데이터의 무결성이 법정에서 사용될 경우, 진실되고 유효하게 유지되도록 하는 방법으로 쓰기 방지 도구를 사용한다.
도전 과제는 플레이어들을 하나의 여정, 여러 데이터 소스를 통한 조사로 이끌어 결국 하나의 해결책으로 귀결되도록 해야 한다.
- 네트워크 도전 과제(.pcap 등)를 만들 때, “TCP 스트림 따라가기” 과정보다는 플레이어가 데이터의 다양한 출처와 목적지, 그들 사이의 타이밍을 이해하고 이를 스토리로 매핑하도록 하기를 바란다.
- 기본적인 인코딩, 암호화 또는 사용자 정의 프로토콜을 추가하는 것은 더 고급 도전과제에 훌륭할 수 있지만, 네트워크 포렌식 도전 과제를 암호학 문제 정의로 바꾸지 않도록 주의해야 한다.; 여기서 강조하고 싶은 기술은 네트워크 개념과 기술을 이해하고 wireshark/tshark/scapy를 자신있게 활용할 수 있는 플레이어의 능력을 강화하는 것이다.
- 계획된 잡은/간섭이 있는 샌드박스에서 도전 과제를 만들자. 드롭박스 패킷이 날아다니는 로컬 기계에서 실행하지 않도록 한다.
- 분명 사물인터넷(IoT) 기기, QR 코드 조회, 프록시/VPN을 통한 공격자 원본 IP 출처 찾기 등을 실험해보자. 이런 것들은 모두 유효한 실제 사례이며, 플레이어가 관찰하고 식별하며 분석할 것을 요구한다.
Miscellaneous
가능한 다음은 피하도록 한다:
- 랜덤 게싱 문제
- zip파일이나 stego 프로그램의 비밀번호 크래킹
- 스테가노 그래피 문제
- Metasploit, nessus, dirbuster 등을 실행하여 해결되는 모든 것, 좋은 CTF 문제에는 기술이 필요하다.
- 시간이 많이 걸리는 Recon 문제
마치며
이상으로 문서의 번역을 마치며,
해당 글은 CTF를 운영하는 사람이나, CTF 참가자 모두에게 도움이 될 것으로 기대한다.
CTF 참가자는 CTF의 운영을 이해하고 어떤 문제들이 어떻게 출제되는 지 큰 그림을 그릴 수 있으며, CTF 운영자는 직접적으로 CTF에 어떤 문제를 출제하고 어떻게 운영해야하는 지 알 수 있기 때문이다.
(실제로 위 내용의 부적절한 사항에 해당하는 CTF들을 참가해본 경험이 자주 있었다.)