kt cloud tech up 2,4팀 침투 테스트 보고서 및 후기

HoHk☔️🐁·2026년 4월 5일

Kt Cloud tech up

목록 보기
13/13
post-thumbnail

3줄 요약

  1. 클라우드 보안 훈련에서 72시간 동안 KBO 티켓 예매 플랫폼 2개를 대상으로 레드팀 침투테스트를 수행했다
  2. 65건의 취약점을 발견했고, 그 중 CRITICAL 15건 — 금융 사기, 관리자 장악, 서비스 중단, 데이터 변조까지 전부 성공했다
  3. GitHub에 올라간 .env 파일 하나가 전체 인프라 장악으로 이어지는 걸 직접 증명했다

모든 민감 정보(도메인, IP, 크리덴셜)는 마스킹 처리했다. 인가된 보안 훈련 환경에서 수행한 테스트다.


시작하며

클라우드 기반 72시간 사이버보안 훈련에 레드팀으로 참가했다. 대상은 KBO 야구 티켓 예매 플랫폼 2개. 블루팀(방어팀)이 실시간으로 대응하는 상황에서, 혼자서 2개 플랫폼을 동시에 공격해야 했다.

72시간이면 넉넉해 보이지만, 인프라 구축부터 정찰, 공격, 보고서까지 전부 혼자 해야 하니까 시간이 진짜 부족했다. 특히 Day 3에 IP가 블랙리스트에 걸려서 VPN 전환하느라 시간 날린 게 아쉽다.

결과적으로 Target A에서 38건, Target B에서 27건, 총 65건을 찾아냈다. 금융 사기 벡터부터 JWT 위조, S3 변조, 기업 이메일 탈취까지 꽤 다양한 공격을 성공시켰다.


대상 시스템 분석

두 플랫폼 다 Spring Boot + AWS EKS + Cloudflare 조합이었다. 구조가 비슷해 보이지만, 보안 수준은 완전히 달랐다.

Target A — 티켓 예매 + 리세일 플랫폼

항목스택
인프라AWS EKS (K8s) + Cloudflare CDN/WAF
프록시Envoy/Istio 서비스 메시
백엔드Spring Boot (6개 마이크로서비스)
프론트엔드React (Rsbuild)
인증카카오/네이버/구글 OAuth + JWT (RS256)
CI/CDArgoCD v3.3.2 + Argo Rollouts
모니터링Grafana + Prometheus(Mimir) + Loki + Tempo

6개 마이크로서비스가 각각 다른 포트에서 돌아가고 있었다 — 유저(8081), 야구구단(8082), 티켓팅(8083), 결제(8084), 리셀(8085), 대기열(8086). 서비스 메시까지 제대로 구성해놔서 아키텍처 자체는 잘 만들었는데, 문제는 인가(Authorization) 처리가 통째로 빠져있었다는 거다.

Target B — 티켓 예매 플랫폼

항목스택
인프라AWS EKS + Cloudflare (prod만, staging은 없음!)
백엔드Spring Boot (API Gateway + 4 마이크로서비스)
프론트엔드Vercel (staging)
메시징Apache Kafka
DBPostgreSQL + Redis
인증카카오 OAuth + JWT (RS256)

Target B는 prod에 Cloudflare IP 화이트리스트를 걸어놔서 외부에서 접근 자체가 안 됐다. 근데 staging 환경이 ALB 직접 노출 상태였다. Cloudflare가 없으니 WAF도 없고, 그대로 뚫렸다.


공격 인프라 구축

