← Blog

평범한 사람인 내가 1류 해커를 따라해서 CVE를 발급 받은 경험(부제:야, 너두 할 수 있어)

들어가며

저는 지극히 평범한 사람입니다.

컴퓨터과학 전공이긴 하지만, 졸업 전까지 보안 관련은 경험은 Zero, 작년 이맘때(25년 4월)만 해도 KISA에 운 좋게 Stored XSS 하나 제보한 게 전부였습니다.

그런데 AI를 활용해서 주간 다운로드 5천만 회의 npm 패키지(TanStack Query)와 Apache Airflow 같은 거대 오픈소스에서 취약점을 발견하고, 실제로 CVE를 발급받았습니다.

CVE ID 대상 취약점 심각도
CVE-2026-26903 TanStack Query DoS via Unbounded Recursion Medium
CVE-2026-25604 Apache Airflow AWS Auth Manager Host Header Injection Medium

이 글은 나도 할 수 있을까? 고민하시는 분들께 야, 너두 할 수 있어 라고 말씀드리고 싶어서 쓰게 되었습니다.


시작: 인터넷에서 오아시스를 발견하다

시작은 윤석찬님의 블로그 글이었습니다.

$5짜리 프롬프트로 $2,418짜리 취약점 찾은 썰

이 글을 정말 n번은 정독했습니다. 이해가 안 되면 Claude한테 읽히고 설명시키고를 반복

이 자리를 빌려 윤석찬님께 진심으로 감사의 말씀을 드립니다.


난 무엇을, 어떻게 했나?

도구: Claude Pro + Claude API

특별한 도구는 없었습니다. Claude Pro 구독과 Claude API, 이 두 가지만 사용했습니다.

Step 1: 스캐너 만들기

윤석찬님의 글을 토대로 Claude에게 취약점 스캐너를 만들라고 지시했습니다. 처음엔 단순한 구조(v1)로 시작해서 오탐(False Positive)을 줄여가며 v3까지 발전시켰습니다.

여기서 놀라운 것은 Claude가 스스로 버전을 업그레이드 시킨다는 것입니다. v1의 결과를 계속 LLM에 던져주면 “현재 탐지 로직을 이렇게 수정 하면 좀 더 비용적으로나 오탐 비율을 개선할 수 있습니다” 하면서 v2, v3까지 업그레이드 됐습니다.

Claude가 스스로 만든 v1 ~ v3 내용입니다.

v1: “그냥 버그 찾아줘” — 순진했던 첫 번째

“너는 보안 연구원이야. 코드에서 보안 취약점을 찾아.”

구조도 간단했습니다:

  1. 리포를 git clone
  2. 소스 파일을 ~25K 토큰 단위로 잘라서
  3. 각 청크를 프롬프트 하나와 함께 Claude에게 전송
  4. XML 결과를 파싱

작동은 했습니다 — 기술적으로는요. 문제는? 뭐든 다 보고한다는 거였습니다. command 파라미터를 받는 라이브러리 함수? “Command Injection!” 파일 경로를 받는 개발자용 API? “Path Traversal!” DB URL을 설정하는 설정 옵션? “SSRF!”

테스트 삼아 몇 개 리포에 돌려봤더니 수백 개의 발견 사항이 쏟아졌습니다. 거의 다 쓰레기였습니다. 오탐률(False Positive Rate)이 95%를 넘었을 겁니다.

이때 Claude는 깨달았습니다. 스캐너에 이 함수를 누가 호출하는가? 라는 개념 자체가 없었던 겁니다. 모든 파라미터를 공격자가 직접 입력한 것처럼 취급하고 있었습니다.


v2: 다단계 파이프라인 — 회의론자 추가

v2의 핵심 통찰은 이것이었습니다: 버그를 찾기만 하지 말고, 반증도 해봐라.

