ENKI RedTeam CTF
개발자가 참여해본 해킹(RedTeam) 대회 후기
해킹 대회에 참가했다. 보안 공부하는 친구가 참가한다길래, 그럼 나도 해봐?? 5년차 개발자 짬바가 있지 하는 마음으로 찍먹을 시도했다.
들어가기에 앞서,
내가 참가한 대회명은 ENKI RedTeam CTF다. (링크)
본격적으로 후기를 풀기 전에, 보안 용어가 낯선 분들을 위해 간단히 정리하고 넘어가자. (참가하는 나도 용어를 몰랐다)
RedTeam: 보안 취약점을 점검하기 위해 '공격자' 입장에서 침투 시나리오를 실행하는 팀이다. 반대로 방어자는 'BlueTeam'이라고 부른다.
CTF(Capture The Flag): 직역하면 '깃발 뺏기'다. 시스템의 약점을 파고들어 숨겨진 특정 문자열(Flag)을 찾아내면 점수를 얻는 해킹 경진대회다.
ENKI: 꽤 규모가 있는 국내 사이버 보안 기업이다. 화이트 해커 채용을 위해 이번 대회를 열었다고 함.. (상위 ~30위까지 채용 기회를 제공한단다.)
여튼 대회명을 풀어보면.. ENKI가 주최한 침투(해킹) 대회인데, 누가 FLAG(문자열)을 잘 찾아내는지로 등수를 매기겠다는 의미이다.
대회 시작
사실 나는 보안이란 분야에 접한 적이 많이 없었다. 정보보안 과목은 드랍했었고, 보안기사도 필기만 겨우겨우 붙어 실기를 드랍했다.
보안이라는게 알아야 할 것도 남다르게 많고, 전 날 하루 보는게 무슨 의미가 있나.. 하는 마음에 잠이나 잘자고 머리 박자라는 마음으로 그냥 일찍 잤다.
대회는 온라인으로, 토요일 오전 11시 ~ 일요일 오전 11시 총 24시간 동안 진행했다.
나는 11시 시작과 동시에 문제 유형을 먼저 파악했다.
첫 번째 유형은 클라이언트/서버의 소스 코드와, 공격 대상 웹사이트 주소를 제공하는 유형
두 번째 유형은 아무 정보 없이 웹사이트 주소만 달랑 있는 유형 (이 유형은 웹사이트를 통해 공격 대상 서버의 루트 권한에 접근하고 플래그를 얻어야 했다.)
유형을 모두 파악한 나는 나의 필살기 클로드 코드를 터미널에 여러개 올렸다.
나름 전략적으로 인터넷에서 senior-security-engineer라는 SKILL도 찾아 맥였다.
나의 재빠른 손가락과 클로드의 명석한 두뇌와 함꼐라면 무서운게 없었다.
총 4개의 터미널에 4개의 문제를 올려놓고, 클로드에게 두뇌를 의탁한 나는 '너무 빨리 풀어버리면 어떡하지?' 하는 근자감을 디저트로 커피 한 잔 했다.
클로드에게 실망했다
내 역할은 4개의 터미널에 Y를 눌러주는 일이었다. Y 클릭 노동을 1시간쯤 하고 있자니 분위기가 슬슬 이상했다.
여러가지 시도를 하기는 하는데 통과하는 취약점을 찾지는 못하고, 특정 케이스에 매몰되어 의미없는 시도만 반복했다.
설상가상 클로드 코드 세션 한도도 도달하고, 이제 남은건 gemini 무료 플랜과 내 머리 뿐이었다.
직접 풀기
내가 그나마 좀 아는 분야의 하나의 문제를 잡고 깊이 보았다.
문제의 서비스는 웹을 제공하는 Next 프론트엔드와, 외부에 노출되지 않은 Flask + Redis 백엔드로 분리된 구조였다.
대부분의 소스 코드 분석은 완료했는데, 결론은 Next가 Flask의 특정 endpoint로 내부 요청을 보내게 해야만 한다는 것이었다.
근데 그걸 어케함??? 소스 코드 상 internal 호출하는 부분은 모두 활용할 수 없어 보였다.
여기서 막혀 6시간이 지나 오후 5시가 되었다.
클로드는 신이야
클로드 세션 한도가 풀리고, 클로드에게 내가 파악한 부분을 공유하여 어떻게 해결할 수 있을지를 함께 고민했다.
그렇게 대화를 나누던 중 가능해 보이는 제안이 나왔다.
Next가 제공하는 next-image 기능을 악용하자는 것인데, 결론은 이 것으로 해결 보았다.
Next는 Image를 Optimize 하기 위해, 내부 파일 시스템 혹은 외부의 특정 엔드포인트로부터 이미지를 불러와 해상도에 맞는 리사이징을 해준다.
이 때, 외부 이미지를 가져오도록 https://nextservice.com/_next/image?url=https://img.com/cat.png 형태로 호출할 수 있는데, ?url= 쿼리 파라미터에 내부(127.0.0.1:5000/get-image) 엔드포인트를 호출하도록 유도하면 된다.
처음에 이 아이디어를 보자마자 이건 풀었다. 라고 생각했는데, 실제로 ?url=http://127.0.0.1/get-image의 형태로 호출하니 Next 정책에 의해 차단되었다.
찾아보니 Next는 127.0.0.1이나 localhost 같은 내부 호출을 자체적으로 막는다고 하더라.
여기서도 클로드라에몽에게 안된다고 징징댔더니, DNS-Rebinding이라는 기법이 있다고 소개해줬다.
url을 검사하는 시점과 실제로 데이터를 가져오려고 접속하는 시점 사이에 DNS 응답값을 달리해, ip 기반 검사를 뚫는 기법이다.
function fetchImage(url) {
const host = url.getHost();
if (host === '127.0.0.1') { // 이 시점에는 정상적인 공인 ip
return null;
}
const image = fetch(url); // 이 시점에는 127.0.0.1
return optimize(image);
}next에서는 위와 같은 형태로 호출을 막을 것이므로, url 검사에서 정상적인 공인 ip로 뚫고 fetch()하는 시점에 127.0.0.1로 호출하면 되는 것이다.
이 방법을 사용해 최종적으로 FLAG를 획득했다. 문제 풀이를 시작한지 8시간 만인 오후 7시였다.
FLAG를 얻고서는 정말 오랜만에 짜릿한 도파민을 느꼈다. 하나의 문제를 8시간동안 잡은 것이 얼마 만인가?
그 풀이는 물론 클로드의 영향이 크지만, 클로드님의 가르침을 알아듣고 똑바로 실행한 나에게도 조금의 공은 있다고 본다.
성적
진땀 풀이로 1문제 풀었다.
(사진에는 2개로 보이는데, 한 문제는 디스코드 참여만 하면 푼 것으로 쳐준다.)

느낀 점
이후 다른 문제도 하나 해봤다. 다만 꼭 하나의 연결 고리가 막혀 진행이 안되더라.
느낀 점은 어지간히 아는 정도로는 문제 하나 풀기도 힘들다는 것이다.
하나라도 풀었다는 것에 성취감이 있다.
Disclaimer
엔키에서 공개한 정답 풀이(writeup) 및 다른 문제를 이 링크에서 확인할 수 있습니다.
본 후기는 개인적인 학습 경험을 기록하기 위해 작성했습니다. 아티클에 저작권이나 기타 문제가 있는 경우
wichan.dev@gmail.com로 연락 부탁드립니다.