훈련 시작하자마자 AWS에 EC2 3대를 띄워서 공격 인프라를 구축했다. 인프라 없이 침투테스트하는 건 맨손으로 싸우는 거나 마찬가지다.

 ┌──────────────────────────────────────────────────────┐
 │                  Attack Infrastructure                │
 ├───────────────┬───────────────┬──────────────────────┤
 │   C2 Server   │  Redirector   │     Operator         │
 │  (Private IP) │  (Public IP)  │    (Public IP)       │
 │               │               │                      │
 │  Sliver C2    │  Nginx RP     │  Nmap, Masscan       │
 │  Metasploit   │  socat relay  │  Nuclei, SQLMap      │
 │  Chisel       │  OAuth 캡처   │  Hydra, Nikto        │
 │  Ligolo-ng    │  서버         │  AWS CLI, pyjwt      │
 └───────────────┴───────────────┴──────────────────────┘

C2 서버는 Sliver를 메인으로 썼다. Sliver는 Go 기반 C2 프레임워크인데, Cobalt Strike보다 탐지 회피가 좋고 무료다. 다만 이번 훈련에서는 웹 앱 공격이 메인이라 C2를 본격적으로 쓸 일은 별로 없었다.

리다이렉터는 Nginx 리버스프록시로 구성했다. C2 콜백용이면서 동시에 OAuth 인증 코드 캡처 서버로도 활용했다. Target A에서 OAuth redirect_uri 검증이 없는 걸 발견하고, 여기로 인증 코드를 빼돌리는 구조를 만들었다.

DNS는 공격 도메인 하나를 사서 서브도메인 4개(C2 콜백, 백업, 대체, 오퍼레이터)를 설정했다.


Day 1: 정찰 + 초기 침투

JS 번들 역공학 (Target A)

가장 먼저 한 건 프론트엔드 JS 번들 분석이다. 별도 도구 없이 curl이랑 브라우저 DevTools만으로 15분 만에 핵심 정보 대부분을 뽑아냈다.

React SPA는 빌드하면 JS 번들에 모든 클라이언트 로직이 들어간다. API 엔드포인트, OAuth 설정, 라우팅 구조 전부 다. 난독화를 했다 해도 결국 브라우저에서 실행되는 코드니까, 시간만 들이면 다 읽을 수 있다.

수집 정보방법소요 시간
28개 API 엔드포인트JS 번들 역공학3분
카카오 OAuth Client IDJS 번들에서 추출2분
인프라 스택 (Spring Boot + Envoy + EKS)HTTP 응답 헤더 분석3분
ArgoCD v3.3.2 서브도메인직접 접근1분
Grafana 12.4.0 서브도메인직접 접근1분
Cloudflare WAF 활성 확인SQLi 시도 -> 4031분

API 엔드포인트 28개를 한 번에 확보한 게 컸다. 일반적으로 API 매핑은 시간이 오래 걸리는 작업인데, JS 번들에 다 나와있으니 그냥 읽기만 하면 됐다.

Day 3에는 더 깊이 들어가서 dev + prod 번들 10개를 전부 다운로드해서 완전 역공학을 수행했다.

[핵심 발견]
대기열 토큰 JTI = "queue-token-" + gameId

gameId는 공개 API(/api/v1/games/schedules)에서 누구나 조회 가능
-> 대기열 토큰을 예측해서 위조할 수 있다
-> 수만 명이 대기 중인 대기열을 건너뛰고 바로 티켓 구매 가능

여기서 Queue Token이라는 개념을 짚고 넘어가야 한다. 인기 경기 티켓팅 때 동시 접속자가 몰리면 대기열(Queue)에 넣어서 순서대로 입장시키는 구조다. 이 대기열을 통과했다는 증명이 Queue Token인데, 이 토큰의 고유 식별자(JTI)가 queue-token-${gameId} 형태로 완전히 예측 가능했다. gameId만 알면 대기열 안 거치고 바로 좌석 선택 페이지로 들어갈 수 있는 거다. 매크로 업자한테는 꿈 같은 취약점이다.

GitHub OSINT (Target B) — .env 하나로 전부 털림

Target B는 GitHub 공개 레포에서 .env 파일이 그대로 올라가 있었다. 솔직히 이건 정찰이라기보다 그냥 선물이었다.