스캐너를 다단계 파이프라인으로 재구성했습니다:

  • Phase 1 — 정찰(Reconnaissance): 바로 취약점 분석에 뛰어들지 않고, 먼저 공격 표면을 매핑합니다. HTTP 핸들러가 어디 있는지, eval()이나 exec() 같은 위험한 싱크가 어디 있는지, 어떤 파일이 깊이 분석할 가치가 있는지 파악합니다.
  • Phase 2 — 정밀 분석(Deep Analysis): 정찰에서 플래그된 파일만 분석합니다. 프롬프트에서 구체적인 공격 벡터를 요구합니다 — 어떻게 익스플로잇하는지 단계별로 설명 못 하면 보고하지 마라.
  • Phase 3 — 적대적 검증(Adversarial Validation): 이게 진짜 게임 체인저였습니다. 각 발견 사항을 Claude에게 다른 페르소나로 다시 보냅니다: “너는 회의적인 보안 리뷰어야. 네 임무는 이 발견 사항을 반증하는 거야.” 입력값이 정말 사용자가 제어 가능한 건지, 새니타이제이션이 있는지, 이 코드 경로가 프로덕션에서 도달 가능한 건지 검증합니다.

파일 우선순위 점수 시스템도 추가했습니다. HTTP 라우트 핸들러(app.get(), @PostMapping)나 사용자 입력 패턴(req.body, request.form)이 있는 파일의 우선순위를 높이고, 테스트 파일이나 설정 파일, 개발 전용 코드는 자동으로 필터링했습니다.

오탐률이 눈에 띄게 줄었습니다. 하지만 v2로는 해결할 수 없는 근본적인 문제가 하나 남아 있었습니다.


v3: 신뢰 경계 분석(Trust Boundary Analysis) — 돌파구

v2를 돌리면서 계속 같은 패턴의 오탐을 봤습니다:

“이 라이브러리에 execute(query)라는 함수가 있는데 파라미터화 없이 SQL을 실행합니다 — SQL Injection!”

근데 이 함수는 라이브러리 API입니다. 이 라이브러리를 import하는 개발자가 뭘 전달할지 선택하는 겁니다. 개발자는 신뢰할 수 있는 주체입니다. 이건 취약점이 아니라 기능입니다.

npm 패키지와 Java 라이브러리를 스캔할 때 이게 가장 큰 오탐 원인이었습니다. 그래서 Phase 0: 대상 분류(Target Classification) 를 만들었습니다.

스캔을 시작하기 전에 v3는 먼저 이 질문에 답합니다: “이 코드베이스는 뭔가?”

대상을 네 가지 신뢰 모델로 분류합니다:

유형 누가 신뢰되는가? 실제 취약점이란?
APPLICATION 사용자는 비신뢰 HTTP 입력 → 위험한 싱크
LIBRARY 개발자는 신뢰 개발자 개입 없이 외부 데이터가 싱크에 도달하는 경우만
FRAMEWORK 플러그인 개발자는 신뢰 프레임워크 코어가 사용자 입력을 잘못 처리하는 경우만
CLI_TOOL 혼합 처리하는 파일, 네트워크 응답

스캐너는 package.json 필드(main, module, exports → 라이브러리), 코드 패턴(app.get(), req.body → 애플리케이션), 구조적 지표를 보고 유형을 자동 감지합니다.

그 후 모든 후속 단계에서 이 신뢰 컨텍스트를 사용합니다. 정찰 프롬프트는 라이브러리와 애플리케이션에 대해 다른 질문을 합니다. 정밀 분석 프롬프트에는 신뢰 모델별 전용 규칙이 있습니다. 그리고 결정적으로 — 라이브러리 대상에서 requires_malicious_developer: true로 플래그된 발견 사항은 자동으로 필터링됩니다.

두 개의 CVE를 찾아낸 건 바로 이 버전이었습니다. 신뢰 경계 필터가 노이즈를 제거하니, 비신뢰 사용자 입력이 실제로 위험한 싱크에 도달하는 진짜 취약점들이 명확하게 드러났습니다.


실제로 어떻게 만들었나

코드 대부분을 제가 직접 짠 게 아닙니다.

