본문으로 건너뛰기
>_ITDITD웹 보안 플랫폼

보안 가이드

AI가 작성한 코드에서 API 키가 유출돼 부정 과금이 발생했다 — 진짜 원인은 방치된 CVSS 10.0

인디 개발자가 실제로 자주 겪는 패턴: AI의 도움으로 출시한 앱에서 API 키가 탈취되어 낯선 대량 작업으로 부정 과금이 쌓인다. 엉뚱한 범인을 의심한 우회로, 진짜 원인 — 몇 달 묵은 공개 CVSS 10.0 RCE — 그리고 재발을 막는 법을, 공격 절차 없이 방어 관점으로 풀어낸다.

게시 2026-06-07 5분 읽기

보안에 익숙하지 않은 인디 개발자가 흔히 겪는 사례를, 식별 가능한 세부 정보를 모두 제거하고 교훈으로 바꾼 글입니다. 여기에 공격 재현은 없습니다 — 목표는 방어이며, 같은 실수가 당신에게 일어나지 않게 하는 것입니다.

사례 파일 — INCIDENT FILE
분류
API 키 유출 / 부정 과금
심각도
크리티컬 (공개된 CVSS 10.0이 악용됨)
근본 원인
웹 프레임워크의 인증 전(pre-auth) RCE가 몇 달간 패치되지 않은 채 방치됨
유출 범위
환경의 모든 시크릿(키링 전체)
실행 범위
권한 없는 컨테이너 내부에 머무름(호스트 root는 무사)
영구 조치
①패치 버전으로 업그레이드 ②모든 키 교체 ③기계 CVE 모니터링
10.0
CVSS / 최악 등급
몇 달
패치되지 않은 기간
전체 환경
가정한 유출 범위
모니터링
진짜 예방책

“과금을 멈췄다” ≠ “처리했다”

청구를 멈추는 것은 응급 처치다. 유출 경로를 닫는 것은 별개의 작업이다. 둘 다 끝냈을 때 비로소 완료된 것이다.

무슨 일이 있었나 (타임라인)

  1. 0일째 — 출시·가동

    AI의 도움으로 만든 앱이 출시되어 프로덕션에서 가동 중이었다.
  2. 어느 날 — 요금 급증

    클라우드 AI 요금이 갑자기 치솟는다. 결코 쓸 일 없는 모델에서 대량 작업이, 자기 것이 아닌 일을 하고 있었다.
  3. 조사 — 제3자의 악용

    "우리 앱이 폭주했다"로는 설명되지 않는다. 제3자가 탈취한 키로 그것을 돌리고 있었다.
  4. 추적 — 진짜 조사의 시작

    "그럼 키는 어디서 유출됐나?" — 그것이 진짜 조사였다.

처음에 잡히지 않은 이유 (우회로들)

실수가 저질러진 순서대로 — 실수가 곧 교훈이기 때문이다.

1

성급하게 범인을 단정

첫 가정: "우리 폴백 코드가 폭주했다." 하지만 데이터(실제로 처리된 내용)는 그것일 수 없음을 분명히 했다.
→ "내 잘못"이나 "남의 잘못"을 정하기 전에, 먼저 데이터를 보라.
2

깨끗한 grep이 안도를 줄 뻔했다

코드, git 히스토리, 연결된 저장소를 모두 검색 → 어디에도 평문 키 없음. "추적 불가"로 거의 포기할 뻔했다. 파일만 응시하다 런타임 유출을 놓쳤다.
3

증상을 가리는 것을 고친 것으로 착각

프록시에서 증상을 가리니 거의 끝난 듯 느껴졌다. 하지만 가린다고 RCE는 살아남는다. 근본을 고치기 전까지 구멍은 열려 있다.

깨끗한 grep은 안전 확인이 아니다

어떤 파일에도 키가 없더라도, 취약점은 실행 중인 프로세스에서 환경 변수를 빼낼 수 있다. 유출은 파일뿐 아니라 런타임(RCE, HTTP 헤더)에서도 일어난다. RCE란 무엇인가 참고.

진짜 원인: 방치된, 공개된 CVSS 10.0

오래된 액세스 로그의 이상한 시그니처가 위협 인텔리전스와 즉시 일치했다. 사용 중이던 웹 프레임워크 버전 범위에는 공개된 인증 전 RCE(CVSS 10.0) 가 있었고, 이미 실제로 악용되고 있었으며 — 몇 달간 패치되지 않은 채 가동되고 있었다.

  • 이것은 수동적인 버그가 아니라, 공격자가 서버에서 코드를 실행해 환경을 빼낸 것이었다.
  • 그나마 다행인 점: 실행은 권한 없는 컨테이너 내부에 머물렀다(호스트 root는 탈취되지 않았다). 침해 검토 결과 지속형 백도어, 마이너, C2는 없었고 — 확인된 피해는 시크릿 탈취였다.
  • 내부 DB에 도달 가능했기 때문에, 대응은 DB 내용도 유출됐다고 가정했다.