[.env에서 발견한 시크릿 목록]

1. JWT RSA Private Key (2048bit 전문)
2. 대기열 전용 RSA Private Key  
3. AWS IAM Access Key + Secret Key
4. OAuth Client ID + Client Secret
5. PostgreSQL 크리덴셜 (비밀번호가 1234...)
6. DB 관리도구 계정 3개
7. 개발 계정 (dev / 1234)
8. DB 암호화 키
9. Internal API Key
10. Gmail App Password

RSA Private Key가 뭐냐면, JWT 토큰을 서명하는 데 쓰는 비밀 키다. 서버가 "이 토큰은 내가 발급한 게 맞다"라고 증명하는 서명을 만들 때 이 키를 사용한다. 이 키가 유출되면? 공격자가 아무 권한의 토큰이든 자유롭게 만들어낼 수 있다. ADMIN이든 뭐든.

.env 파일 하나가 이후 전체 공격 체인의 출발점이 됐다. JWT 위조로 관리자 장악, AWS 키로 S3 변조, Gmail 비밀번호로 이메일 탈취까지. 하나의 실수가 도미노처럼 전부 무너뜨린 거다.

ArgoCD 무인가 접근 (Target A)

ArgoCD는 K8s 환경에서 GitOps 기반 배포를 자동화하는 도구다. Git 레포에 설정을 push하면 ArgoCD가 자동으로 클러스터에 반영하는 구조.

이 ArgoCD가 Google OAuth(Dex)로 인증을 처리하고 있었는데, 허용 도메인 제한이 없었다. 아무 Gmail 계정으로 로그인이 됐다.

[로그인 결과]
{"loggedIn": true, "username": "[공격자]@gmail.com"}

ArgoCD에 들어가니까 앱 배포 구조, Dex JWKS 공개키 2개, 리소스 오버라이드 설정이 전부 보였다. RBAC으로 읽기 전용 권한만 줬기 때문에 클러스터/앱 생성은 403이었지만, 내부 아키텍처 파악에는 충분했다.

OAuth Redirect URI 미검증 (Target A)

OAuth는 카카오/네이버/구글 같은 외부 서비스로 로그인하는 방식이다. 사용자가 카카오에서 로그인하면, 카카오가 "이 사람 인증했어"라는 코드를 redirect_uri로 보내준다. 이 redirect_uri는 반드시 우리 서비스 도메인만 허용해야 한다.

근데 Target A에서는 redirect_uri를 공격자 도메인으로 바꿔도 정상 리다이렉트가 됐다. 이게 왜 위험하냐면:

[공격 시나리오]

1. 공격자가 조작된 카카오 로그인 URL을 피싱으로 배포
   (redirect_uri를 공격자 서버로 변경)
2. 피해자가 카카오 로그인 수행 (정상 카카오 페이지라서 의심 안 함)
3. 인증 코드가 공격자 서버로 전달됨
4. 공격자가 코드 -> 토큰 교환 -> 피해자 계정 탈취

이건 발견하자마자 리다이렉터에 OAuth 캡처 서버를 배포했다. 실제로 인증 코드를 캡처하는 구조까지 만들어놨다.

전체 API RBAC 부재 발견 (Target A)

여기서부터 진짜 심각한 취약점들이 나오기 시작했다.

dev 환경에서 테스트 유저 API로 일반 사용자(MEMBER) 토큰을 발급받았다. 그리고 이 토큰으로 관리자 전용 엔드포인트에 요청을 보냈다.

[예상]
403 Forbidden — "권한이 없습니다"

[실제]
400 Bad Request — "시/군/구 값은 필수 항목입니다"

403이 아니라 400이 온다? 이건 권한 검증 자체를 안 하고 있다는 뜻이다. 서버가 "너 관리자 아닌데?" 하는 게 아니라 "필드가 빠졌어"라고 응답한다는 건, 요청이 권한 체크를 통과해서 비즈니스 로직까지 도달했다는 거다.