제 워크플로우는 이랬습니다:

  1. 윤석찬님의 원래 접근 방식을 완전히 이해할 때까지 읽기
  2. 원하는 것을 Claude에게 상세하게 설명 — 아키텍처, 단계, 프롬프트
  3. 결과물 검토 — 여기서 제 보안 지식이 중요했습니다
  4. 실제 리포에서 테스트하고 실패 원인 분석
  5. 실패 사례를 다시 Claude에게 피드백: “이건 오탐이야. 왜 이런 일이 생긴 거야? 어떻게 방지하지?”
  6. 반복 — v1 → v2 → v3 각각은 이전 버전이 왜 실패했는지 이해하는 데서 탄생했습니다

여기서 핵심은 버전 업그레이드를 Claude가 제시할 때 이게 합리적인지 아닌지를 판단하는 것이라고 생각합니다. 만약 버전 업그레이드를 그냥 Claude가 그런가 보다 해서 무작정 했다면 스캐너는 오히려 성능이 안좋아질 수 있었겠죠?

Step 2: 스캔-> 검토-> 제출

스캐너를 v3까지 완성하면서 결과를 토대로 리포트를 만들고 제출하였습니다.

Spring, npm 등 유명한 오픈소스들을 다운로드 수 순으로 정렬한 다음, 하나하나 git clone → 스캔을 반복했습니다. 총 100개 이상의 리포지토리를 스캔했고, 26년 1월 한 달을 통째로 투자했습니다.

스캐너가 뱉어낸 결과(XML)는 바로 리포트로 쓸 수 없었습니다. 해당 결과와 실제 소스코드를 함께 Claude에게 던져서 오탐인지 유효한 취약점인지 최종 검토시켰습니다. 자동화된 결과를 맹신하지 않고, LLM을 2차 검증관으로 활용한 셈이죠.

최종적으로 유효하다고 판단된 건에 대해서는 리포트를 작성해 제출했습니다. 리포트 작성도 다른 사람들의 CVE 리포트를 Claude에게 학습시켜서 작성했고, 증적(PoC 스크린샷, 코드) 이외의 부분은 거의 Claude가 작성하고 저는 검토만 했습니다.

결과

  • 스캔한 리포: 100개 이상
  • 제출한 리포트: 4건
  • 인정받은 CVE: 2건
  • 소요 기간: 약 1개월 (26년 1월)
  • API 비용: 약 7만원 (Claude Pro 구독 비용 별도)

100개 넘는 리포를 스캔해서 4개를 제출하고, 그중 2개가 인정받았습니다. 생각보다 확률이 매우 낮습니다.


Insight: AI를 잘 쓰는 사람이 되자

저는 AI가 나오기 이전에 CVE를 찾아본 경험이 없는 사람입니다. 그래서 이전 분들이 도대체 어떤 사고 과정으로 취약점을 찾는지 감도 못 잡았습니다.

그런데 大AI시대에는 저 같은 평범한 사람도 CVE를 발급받을 수 있다는 것을 직접 증명했습니다.

솔직히 기분이 좋기도 하고, 이제 막 신입인데 AI로 대체될까 봐 두렵기도 합니다.

하지만 한 달간 이 작업을 하면서 확실히 느낀 점이 있습니다.

AI도 만능이 아닙니다. 사용자의 능력만큼만 발휘됩니다.

질문이 바보 같으면 대답도 바보같고, 똑똑하게 하면 똑똑한 답변이 돌아옵니다. 아마 AI를 써보신 분들이라면 모두 공감하실 거라고 생각합니다.

결국 AI는 도구이고, 그 도구를 잘 쓰는 사람이 되는 것이 중요하다고 생각합니다.


마치며

저는 어쩌다 보니 해킹/보안을 남들보다 조금 늦게 시작한 사람입니다. 졸업하고 나서야 본격적으로 시작했으니까요.

작년 이맘때(25년 4월)만 해도 KISA에 운으로 Stored XSS 하나 제보한 것 외에는 별다른 성과가 없었습니다. 그 후로 제야의 고수들을 직접 찾아뵙고 스터디하며, 버그바운티에도 조금씩 성과를 이루었습니다.

올해 연초(26년 1월) 목표는 기필코 CVE를 발급받겠다! 였고, 어찌저찌 우당탕탕 하다보니 얻어 걸리는거 같습니다.

이 글이 저 같이 평범한 사람들에게 조금이나마 도움이 되었으면 합니다.

여기까지 읽어주셔서 감사합니다.


관련 링크