보이는 증상: 갑작스럽고 낯선 청구서(빙산의 일각)
수면선: 눈에 보이는 전부
↑ 탈취된 키는 나중에 재판매·악용된다
환경 전체(모든 시크릿)가 빠져나간다
↑ 공격자가 가동 중인 서버에서 임의 코드를 실행한다
근본 원인: 방치된 공개 CVSS 10.0(인증 전 RCE)
갑작스러운 청구서는 빙산의 일각이다. 수면 아래에는 진짜 원인 — 방치된, 공개된 CVSS 10.0 — 이 있다.

교훈: 무언가 이상하게 동작하면, 먼저 알려진 CVE를 의심하라. 자기 버그라고 단정하지 마라.

개수도 틀렸다 — 가동 중인 버전으로 판단하라

조사가 번지며 "취약한 의존성이 8개 더 있다"고 보고됐다 — 이것도 틀렸다. package.json하한값(^ 캐럿 범위)으로 센 것이었다. 진짜 위험한 것은 남아 있던 고정(pinned) 의존성 2개뿐이었다.

자신의 가동 중인 버전을 확인하라

락파일이나 가동 중인 컨테이너에서, 실제로 해석된 버전을 보라.

# npm: 실제로 설치된 것을 확인
npm ls next react react-dom
 
# 가동 중인 컨테이너 안에서
docker exec <container> npm ls <package>

여기 나오는 숫자가 진실이다 — package.json^가 아니라.

가장 먼저 한 일 (재현 가능한 절차)

1

악용된 키를 즉시 폐기

응급 처치 — 일의 일부일 뿐이다.
2

프레임워크를 패치 버전으로 업그레이드

실제로 RCE를 닫는다. 업그레이드 후 로그에서 유출 시그니처가 사라졌는지 확인한다.
3

모든 시크릿 교체

유출 가정. "어디서든 악용 가능한 것부터" 순서로: 오브젝트 스토리지 키 → 세션 서명 키 → API 키 → DB 자격증명.
4

침해 검토

지속성, 악성 cron, 외부 C2가 있는지 점검(여기서는 없음).
5

계정 측 방어

비밀번호 변경, MFA 활성화, 낯선 API 키가 있는지 확인.

진짜 예방책: 기계가 감시하게 하라

요약하면, 이 사고는 "사람이 공개된 CVSS 10.0을 놓쳤다"였다. 수동 순찰은 늘 무언가를 놓친다. 기계는 그렇지 않다.

오늘 시작할 수 있는 의존성 스캔

무료로 시작 — CI에 한 단계.

# OSV 스캐너(Google)가 락파일을 점검한다
osv-scanner --lockfile=pnpm-lock.yaml
 
# GitHub에서는 Dependabot을 활성화(저장소 Settings → Security)

본 사이트 역시 이 교훈에 따라 자신의 의존성을 CVE 모니터링한다 — 권하는 것을 직접 실천한다.

교훈

흔한 실수

  • "과금을 멈췄다"에서 완료로 친다
  • grep이 깨끗하다고 안심한다
  • 프록시에서 증상을 가리고 고쳤다고 느낀다
  • 취약점을 package.json 하한으로 센다
  • 악용이 확인된 키 하나만 교체한다

올바른 방어

  • 응급 처치와 "유출 경로 차단"을 별개 작업으로 보고, 둘 다 한다
  • 런타임 유출(RCE, 헤더)도 의심한다
  • 근본(프레임워크)을 갱신한다
  • 실제로 가동 중인 버전으로 판단한다
  • 환경이 유출되면 환경 전체를 교체한다

한 줄로: 폭주한 청구서는 빙산의 일각이었다. 진짜 원인은 방치된 CVSS 10.0 RCE였고 — 진실은 운 좋은 한 번의 추측이 아니라, 틀리고, 부딪치고, 오류를 깎아내는 과정에서 나왔다.

다음으로 읽기

FAQ

QAPI 키가 유출됐다면, 악용된 그 하나의 키만 폐기하면 충분한가요?
A

아니요. 유출 경로가 런타임(RCE나 헤더 유출)에 있다면 환경의 모든 시크릿이 한꺼번에 유출됐다고 가정하고 전부 교체하세요. 악용된 것을 본 키는 빙산의 일각일 뿐입니다.

Q코드와 git 전체를 grep 해도 키가 나오지 않으면 안전한가요?
A

반드시 그렇지는 않습니다. 어떤 파일에도 키가 없더라도, 프레임워크 취약점이 실행 중인 프로세스에서 환경 변수를 직접 빼낼 수 있습니다. 유출은 파일뿐 아니라 런타임에서도 일어납니다.

Q이 일이 다시 일어나지 않게 하는 가장 확실한 방법은 무엇인가요?
A

의존성을 기계로 CVE 모니터링하는 것입니다. 여기서의 근본 원인은 사람이 공개된 CVSS 10.0을 몇 달간 놓친 것이었습니다. Dependabot이나 osv-scanner가 그 빈틈을 구조적으로 메웁니다.