본문으로 건너뛰기
블로그로 돌아가기

목록에 없을 뿐, 비공개는 아닙니다

공개된 공격 표면 33곳을 전수 조사해 지금도 작동하는 머신 자격증명 23,912개를 발견했습니다. 각 키의 권한 범위를 등급으로 분류했고, 그중 1,277개는 키를 폐기해도 접근이 차단되지 않았습니다.

김동현
작성자
8 3,864 단어
Share:
목록에 없을 뿐, 비공개는 아닙니다

공개된 공격 표면 33곳을 전수 조사한 결과, 지금도 작동하는 머신 자격증명(Machine Credentials) 23,912개를 발견했습니다. 이번 조사 결과는 각 키가 가진 실제 권한 범위와 위험도를 등급별로 분류한 리포트이며 2026년 9월 10일 열린 제5회 CloudyMous 세미나에서 발표한 내용을 바탕으로 재정리했습니다. CloudyMous는 현업 보안 실무자가 마주한 위협과 대응 사례를 공유하는 정보보안 세미나입니다. 다음 회차 소식은 오픈채팅에서 받아보실 수 있습니다.

숨어있다고 생각한 곳이 다 열려 있었습니다

이번 조사에서 다룬 공격 표면은 대부분 '외부에 노출되지 않았다'고 무심코 넘어가는 영역들입니다. ChatGPT, Claude, Gemini, Grok의 공유 대화 링크 30,990건, GitHub 저장소의 .mcp.json 계열 에이전트 설정 파일, 그리고 AWS ECR Public의 컨테이너 이미지 레이어가 여기에 해당합니다.

이 세 곳의 공통점은 로그인 없이 누구나 내려받을 수 있다는 점입니다. 그리고 세 곳 모두에서 유효한 자격증명이 발굴되었습니다. 일례로 ECR 이미지 레이어에서는 조직 전체 러너(Runner)를 제어할 수 있는 GitHub PAT 5개가 그대로 노출되어 있었습니다.

많은 팀이 "검색 엔진에 인덱싱(목록화)되지 않았다"는 조건을 "접근이 제한되어 있다"는 보안 조건과 착각하곤 합니다. 하지만 이 둘은 다른 이야기입니다.

검증 방식과 선정 기준

사람이 로그인할 때 쓰는 계정과 달리, 코드가 시스템에 인증할 때 사용하는 API 키, 서비스 계정 토큰, OAuth 클라이언트 시크릿, DB 연결 문자열 등을 '머신 자격증명'으로 정의했습니다. 수집한 자격증명 후보는 각 발급처의 공식 엔드포인트로 보내 유효성을 검증했으며, 본문에서 언급하는 모든 수치는 이 유효 판정을 통과한 23,912개만을 대상으로 집계했습니다. 그중 권한 스코프까지 확인한 것은 7,468개이고, 뒤에 나오는 위험도 등급 분포는 이 7,468개를 모집단으로 합니다.

검증 질의는 모두 읽기 전용(Read-Only) 엔드포인트로만 보냈고 접근 체인을 추적하는 대신 스코프 문자열만 기록했습니다. 제3자 계정 ID와 IAM 사용자명은 기록 단계에서 마스킹했습니다. 이렇게까지 범위를 좁힌 데는 이유가 있습니다. 수집한 코퍼스 안에 카나리토큰(Canary Token) 3건이 섞여 있었습니다. 검증 질의를 보낸 그 순간 해당 토큰 소유자에게 알림이 발송되었습니다. 검증은 읽기 전용 엔드포인트에서 멈춰야 한다는 원칙을 실제 사례로 확인한 셈입니다.

공격 표면을 고를 때는 고정된 URL 목록 대신 '규칙'을 새로 정의했습니다. 새로운 AI 도구나 워크플로 도구가 자체 설정 파일 형식을 만들었을 때, 그 설정 파일의 내부 구조가 설계상 키를 담도록 되어 있는지를 기준으로 삼은 것입니다. 이에 따라 네 가지 계열이 핵심 표면으로 지정되었습니다.

  • 에이전트 설정. .mcp.json, .cursor/, .continue/, .aider.conf.yml. env 블록에 키 값을 그대로 기재하는 것이 파일 설계 자체입니다.
  • 워크플로 익스포트. n8n, Flowise, Dify, Langflow. 연결 정보를 JSON으로 그대로 내보냅니다.
  • 확장 패키지. VS Code .vsix, Chrome .crx. 계정 없이 내려받을 수 있고 스토어 검사가 번들 표면에서 멈춥니다.
  • AI 공유 링크. ChatGPT, Claude, Gemini, Grok. 대화에 입력한 값이 링크와 함께 공개됩니다.

