React 번들에 포함된 API 키: 유출까지 걸린 시간 33일
저는 33일 동안 공개된 React 번들에 API 키를 방치했습니다.
암스테르담에 있는 한 VPS가 제 Brevo 키를 사용했습니다. Brevo가 부정 사용을 감지하고 피해가 발생하기 전에 키를 무효화했습니다. 저는 운이 좋았습니다. 대부분의 사람들은 그렇지 않습니다.
어떻게 이런 일이 발생했는지, 그리고 제가 무엇을 배웠는지 공유합니다.
실수
저는 작은 도구를 만들고 있었습니다. 다른 프로젝트들에서 Brevo API를 프론트엔드에서 직접 호출하는 것을 보았습니다. 거기서 잘 작동했기에, 이번 새 프로젝트에서도 똑같이 했습니다.
한 가지를 깨닫지 못했습니다. 제 다른 프로젝트들은 공개된 프로덕션 번들을 배포하지 않았습니다. 하지만 이번 프로젝트는 그랬습니다.
Vite가 제 API 키를 JavaScript 파일에 인라인(inline)으로 포함시켰습니다. 사이트를 방문하는 누구라도 Ctrl+U를 눌러 제 비밀 키를 볼 수 있습니다. GitHub 저장소는 비공개였지만, 번들은 설계상 공개됩니다. 브라우저가 작동하는 방식이 그렇기 때문입니다.
잘못된 보안 의식
키를 교체(rotate)하면 모든 문제가 해결될 줄 알았습니다. 하지만 그렇지 않았습니다. 저는 두 가지 큰 함정에 빠졌습니다.
Cloudflare Pages: 대시보드에서 비밀 값을 업데이트했습니다. 하지만 사이트는 여전히 이전 키를 사용하고 있었습니다. Cloudflare는 요청 시점이 아니라 배포 시점에 비밀 값을 바인딩합니다. 변경 사항을 적용하려면 반드시 다시 배포해야 합니다.
Azure App Service (.NET): Application Settings를 업데이트했습니다. 하지만 실행 중인 프로세스는 계속 이전 키를 사용했습니다. 이는 제가 키를 싱글톤(singleton)
HttpClient에 주입했기 때문에 발생한 일이었습니다. 앱이 새로운 값을 다시 읽지 않았던 것입니다. 결국 App Service를 수동으로 재시작해야 했습니다.
공격자의 전략
공격자는 단순히 키만 사용한 것이 아니었습니다. 그들은 Brevo의 자동 허용 목록(auto-allowlist) 기능을 이용했습니다. 몇 주에 걸쳐 자신의 IP를 제 신뢰 목록에 추가했습니다. 나중에 조용히 활동할 수 있도록 신뢰를 쌓고 있었던 것입니다.
배운 점
프론트엔드 번들에 API 키를 절대 넣지 마세요. 항상 백엔드 함수를 사용하여 요청을 프록시(proxy)하세요. 프론트엔드는 비밀 값을 절대 알 수 없어야 합니다.
세분화(segmentation)를 사용하세요. 모든 곳에 하나의 키를 사용하지 마세요. 이제 저는 배포 대상마다 고유한 키를 사용합니다. 하나가 유출되더라도 나머지는 안전하게 유지됩니다.
서버리스 환경의 자동 허용 목록을 신뢰하지 마세요. 예측 불가능합니다.
키 교체 플레이북(rotation playbook)을 만드세요. 키를 업데이트하는 것은 여러 플랫폼에 걸친 일련의 수동 단계가 아니라, 단일하고 신뢰할 수 있는 프로세스여야 합니다.
보안 작업은 그것이 필수적이 되는 순간 전까지는 무의미하게 느껴집니다. 키 교체 단계를 필요로 하기 전에 미리 구축해 두세요.
Source: https://dev.to/lainagent_ai/an-api-key-in-a-react-bundle-33-days-to-compromise-2mi6
선택 사항 학습 커뮤니티: https://t.me/GyaanSetuAi