인증(Authentication)인가(Authorization)는 완전히 다른 개념이다. 인증은 "너 누구야?"를 확인하는 거고, 인가는 "너 이거 할 수 있어?"를 확인하는 거다. Target A는 인증은 되어있는데 인가가 통째로 빠져있었다. 토큰만 있으면 MEMBER든 ADMIN이든 상관없이 뭐든 할 수 있었다.

21개 이상의 관리자 엔드포인트를 전부 테스트했고, 전부 동일한 결과였다.


Day 1: 핵심 공격 성과

정산 API 금융 사기 — 200건+ (Target A)

RBAC이 없다는 걸 확인하고 바로 정산 API를 노렸다. 정산(Settlement)이란 리셀 거래에서 매도자에게 돈을 지급하는 프로세스다.

PATCH /api/v1/[redacted]/orders/{random-uuid}/settled
-> 200 OK

랜덤 UUID를 넣어서 요청하면 200 OK가 온다. 존재하지도 않는 주문에 대해 정산 처리가 성공한다. 반복 스크립트를 돌려서 200건 이상의 허위 정산을 처리했다 — 30건, 50건, 100건, 20건 순으로.

실제 서비스였으면 이건 무제한 금융 사기 벡터다. 가짜 주문에 대해 정산을 쏟아부으면 자금이 빠져나간다.

방어팀이 Day 2에 403 패치를 완료했다. 빠르게 대응한 편이다.

JWT 위조 -> 관리자 완전 장악 (Target B)

GitHub에서 발견한 RSA Private Key로 ADMIN 권한의 JWT를 위조했다. pyjwt와 cryptography 라이브러리를 사용했다.

# JWT 위조 코드 (민감 정보 마스킹)
import jwt
from cryptography.hazmat.primitives import serialization

# GitHub에서 탈취한 RSA Private Key 로드
private_key = open("stolen_private_key.pem", "rb").read()

payload = {
    "iss": "[redacted]-auth-service",
    "sub": "1",                    # id:1 = 관리자 계정
    "aud": "[redacted]-api",
    "auth": "ROLE_ADMIN",          # 관리자 권한
    "tokenType": "ACCESS",
    "exp": int(time.time()) + 3600 # 1시간 유효
}

token = jwt.encode(payload, private_key, algorithm="RS256")

이 위조 토큰으로 /auth/me를 호출하니까:

{"id": 1, "email": "[redacted]@gmail.com", "nickname": "[방어팀] 개발팀"}

id:1 관리자 계정을 완전히 장악했다. Staging 서버의 모든 API에 관리자 권한으로 접근 가능한 상태.

AWS S3 Defacement (Target B)

.env에서 나온 AWS IAM Access Key로 AWS CLI 접근을 시도했다.

[공격 흐름]

1. aws sts get-caller-identity -> 성공 (IAM User 확인)
2. S3 버킷 열거 -> [bucket-name] 버킷 접근 가능
3. 사용자 업로드 이미지 파일 다운로드 (데이터 유출)
4. 해킹 증거 파일 업로드 (Defacement)
   - hacked.html, proof.txt 업로드 성공

aws sts get-caller-identity는 "지금 이 AWS 키가 누구 것이냐"를 확인하는 명령어다. 이게 성공하면 해당 IAM 유저의 권한 범위 내에서 뭐든 할 수 있다. 이 키는 S3 presigned URL 생성용이라 S3 접근 권한이 있었고, 실제로 파일을 다운로드하고 업로드하는 것까지 성공했다.

Gmail 기업 이메일 탈취 (Target B)

.env에 Gmail App Password까지 있었다. Gmail App Password는 2FA가 걸린 Gmail 계정에서 외부 앱이 접근할 수 있게 발급하는 비밀번호다.