유효 자격증명은 어디서 가장 많이 나왔나

절대적인 유효 건수로 따지면 GitHub 설정 파일이 20,777개로 가장 많았습니다. Google OAuth 클라이언트가 3,649개, GitHub 공개 저장소가 3,230개로 뒤를 이었습니다. 참고로 조사 대상 33곳 중 Common Crawl, Docker Hub, Terraform 모듈 등 11곳은 유효 건수가 0으로 나왔지만 대상에서 제외하지 않고 계속 모니터링했습니다.

다만 탐지 건수가 많다고 해서 반드시 더 위험하다고 볼 수는 없습니다. 건수 1위인 GitHub 설정 파일의 최고 심각도 사례는 북유럽 대형 보험사의 프로덕션 CMS 스페이스 16곳에 쓰기 권한이 열려 있던 건이었습니다. 반면 Supabase는 유효 건수가 643개로 규모는 작았지만 조직 관리 토큰만 670개가 발굴되는 등 CRITICAL 위험도 밀도가 가장 높았습니다. MCP나 에이전트 설정의 경우 유효 건수는 31개로 작았지만 불과 2년 전에는 아예 존재하지 않던 신종 위험 경로라는 점에서 주목할 필요가 있습니다.

발급처별로 보면 인증·결제·미디어 서비스가 상위권을 차지했습니다. Google OAuth가 7,143개로 가장 많았고, Razorpay(2,261개), RapidAPI(1,094개), MercadoPago(942개), Cloudinary(932개) 순이었습니다.

여기서 눈여겨볼 점은 전체 유효 자격증명 중 904개(3.8%)가 GitHub 계열 호스트 어디에도 존재하지 않았다는 사실입니다. 특히 그중 307개는 GitHub, GitLab, Bitbucket 그 어디에서도 발견되지 않았습니다. 즉, 기존처럼 소스코드 저장소만 스캐닝하는 보안 범위로는 잡을 수 없는 노출 자산들입니다. 공격자 입장에서는 코드 저장소 밖에서도 동일하게 수집이 가능하기 때문에, GitGuardian의 State of Secrets Sprawl 2026 리포트에서도 보안 사고의 28%가 저장소 밖에서 시작된다고 강조하는 것입니다.

위험도 등급과 "키 폐기"의 허점

위험도 분류 기준은 단순 건수가 아니라, 그 키를 확보한 공격자가 어디까지 접근할 수 있는가입니다.

키의 권한이 확인된 7,468건의 위험도 등급 분포. CRITICAL 계정 소유자 1,559건, CRITICAL 공급망 185건, HIGH 실제 자금 863건, HIGH 신원 사칭 256건, MEDIUM 사내 시스템 73건, LOW 비용 소진·미디어 4,532건

수치상으로는 LOW 등급이 4,532개로 가장 많고, CRITICAL 두 종류를 합치면 1,744개 수준입니다. 여기서 CRITICAL 판정의 핵심 기준은 권한 이름이 거창해서가 아니라 "이 키로 새 키를 재발급할 수 있는가"입니다. 즉, 원래 키를 폐기하더라도 공격자가 시스템 접근권을 유지할 수 있느냐의 차이입니다.

데이터베이스 연결 문자열은 총 6,042개가 확인되었습니다. 그중 mongodb.net 형태가 3,862개로 63.9%를 차지했습니다. mongodb+srv://user:pw@... 구조는 계정과 비밀번호를 분리해 담을 자리가 없기 때문에 한 문자열 안에 계정 정보 전체가 포함됩니다. CRITICAL 계정 소유자 등급의 다수가 DB 관리 권한으로 직행하는 이유가 여기에 있습니다.

보통 보안 사고가 터지면 대응 절차의 마지막으로 "해당 API 키를 폐기한다"를 택합니다. 하지만 이번 조사 결과 유효 자격증명 중 1,277개는 키 폐기만으로 접근을 차단할 수 없었습니다. 이 키들은 새 키를 발급할 수 있는 권한까지 갖고 있었기 때문입니다. Supabase 조직 관리 토큰 670개, MongoDB atlasAdmin 488개, SendGrid 키 생성 권한 122개 등이 해당합니다. 공격자가 키를 획득하자마자 백도어용 키를 새로 발급해 두었다면, 관리자가 기존 키를 폐기해도 공격자의 접근은 계속 유지됩니다.