[결과]
- SMTP 로그인 성공 (smtp.gmail.com:587) -> 이메일 발송 가능
- IMAP 접속 -> 24개 이메일 전체 열람
- 이 계정으로 피싱 이메일 보내면 정상 이메일과 구분 불가

24개 이메일에 기업 내부 커뮤니케이션이 들어있었다. 실제 공격이었으면 여기서 추가 정보를 뽑아서 소셜 엔지니어링에 활용했을 거다.


Day 2: 심화 공격 + 방어팀 공방

Cloudflare Workers DoS (Target A)

이건 좀 예상 밖이었다. Target A가 Cloudflare Workers 무료 플랜을 쓰고 있었는데, 무료 플랜은 일일 요청 한도가 100K다.

스캐닝이랑 일반 트래픽이 합쳐지면서 이 한도를 넘겼고, 전체 서비스가 중단됐다.

error_code: 1027
error_name: "workers_daily_limit"
-> 전체 서비스 다운 (최대 24시간)

그리고 더 심각한 DoS 벡터를 하나 더 발견했다. 토큰이 만료되면 프론트엔드가 reissue 엔드포인트로 재발급을 시도하는데, reissue가 500 에러를 내면 원래 API를 다시 호출하고, 또 401이 오고, 또 reissue를 하고... 무한 루프에 빠진다.

만료 토큰 -> 401 -> reissue -> 500 -> 원래 API -> 401 -> reissue -> 500 -> ...
(무한 반복)

한 명의 사용자가 이 루프에 빠지면 수 분 안에 100K 한도를 혼자서 소진할 수 있다. 무료 플랜의 DoS 벡터가 두 개나 된 거다.

Grafana 메트릭 7,748줄 탈취 (Target A)

Grafana는 모니터링 대시보드 도구다. Prometheus에서 수집한 메트릭을 시각화해주는 건데, /metrics 엔드포인트가 인증 없이 열려있었다.

[비인증 접근으로 확보한 정보]
- 데이터소스 5개 (Prometheus, Loki, Tempo, Pyroscope, Alertmanager)
- 관리자 1명
- 대시보드 38개
- 폴더 5개  
- admin 요청 98회
- Feature Toggle 68건 전체 노출

Grafana Feature Toggle이 68건이나 노출된 게 특히 중요한데, 이 중에 enableSCIM(ON), dashgpt(ON), kubernetesDashboards(ON), awsDatasourcesTempCredentials(ON) 같은 보안 관련 설정이 다 보였다. 공격자 입장에서 어떤 기능이 켜져있는지 알면 공격 벡터를 좁힐 수 있다.

방어팀 실시간 패치

72시간 동안 방어팀이 실시간으로 대응했다. Day 1에 터진 정산 API를 Day 2에 403으로 패치한 것처럼, 블루팀도 열심히 했다.

엔드포인트Day 1Day 3패치
정산 API200 OK403 Forbidden패치 완료
좌석 섹션/등급 생성400403패치 완료
가격 정책 설정400403패치 완료
에스크로 해제500403패치 완료
경기 생성 (POST)200 OK200 OK미패치
경기 수정/삭제500미패치

경기 생성 API는 끝까지 안 막혔다. Day 3에 이걸 이용해서 경기를 3건 더 만들었다.


Day 3: 최종 공격

IP 블랙리스트 우회

Day 3 시작하자마자 확인해보니 기존 VPN IP가 봇 탐지 시스템에 걸려서 블랙리스트에 등록되어 있었다. 방어팀의 Guardrail(행동 분석 기반 봇 탐지)이 작동한 거다.

VPN 서버를 전환해서 새 IP를 할당받고 공격을 계속했다. 실전에서도 IP가 차단되면 이렇게 전환하는 게 기본이다.

프로덕션 데이터 변조 (Target A)

미패치된 경기 생성 API를 이용해서 프로덕션 환경에 직접 데이터를 주입했다.

항목내용
구장"[레드팀]테스트구장" 1건 생성
경기가상 대진표로 5건 생성 (Day 2에 2건 + Day 3에 3건)
노출공개 일정 API에 즉시 반영 — 일반 사용자에게 가짜 경기가 보이는 상태

MEMBER 토큰으로 프로덕션 데이터를 변조한 거다. 실제 서비스였으면 사이트 변조(Defacement)에 해당한다. 가짜 경기를 만들어놓고 티켓팅 오픈일까지 설정하면, 사용자들이 존재하지 않는 경기에 대해 티켓을 사려고 할 수도 있다.

Staging 인프라 정보 대량 유출 (Target B)

Target B의 staging에는 Cloudflare가 없어서 ALB(Application Load Balancer)가 직접 인터넷에 노출되어 있었다. 여기서 Spring Boot Actuator를 통해 내부 정보가 대량으로 유출됐다.

Spring Boot Actuator는 앱의 상태를 모니터링하기 위한 엔드포인트 모음이다. health, metrics, info 같은 엔드포인트가 있는데, 이게 인증 없이 열려있으면 내부 아키텍처가 그대로 드러난다.

[Actuator에서 추출한 정보]

K8s 내부 서비스 DNS:
- auth-guard.staging-webs.svc.cluster.local:8080
- queue.staging-webs.svc.cluster.local:8081
- seat.staging-webs.svc.cluster.local:8082
- order-core.staging-webs.svc.cluster.local:8083

Gateway 라우트:
- /order/clubs, /auth/token/refresh, /order/matches
- /auth/loadtest/login, /auth/loadtest/signup

서버 리소스:
- 디스크: 14.96GB / 21.4GB
- JVM 메모리: 215MB
- CPU: 1.9%
- Thread Pool MAX: 2,147,483,647 (Integer.MAX_VALUE)

Thread Pool MAX가 Integer.MAX_VALUE다. 이건 스레드 수에 제한이 없다는 뜻인데, 무인증 엔드포인트에 동시 요청을 수만 개 보내면 스레드가 무제한으로 생성되면서 OOM(Out of Memory) Kill로 서비스가 죽을 수 있다.

Gateway 쓰기 메서드 인증 우회 (Target B)

API Gateway에서 GET 요청은 무인증으로 데이터가 반환됐다. 그래서 POST/PUT/PATCH/DELETE도 시도해봤는데:

GET    /order/clubs   -> 200 OK (데이터 반환)
POST   /order/clubs   -> 500 (NOT 401!)
PUT    /order/clubs/1 -> 500 (NOT 401!)
PATCH  /order/clubs/1 -> 500 (NOT 401!)
DELETE /order/clubs/1 -> 500 (NOT 401!)

전부 500이지 401이 아니다. 이 말은 Gateway가 쓰기 메서드를 인증 없이 백엔드까지 그대로 전달하고 있다는 거다. 지금은 백엔드에 해당 API가 제대로 구현 안 되어서 500이 나오지만, 구현이 완성되는 순간 무인가 변조가 바로 가능해진다. 시한폭탄이다.


실패한 시도들

성공한 것만 쓰면 포트폴리오가 아니라 자랑글이 된다. 실패 사례도 중요한데, 여기서 방어가 어디서 잘 작동했는지 보이기 때문이다.

시도방법결과
Grafana CVE SSRFcurl SSRF payloadCloudflare WAF 차단
WAF 우회 (5가지)Path traversal, URL encoding, Double encoding, Case variation, Trailing dot전부 실패 — Cloudflare가 정규화 후 매칭
JWT 알고리즘 혼동HS256 (6개 비밀키), "none" 알고리즘RS256만 수용 (방어 작동)
Grafana 비밀번호admin:admin 등 15개+전부 불일치
K8s API 익명 접근직접 요청401 Unauthorized (익명 차단)
SQLi (Staging)OR 1=1, UNION SELECT앱 레벨 보안 필터 차단
ArgoCD 권한 상승클러스터/앱/GPG 접근RBAC으로 403
Prod 직접 접근 (Target B)Origin IP 탐색IP 화이트리스트 차단