서로 다른 서비스의 키가 한 파일 안에 얽혀 있던 건도 1,152개에 달했습니다. 중복을 제외하더라도 총 2,296개의 자격증명이 '단순 키 폐기 1회'로 해결되지 않는 구조였습니다.

유출 등록된 키가 버젓이 인증되는 기현상

알리바바 클라우드 키 172개를 검증하는 과정에서는 기이한 현상이 관찰되었습니다. 148개의 키에서 RAM 엔드포인트(GetAccountAlias)로 질의를 보내면 403 Forbidden과 함께 "유출 위험이 등록된 AccessKey"라는 응답이 돌아왔습니다. 이미 해당 키가 발급처 내부에서 유출된 것으로 식별되었다는 뜻입니다.

하지만 같은 키로 STS 엔드포인트(GetCallerIdentity)를 호출하자 200 OK 정상 응답이 돌아왔습니다. 유출을 탐지하는 내부 경로와 접근을 차단하는 IAM 경로가 분리 운영되고 있기 때문입니다. 결과적으로 시스템상 '유출로 등록된 키'임에도 불구하고 공격자는 같은 키로 계속해서 시스템 인증에 성공할 수 있었습니다.

이러한 유출 키 하나가 가져오는 파급력은 상당합니다.

유출 지점에서 권한을 거쳐 도달 범위까지 이어지는 측면이동 경로 다섯 가지. .mcp.json의 Supabase 조직 관리 토큰 670건은 조직 1,211곳과 프로젝트 2,607개에 도달하고 설정 파일의 Atlassian 관리 토큰 30건은 사이트 28곳과 Confluence 9곳으로 이어진다

.mcp.json에서 유출된 Supabase 관리 토큰 670개는 1,211개 조직, 2,607개 프로젝트 전체를 통제할 수 있었고, Atlassian 관리 토큰 30개는 사내 Confluence 위키로 직행했습니다. 사내 위키에 적힌 런북(Runbook) 안에는 또 다른 시스템들의 자격증명이 적혀 있습니다. 따라서 Atlassian 토큰 하나가 사내 전체 시스템의 자격증명을 수집하는 통로가 되는 셈입니다.

다만 이 도달 범위는 권한 스코프로 확인한 값이며 그 경로를 따라 접근을 시도하지는 않았습니다.

AI 도구가 더한 새로운 유출 경로와 .gitignore의 한계

최근 유출 트렌드는 AI 도구의 등장으로 인해 급격히 변화하고 있습니다. 기존 GitHub 저장소 내부지만 2년 전에는 없던 새로운 경로들이 생겨났습니다.

  • .mcp.json 308개
  • .cursor/mcp.json 302개
  • .vscode/mcp.json 144개
  • .gemini/settings.json 43개
  • .claude/settings.local.json 43개
  • .kiro/settings/mcp.json 31개
  • .kilocode/mcp.json 28개

AI 에이전트 설정 파일에서 나온 유효 키만 1,248개로 전체의 5.3%를 차지했습니다. 또한 AI/LLM 관련 API 키(Pinecone 609개, Tavily 567개, Context7 522개 등)가 2,998개(12.5%)나 발굴되며 유출되는 키의 종류 자체가 AI 생태계로 이동하고 있음을 보여주었습니다. 반면 AI 공유 대화 링크는 30,990건 중 자격증명이 포함된 비율이 0.384%(Wilson 95% 신뢰구간 0.321~0.459%)입니다. 화제성에 비해 실제 유출 비중은 작았습니다.

문제는 개발팀이 작성하는 .gitignore가 이 속도를 따라가지 못한다는 점입니다.

저장소 2,537개의 .gitignore 전수 확인 결과. .env를 제외한 비율 76.9%, 에이전트 디렉터리를 언급만 한 비율 62.3%, 유출 파일까지 덮은 비율 9.3%, 언급했으나 덮지 못한 비율 53.1%

전형적인 실수는 제외 규칙에 .claude/ 같은 디렉터리 이름만 적어두는 경우입니다. 실제 유출 파일명인 .claude/settings.local.json까지 덮지 못해 53.1%의 저장소가 설정 파일을 그대로 외부에 노출시켰습니다. 더욱이 유출 파일 4,049건 중 89.8%가 커밋이 올라온 당일에 공개되었습니다. 중앙값은 0일, 평균은 1.91일이었습니다. 게시되는 순간 수집봇이 가져가므로 .gitignore 규칙이 작동할 여유 시간 자체가 존재하지 않는 것입니다.