JWT 알고리즘 혼동 공격(Algorithm Confusion)은 좀 설명이 필요하다. JWT는 서명 알고리즘을 헤더에 명시하는데, 서버가 이걸 신뢰하면 문제가 된다. 예를 들어 RS256(비대칭)으로 서명된 토큰을 HS256(대칭)으로 바꾸고, 공개키를 비밀키로 사용해서 서명하면 검증이 통과되는 경우가 있다.

Target A에서 이걸 시도했는데, RS256만 수용하도록 제대로 설정되어 있어서 실패했다. 여기는 방어팀이 잘 한 부분이다.

Cloudflare WAF 우회도 5가지 기법을 전부 시도했지만 다 막혔다. Cloudflare가 URL을 정규화한 다음에 패턴 매칭을 하기 때문에, 단순한 인코딩 우회로는 뚫리지 않는다. WAF의 존재감을 제대로 느꼈다.


종합 성과

지표Target ATarget B
CRITICAL87
HIGH1611
MEDIUM119
INFO30
합계3827

카테고리별 주요 성과

카테고리내용
금융 사기정산 API 200건+ 무인가 처리
관리자 장악JWT 위조 -> Staging 관리자 계정 완전 장악
데이터 변조프로덕션 구장 1건 + 경기 5건 무인증 생성, S3 Defacement
데이터 유출S3 파일, Gmail 24건, Grafana 메트릭 7,748줄, 구단 데이터
서비스 중단Cloudflare Workers DoS + 토큰 무한재시도 DoS
인프라 파악ArgoCD 설정, K8s 서비스 DNS 4개, API 서버 IP, Gateway 라우트 5개
계정 생성loadtest API로 51개 무인가 계정
코드 역공학JS 번들 10개 -> Queue Token/API 체인/인증 로직 완전 복원

OWASP Top 10 매핑

두 플랫폼의 취약점을 OWASP Top 10에 매핑하면 이렇다.

OWASPTarget ATarget B
A01: Broken Access Control전체 API RBAC 없음, 정산 무인가, 프로덕션 변조Gateway 쓰기 인증 우회, loadtest 무인가, Actuator 노출
A02: Cryptographic FailuresJWT 구조 노출, JWKS 에러RSA Private Key, DB 암호화 키, Admission Key 전체 노출
A04: Insecure DesignQueue Token 예측, Workers 무료 플랜 DoSThread Pool 무제한, 단순 DB 비밀번호
A05: Security MisconfigurationArgoCD 무인가, Grafana 외부 노출, dev SwaggerStaging Cloudflare 미적용, K8s API 외부 노출
A06: Vulnerable ComponentsGrafana 미패치 CVE 3건
A07: Auth FailuresOAuth redirect_uri 미검증Gmail 앱 비밀번호, OAuth Secret 노출
A08: Software Integrity무인증 경기/구장 생성JWT 위조, S3 Defacement

A01(Broken Access Control)과 A02(Cryptographic Failures)가 두 플랫폼 모두에서 가장 심각했다. OWASP Top 10에서 1위와 2위가 그대로 나온 셈이다.


핵심 교훈

1. 인증이랑 인가는 다르다

Target A는 인증(Authentication)은 있었다. 토큰 발급도 하고, JWT 서명도 RS256으로 제대로 하고 있었다. 근데 인가(Authorization)가 통째로 빠져있었다. 토큰의 role이 MEMBER든 ADMIN이든 상관없이 모든 API에 접근 가능했다.

Spring Security에서 @PreAuthorize("hasRole('ADMIN')")를 안 달아놓으면 이런 일이 발생한다. 인증만 하고 인가를 빼먹는 건, 신분증은 확인하면서 출입증은 안 보는 것과 같다.