유출 파일 4,049건의 게시 시점 분포. 당일 게시가 89.8%인 3,636건이고 1일 4.8%, 2~7일 2.4%로 줄어듭니다. 중앙값 0일, 평균 1.91일

대응과 검증의 한계

조직 입장에서 노출을 차단하려면 다음 순서를 권합니다.

  1. `git log`로 이미 추적 중인 파일부터 확인합니다. .gitignore는 이미 커밋된 파일에 소급 적용되지 않습니다.
  2. 설정 파일 내 키 값을 환경변수 참조로 바꿉니다. 디렉터리 이름만 적고 실제 유출 파일을 빠뜨린 저장소가 53.1%였습니다.
  3. Key Rotation 시 재발급 권한을 함께 점검합니다. 단순 폐기에 그치지 않고, 그 키로 발급된 하위 계정이나 추가 키가 없는지 확인해야 합니다.
  4. 점검 범위를 Git 저장소 밖으로 넓힙니다. 패키지 레지스트리, 컨테이너 이미지 레이어, 확장 마켓플레이스가 대상입니다. 내부 접근 없이 공격자 시점에서 외부 자산을 훑는 외부 노출 스캐닝이 이 범위를 담당합니다.
  5. Pre-commit 게이트를 도입합니다. 유출의 89.8%가 게시 당일에 공개되는 만큼, 커밋 전 단계를 막아야 합니다.

조사 과정에서의 한계점도 명확했습니다. 유효성 질의 시 무효로 판정된 60,865건 중 공식적으로 '폐기됨'이 명시된 건은 12.4%(7,535건)에 불과했습니다. 나머지 75.3%는 원인을 알 수 없는 401/403 에러나 무응답이었습니다. 발급처가 키 무효화 원인을 응답에 담는지 여부도 향후 서비스 선택의 기준이 되어야 함을 시사합니다.

소유 조직을 특정하는 일은 더 어려웠습니다. 유효 자격증명 23,681개 중 발급처가 계정 메일 주소를 돌려준 것이 1,566개, 그 주소가 회사 도메인인 것이 309개였습니다. 여기에 네 단계 검증을 더 적용하자 최종 확인된 건은 3개까지 줄었습니다.

조직 귀속 검증 퍼널. 유효 자격증명 23,681건에서 계정 메일 반환 1,566건, 회사 도메인 309건, 네임스페이스 일치 25건을 거쳐 최종 3건까지 좁혀진다

가장 많이 탈락한 구간은 309개에서 25개로 줄어든 지점입니다. 회사 도메인 메일 주소는 그 계정을 만든 사람이 누구인지를 알려줄 뿐, 그 자산이 회사 소유라는 증거가 되지는 못합니다. 계정 설정에 적힌 회사명, 조직 이름, 워크스페이스 제목도 누구나 임의로 적을 수 있는 값이라 근거로 쓸 수 없습니다. 이 조사에서도 초안에는 "306개가 회사 자산이 아니었다"고 쓸 뻔했습니다. 검증을 통과하지 못한 것과 회사 자산이 아닌 것은 서로 다른 이야기입니다.

측정하지 못한 부분을 0으로 적지 않는다는 원칙으로 조사를 진행했기에 빈칸도 그대로 남겼습니다. 권한 스코프를 확인하지 못한 16,444개, 무효화 원인을 알 수 없는 75.3%가 남았습니다. 이 빈칸까지 채워 넣으면 숫자는 더 커지지, 작아지지는 않습니다.

이번 조사가 확인한 것은 하나입니다. 목록에 올라 있지 않다는 조건은 접근이 제한되어 있다는 조건과 다릅니다. 공유 링크도, 에이전트 설정 파일도, 컨테이너 이미지 레이어도 누구의 눈에도 띄지 않는다는 전제 위에서 운영되고 있었습니다. 그러나 로그인 한 번 없이 23,912개의 살아 있는 키를 내주고 있었습니다.

네트워크에 공유해보세요LinkedInX

이 글이 유익하셨나요?

네트워크에 공유해보세요

Share:

이어서 읽기

뉴스레터

다음 글을 메일로 받아보세요

Cremit 리서치 팀의 월간 NHI 브리프. 한 통에 핵심만 담습니다.

이메일을 판매하거나 외부에 넘기지 않으며, 언제든 수신거부할 수 있습니다.

유효 자격증명 23,912개의 영향 범위 | Cremit