2. .env를 GitHub에 올리면 끝난다

Target B의 모든 공격은 GitHub에 올라간 .env 파일 하나에서 시작됐다. RSA Private Key, AWS 키, OAuth Secret, DB 비밀번호, Gmail 비밀번호... 전부 한 파일에 들어있었다.

.gitignore.env를 추가하는 건 기본 중의 기본인데, 이미 한 번 커밋된 파일은 .gitignore에 추가해도 git history에 남아있다. git filter-branch나 BFG Repo-Cleaner로 history까지 완전히 삭제해야 한다.

그리고 가능하면 시크릿은 파일에 넣지 말고 AWS Secrets Manager나 HashiCorp Vault 같은 시크릿 관리 서비스를 써야 한다.

3. Staging도 프로덕션처럼 보호해야 한다

"개발 환경이니까 괜찮겠지"는 가장 위험한 가정이다. Target B의 Staging은 Cloudflare가 없어서 ALB가 직접 노출되어 있었고, 거기서 Actuator, Swagger, loadtest API가 전부 열려있었다. Staging에서 확보한 정보로 prod 공격 전략을 세울 수 있다.

4. 단일 방어선은 방어가 아니다

Target B의 prod는 Cloudflare IP 화이트리스트가 유일한 방어선이었다. 이게 뚫리면 DB 비밀번호 1234로 뚫린다. 그리고 실제로 Staging이라는 우회 경로가 있었다.

Defense in Depth(심층 방어) — 방어를 여러 계층으로 쌓아야 한다. WAF, 네트워크 ACL, 인증, 인가, 시크릿 관리 각각이 독립적으로 방어할 수 있어야 하나가 뚫려도 다음 계층이 막는다.

5. 프론트엔드 번들에 비즈니스 로직 넣지 마라

JS 번들은 클라이언트에서 실행되는 코드다. 아무리 빌드하고 난독화해도, 결국 브라우저에서 돌아가는 코드라 전부 읽을 수 있다. API 엔드포인트, OAuth Client ID, 토큰 생성 로직 같은 게 다 들어있으면 공격자한테 로드맵을 제공하는 거나 마찬가지다.

특히 Queue Token의 JTI를 클라이언트에서 생성하는 건 서버 측 검증이 없다면 대기열 우회로 직결된다.


마치며

72시간은 짧다. 인프라 구축, 정찰, 공격, 보고서까지 혼자 다 해야 하니까 시간 관리가 진짜 중요했다. 특히 Day 3에 IP 차단당해서 VPN 전환하느라 시간 날린 거랑, 인증 서비스가 500 에러 내면서 토큰 재발급이 안 됐던 게 아쉽다. 토큰만 살아있었으면 Target A에서 더 많은 걸 할 수 있었다.

그래도 2개 플랫폼에서 65건을 찾아낸 건 꽤 만족스러운 결과다. 특히 Target B에서 .env 하나로 전체 인프라를 장악하는 공격 체인을 완성한 게 이번 훈련의 하이라이트였다.

앞으로 개선하고 싶은 점이라면:

  • 자동화 스크립트를 좀 더 미리 준비해갈 것
  • VPN IP 관리를 처음부터 신경쓸 것 (Day 1부터 로테이션)
  • C2 인프라를 웹 앱 공격에 더 활용할 방법 찾기

이 글에 포함된 모든 공격은 인가된 보안 훈련 환경에서 수행되었다.
도메인, IP, 크리덴셜, 계정 정보 등 민감 정보는 전부 마스킹 처리했다.

출처: 클라우드 기반 72시간 사이버보안 훈련 (2026)

Tags: #레드팀 #침투테스트 #펜테스트 #보안 #OWASP #AWS #Kubernetes #JWT #OAuth #사이버보안

profile
nyo님 좋아합니다!

0개의 댓글