클라우드 보안 사고, 대부분은 '설정 실수'에서 시작된다 — CSPM 실전 점검법
이 글을 끝까지 읽으면, 해커보다 무서운 '내 손가락 한 번의 실수'를 어떻게 찾아내고 막는지 정확히 알게 됩니다. Capital One·Toyota·Microsoft 실제 사고 분석부터 바로 돌려볼 수 있는 점검 코드 3종까지 한 번에 정리했습니다.
안녕하세요. 오랜 기간 개발과 보안 현장을 누벼온 ICT리더 리치입니다. 클라우드 보안 컨설팅을 나가면 제가 가장 먼저 여는 화면은 화려한 위협 탐지 대시보드가 아닙니다. S3 버킷 목록, 보안 그룹 인바운드 규칙, 그리고 IAM 액세스 키 생성일입니다. 이 세 화면만 10분 훑어봐도 그 조직의 보안 수준이 거의 보이거든요.
솔직히 말씀드리면, 현장에서 마주친 클라우드 사고의 상당수는 '최신 해킹 기법' 때문이 아니었습니다. "테스트하려고 잠깐 열어둔" 포트, "공유하려고 잠깐 만든" 공개 링크, "나중에 지우려던" 루트 액세스 키. 바로 이 '잠깐'이 몇 년씩 방치되면서 사고가 됩니다.
오늘은 클라우드 설정 실수(Misconfiguration)가 왜 사고 원인 1순위인지 최신 통계로 확인하고, 세계적인 기업들이 실제로 어떻게 당했는지 사례 3+1개를 해부한 뒤, 이를 자동으로 잡아내는 CSPM(Cloud Security Posture Management) 점검법을 실전 코드와 함께 정리합니다. 클라우드 담당자, 개발자, 보안 담당자 모두 오늘 바로 써먹을 수 있도록 구성했습니다.
📌 바로가기 목차
| CSPM을 활용해 잘못된 클라우드 설정을 발견하고 조치로 연결하세요. |
1. 왜 '설정 실수'가 클라우드 사고 1순위인가 — 2026 통계로 본 현실
"클라우드로 옮기면 AWS나 Azure가 알아서 지켜주는 것 아닌가요?" 고객사 임원분들께 정말 자주 듣는 질문입니다. 결론부터 말하면, 아닙니다. Gartner는 클라우드 보안 실패의 99%가 고객 책임이 될 것이라고 전망했고, 클라우드 보안 연합(CSA)의 'Top Threats to Cloud Computing 2024' 보고서도 500명이 넘는 전문가 의견을 모아 '설정 실수와 부적절한 변경 관리'를 클라우드 위협 1위로, IAM(신원·접근 관리)을 2위로 꼽았습니다.
2026년 데이터도 크게 다르지 않습니다. Red Hat의 2026 클라우드 네이티브 보안 현황 보고서에 따르면 응답 조직의 97%가 지난 1년간 최소 한 번 이상 보안 사고를 겪었고, 가장 흔한 유형이 바로 잘못 구성된 인프라·서비스(78%)였습니다. 보고서는 그 원인을 정교한 공격이 아니라 일상적인 운영 실수로 짚었습니다.
멀티클라우드는 문제를 더 키웁니다. Intruder가 3,000개 조직 데이터를 분석한 '2026 Cloud Security Index'를 보면 AWS·Azure·Google Cloud에서 가장 흔한 설정 실수 항목이 서로 거의 겹치지 않았습니다. 같은 '공개 스토리지' 위험이라도 클라우드마다 용어·설정 위치·교정 방법이 다르다는 뜻이죠. 한 클라우드에서 익힌 감각이 다른 클라우드에서는 통하지 않습니다.
| 지표 | 수치 | 출처 |
|---|---|---|
| 클라우드 보안 실패 중 고객 책임 비율(전망) | 99% | Gartner |
| 클라우드 위협 순위 | 1위 설정 실수 / 2위 IAM | CSA Top Threats 2024 |
| 최근 1년 클라우드 네이티브 보안 사고 경험 조직 | 97% | Red Hat 2026 보고서 |
| 가장 흔한 사고 유형 — 인프라·서비스 설정 오류 | 78% | Red Hat 2026 보고서 |
| 클라우드 사고의 초기 침투 수단 — 유효 계정 악용 | 35% | CrowdStrike 2025 위협 헌팅 보고서 |
마지막 숫자가 특히 의미심장합니다. 공격자가 '해킹해서 들어오는' 것이 아니라 '로그인해서 들어오는' 비중이 커지고 있다는 뜻이거든요. MFA 없는 계정, 유출된 액세스 키, 너무 넓은 권한 — 이 모두가 결국 설정 문제입니다. 그렇다면 이 설정은 누가 책임져야 할까요? 다음 섹션에서 정리합니다.
2. 공유 책임 모델 — 클라우드 사업자가 지켜주지 않는 영역
클라우드 보안의 출발점은 '공유 책임 모델(Shared Responsibility Model)'입니다. 한 줄로 요약하면 이렇습니다. 사업자는 '클라우드 자체(of the cloud)'를, 고객은 '클라우드 안에 올린 것(in the cloud)'을 책임진다.
데이터센터 출입 통제, 물리 서버, 하이퍼바이저는 AWS·Azure·Google이 지킵니다. 하지만 버킷을 공개로 둘지, 보안 그룹에 0.0.0.0/0을 열지, 누구에게 어떤 IAM 권한을 줄지는 전적으로 우리가 결정합니다. 사업자는 우리가 내린 그 결정을 '정확히' 실행해 줄 뿐입니다. 공개하라고 설정했다면, 아주 성실하게 전 세계에 공개해 줍니다.
| 보안 영역 | IaaS (EC2·VM) | PaaS (RDS·App Service) | SaaS (M365·Salesforce) |
|---|---|---|---|
| 데이터·분류·암호화 정책 | 고객 | 고객 | 고객 |
| 계정·IAM·MFA | 고객 | 고객 | 고객 |
| 네트워크 접근 규칙 | 고객 | 고객 | 공동 |
| OS·미들웨어 패치 | 고객 | 사업자 | 사업자 |
| 물리 인프라·하이퍼바이저 | 사업자 | 사업자 | 사업자 |
ℹ️ 참고: 표에서 보듯 '데이터'와 '계정·권한'은 어떤 서비스 모델에서도 100% 고객 책임입니다. SaaS로 갈수록 관리 부담이 줄어든다고 생각하기 쉽지만, 가장 치명적인 두 영역은 끝까지 우리 몫입니다.
이론은 여기까지입니다. 이 경계를 놓친 대가가 실제로 얼마나 컸는지, 세계적인 기업들의 사례로 확인해 보겠습니다.
3. 실전 사례 해부 — Capital One·Toyota·Microsoft, 그리고 Snowflake
아래 사례들의 공통점을 먼저 말씀드리겠습니다. 어느 것도 클라우드 사업자의 플랫폼 취약점이 근본 원인이 아니었습니다. 모두 '고객 쪽 설정'에서 시작됐고, 모두 CSPM 점검 한 번이면 미리 잡을 수 있었던 항목입니다.
📍 사례 ① Capital One (2019) — 잘못 설정된 WAF + 과도한 IAM 권한
미국 대형 금융사 Capital One은 AWS 환경에서 운영하던 오픈소스 웹 방화벽(WAF)의 설정 오류로 약 1억 명 규모의 고객 정보가 유출되는 사고를 겪었습니다. 공격자는 SSRF(서버 측 요청 위조) 기법으로 WAF 서버가 EC2 인스턴스 메타데이터 서비스에 요청을 보내게 만들었고, 그 응답으로 WAF에 붙어 있던 IAM 역할의 임시 자격 증명을 손에 넣었습니다.
진짜 문제는 그다음이었습니다. 그 역할에 필요 이상으로 넓은 S3 접근 권한이 붙어 있었던 거죠. 공격자는 이 자격 증명으로 버킷 목록을 조회하고 데이터를 그대로 동기화해 갔습니다. AWS도 공식 입장에서 SSRF 자체보다 방화벽 설정 오류와 과도한 권한을 핵심 요인으로 지목했습니다.
- 설정 실수: WAF 설정 오류 + WAF 역할에 과도한 S3 권한 부여
- CSPM이 잡았을 항목: IMDSv1 허용 인스턴스, 와일드카드(s3:*) 권한 역할
- 교훈: 이후 AWS는 토큰 기반 IMDSv2를 내놓았고, 지금은 '모든 인스턴스 IMDSv2 강제'가 기본 점검 항목입니다.
📍 사례 ② Toyota (2023 공개) — 10년 가까이 아무도 몰랐던 공개 설정
Toyota는 2023년 5월, 클라우드 환경 설정 오류로 일본 내 약 215만 명 고객의 차량 데이터가 2013년 11월부터 2023년 4월까지 외부에서 접근 가능한 상태였다고 발표했습니다. T-Connect, G-Link 등 커넥티드 서비스 이용자의 차량 위치 정보와 시간 정보, 내비게이션 단말 ID, 차대번호 등이 포함됐고, 차량 외부 촬영 영상도 노출 가능성이 언급됐습니다.
보도에 따르면 원인은 인적 오류로 클라우드 시스템이 비공개가 아닌 공개로 설정된 것이었습니다. Toyota는 데이터 취급 규칙에 대한 설명과 철저함이 부족했다고 인정하며, 클라우드 설정 감사 체계와 지속적인 설정 모니터링을 도입하겠다고 밝혔습니다. 정확히 CSPM의 정의 그 자체입니다.
- 설정 실수: 클라우드 스토리지를 비공개가 아닌 공개로 설정
- CSPM이 잡았을 항목: 퍼블릭 스토리지 탐지, 계정 단위 퍼블릭 차단 미설정
- 교훈: 무서운 건 '실수'가 아니라 '10년간 아무도 몰랐다'는 사실입니다. 가시성 부재가 실수를 재앙으로 키웁니다.
📍 사례 ③ Microsoft AI 연구팀 (2023) — 공유 링크 하나에 38TB
클라우드 보안 기업 Wiz 연구팀은 Microsoft AI 연구 부서가 GitHub에 오픈소스 학습 데이터를 공유하면서, 의도와 달리 38TB 규모의 내부 데이터까지 노출한 사실을 발견했습니다. 여기에는 직원 두 명의 워크스테이션 백업, 비밀번호와 비밀 키, 3만 건이 넘는 내부 Teams 메시지가 포함돼 있었습니다.
원인은 Azure Storage의 공유 기능인 SAS 토큰이었습니다. 특정 파일만 공유하도록 범위를 좁힐 수 있었지만, 실제 링크는 스토리지 계정 전체를 대상으로 만들어졌고, 읽기뿐 아니라 쓰기까지 가능한 권한에 만료일은 무려 2051년이었습니다. Wiz는 공격자가 이 저장소의 AI 모델에 악성 코드를 심을 수도 있었다고 경고했습니다. 데이터 유출을 넘어 '공급망 공격'의 통로가 될 뻔한 것이죠.
- 설정 실수: 계정 전체 범위 + 쓰기 권한 + 사실상 무기한 SAS 토큰
- CSPM이 잡았을 항목: 장기 만료 SAS, Account SAS 사용, 저장소 내 비밀정보
- 교훈: '설정'은 콘솔에만 있지 않습니다. 코드 저장소에 박힌 링크와 키도 설정의 일부입니다.
📍 보너스 사례 ④ Snowflake 고객 계정 (2024) — MFA 없는 계정이 문을 열었다
2024년에는 데이터 클라우드 플랫폼 Snowflake를 쓰는 여러 고객사 계정이 연쇄적으로 침해됐습니다. 조사 결과 공격자는 플랫폼 취약점이 아니라 정보 탈취 악성코드로 유출된 자격 증명을 사용해 로그인했고, 피해 계정 상당수가 MFA를 설정하지 않은 상태였습니다. 앞서 본 '해킹이 아닌 로그인'의 대표 사례입니다. 이후 Snowflake는 MFA 강제 정책을 강화했습니다.
⚠️ 주의: 네 사례 모두 '보안 제품이 없어서'가 아니라 '이미 있는 설정을 아무도 점검하지 않아서' 발생했습니다. 여러분 조직에도 지금 이 순간 "잠깐 열어둔" 설정이 남아 있을 가능성이 매우 높습니다.
![]() |
| 발견한 위험에 담당자와 처리 기한을 지정하고 교정 후 다시 확인하세요. |
4. CSPM이란? — 반드시 점검해야 할 설정 실수 TOP 10
CSPM(Cloud Security Posture Management)은 클라우드 계정 전체의 설정을 지속적으로 스캔해 보안 기준(CIS 벤치마크 등)과 어긋난 부분을 찾아내고, 우선순위를 매기고, 가능하면 자동으로 고치는 체계를 말합니다. 쉽게 말해 '클라우드 설정 건강검진'을 매일 자동으로 받는 것이죠.
저는 고객사에 처음 들어가면 아래 10가지부터 확인합니다. 순서는 '사고 났을 때 피해가 큰 순'입니다. 이 10개만 정리해도 앞에서 본 사례 네 건은 모두 예방할 수 있었습니다.
- ① 루트·관리자 계정 MFA: 루트 계정 MFA 필수, 루트 액세스 키는 존재 자체가 위험. 모든 사람 계정에 피싱 저항형 MFA 적용.
- ② 퍼블릭 스토리지 차단: S3 계정 단위 퍼블릭 액세스 차단 4개 옵션 ON, Azure Blob 익명 접근 비활성화, GCS 균일 버킷 수준 접근 사용.
- ③ 관리 포트 인터넷 공개 금지: 22(SSH)·3389(RDP)·DB 포트의 0.0.0.0/0 개방 제거. Session Manager·Bastion·VPN 경유로 전환.
- ④ IMDSv2 강제: 모든 EC2와 신규 인스턴스 기본값을 토큰 필수 모드로. Capital One 유형 공격의 핵심 차단점.
- ⑤ 과도한 IAM 권한 정리: "Action": "*", "Resource": "*" 정책 제거. 90일 이상 사용 안 한 권한은 회수.
- ⑥ 장기 액세스 키 제거: 90일 넘은 키 교체, 가능하면 IAM 역할·OIDC 연동 임시 자격 증명으로 전환.
- ⑦ 감사 로그 상시 활성화: 멀티 리전 CloudTrail / Azure Activity Log / GCP Audit Log를 별도 보안 계정에 불변 저장.
- ⑧ 저장 데이터 암호화: EBS 기본 암호화 ON, 민감 데이터는 고객 관리형 키(CMK)로 키 회전 관리.
- ⑨ 공유 링크·토큰 수명 관리: SAS·Presigned URL은 최소 범위·최소 기간. 코드 저장소에 비밀정보 커밋 금지(시크릿 스캐닝).
- ⑩ 위협 탐지 서비스 활성화: GuardDuty / Defender for Cloud / Security Command Center를 전 리전에서 켜고 알림을 실제 담당자에게 연결.
💡 실전 팁: 10개를 한 번에 다 하려다 아무것도 못 하는 조직을 많이 봤습니다. 이번 주에는 ①②③ 세 가지만 하세요. 이 셋이 실제 대형 사고의 대부분을 차지하는 '초기 진입로'입니다.
5. CSPM 도구 비교 — 오픈소스부터 클라우드 네이티브까지
"그래서 어떤 도구를 써야 하나요?" 다음으로 많이 받는 질문입니다. 정답은 조직 규모와 클라우드 구성에 따라 다르지만, 제 경험상 순서는 거의 같습니다. 무료 오픈소스로 현황을 먼저 파악하고, 클라우드 네이티브 서비스로 상시 모니터링을 붙이고, 멀티클라우드가 복잡해지면 통합 플랫폼(CNAPP)을 검토하는 순서입니다.
| 구분 | 대표 도구 | 장점 | 한계 |
|---|---|---|---|
| 오픈소스 | Prowler, Checkov(IaC), ScoutSuite | 무료, CIS 벤치마크 내장, CI/CD 연동 쉬움 | 상시 운영·대시보드는 직접 구축 |
| AWS 네이티브 | Security Hub, Config, IAM Access Analyzer | 조직(Organizations) 단위 일괄 적용, 자동 교정 연계 | AWS 전용 |
| Azure 네이티브 | Microsoft Defender for Cloud | 보안 점수 제공, AWS·GCP 커넥터 지원 | 고급 기능은 유료 플랜 |
| GCP 네이티브 | Security Command Center | 조직 전체 자산 인벤토리·노출 분석 | 고급 기능은 상위 티어 |
| 상용 CNAPP | Wiz, Prisma Cloud, Orca 등 | 멀티클라우드 통합, 공격 경로 분석 | 비용 부담, 도입·튜닝 필요 |
💡 실전 팁: 도구보다 중요한 건 '누가, 며칠 안에 고치느냐'입니다. CSPM 경고가 수천 건 쌓여 아무도 안 보는 조직이 정말 많습니다. CRITICAL은 72시간, HIGH는 14일처럼 심각도별 처리 기한(SLA)부터 정하세요.
6. 실전 코드 3종 — 점검·교정·유출 탐지 자동화
이제 직접 손을 움직여 보겠습니다. 상용 도구 없이도 오늘 당장 돌려볼 수 있도록 '점검 → 교정 → 유출 방지' 흐름으로 코드 3개를 준비했습니다. 모두 실제 동작하는 코드이며, 앞에서 본 사례별 교훈을 그대로 점검 항목에 반영했습니다.
▶ 실전 코드 ① — AWS 미니 CSPM 스캐너 (Python · 읽기 전용)
boto3로 루트 MFA, 오래된 액세스 키, 퍼블릭 S3, IMDSv1 허용 인스턴스, 위험 포트 공개, CloudTrail 로깅 여부를 한 번에 점검합니다. 결과는 심각도순으로 출력하고 JSON 리포트로 저장하며, CRITICAL이 하나라도 있으면 종료 코드 1을 반환해 CI/CD 파이프라인에서 배포를 막을 수 있습니다.
#!/usr/bin/env python3
# [실전 코드 ①] AWS 미니 CSPM 스캐너 — 설정 실수 7종 읽기 전용 점검
# 필요 권한: AWS 관리형 정책 SecurityAudit (읽기 전용)
# ※ 보안: 관리자(Admin) 키로 실행하지 말 것. 점검 도구도 최소 권한 원칙 적용
# 설치: pip install boto3 실행: python3 cspm_scan.py
import sys
import json
import datetime
import boto3
from botocore.exceptions import ClientError
# 인터넷(0.0.0.0/0)에 열려 있으면 안 되는 관리·DB 포트
RISKY_PORTS = {22: "SSH", 3389: "RDP", 3306: "MySQL", 5432: "PostgreSQL",
6379: "Redis", 9200: "Elasticsearch", 27017: "MongoDB"}
KEY_MAX_AGE_DAYS = 90
findings = []
def add(severity, resource, issue, fix):
findings.append({"severity": severity, "resource": resource,
"issue": issue, "fix": fix})
# ① 루트 계정 MFA·액세스 키 점검
def check_root(iam):
s = iam.get_account_summary()["SummaryMap"]
if s.get("AccountMFAEnabled", 0) != 1:
add("CRITICAL", "root", "루트 계정 MFA 미설정", "루트 계정에 하드웨어/패스키 MFA 즉시 등록")
if s.get("AccountAccessKeysPresent", 0) > 0:
add("CRITICAL", "root", "루트 계정 액세스 키 존재", "루트 키 삭제 후 IAM 역할 기반으로 전환")
# ② 90일 넘은 IAM 액세스 키 점검 (장기 자격 증명 = 유출 시 피해 최대)
def check_old_keys(iam):
now = datetime.datetime.now(datetime.timezone.utc)
for page in iam.get_paginator("list_users").paginate():
for user in page["Users"]:
keys = iam.list_access_keys(UserName=user["UserName"])["AccessKeyMetadata"]
for k in keys:
age = (now - k["CreateDate"]).days
if k["Status"] == "Active" and age > KEY_MAX_AGE_DAYS:
add("HIGH", f"iam-user:{user['UserName']}",
f"액세스 키 {age}일 경과 ({k['AccessKeyId'][:8]}****)",
"키 교체 또는 IAM Identity Center·역할 기반 임시 자격 증명으로 전환")
# ③ 계정 단위 + 버킷 단위 S3 퍼블릭 노출 점검 (Toyota·Microsoft 사례 교훈)
def check_s3(account_id):
s3control = boto3.client("s3control")
try:
cfg = s3control.get_public_access_block(AccountId=account_id)["PublicAccessBlockConfiguration"]
if not all(cfg.values()):
add("HIGH", f"account:{account_id}", "계정 단위 S3 퍼블릭 차단 일부 해제", "4개 옵션 모두 true")
except ClientError as e:
if e.response["Error"]["Code"] == "NoSuchPublicAccessBlockConfiguration":
add("HIGH", f"account:{account_id}", "계정 단위 S3 퍼블릭 차단 미설정", "4개 옵션 모두 true")
else:
raise
s3 = boto3.client("s3")
for b in s3.list_buckets()["Buckets"]:
name = b["Name"]
try:
status = s3.get_bucket_policy_status(Bucket=name)["PolicyStatus"]
if status.get("IsPublic"):
add("CRITICAL", f"s3:{name}", "버킷 정책이 퍼블릭(누구나 접근)", "버킷 정책 Principal \"*\" 제거")
except ClientError as e:
# 정책이 없는 버킷은 정상 — 그 외 오류만 기록
if e.response["Error"]["Code"] not in ("NoSuchBucketPolicy", "AccessDenied"):
add("INFO", f"s3:{name}", f"점검 실패: {e.response['Error']['Code']}", "권한/리전 확인")
# ④ IMDSv1 허용 인스턴스 (Capital One 사례 교훈) + ⑤ 위험 포트 전체 공개
def check_ec2(region):
ec2 = boto3.client("ec2", region_name=region)
for page in ec2.get_paginator("describe_instances").paginate():
for r in page["Reservations"]:
for inst in r["Instances"]:
if inst["State"]["Name"] == "terminated":
continue
if inst.get("MetadataOptions", {}).get("HttpTokens") != "required":
add("HIGH", f"ec2:{region}/{inst['InstanceId']}",
"IMDSv1 허용 — SSRF로 IAM 임시 자격 증명 탈취 가능",
"http-tokens=required 로 IMDSv2 강제")
for page in ec2.get_paginator("describe_security_groups").paginate():
for sg in page["SecurityGroups"]:
for perm in sg.get("IpPermissions", []):
open_world = any(r.get("CidrIp") == "0.0.0.0/0" for r in perm.get("IpRanges", [])) or \
any(r.get("CidrIpv6") == "::/0" for r in perm.get("Ipv6Ranges", []))
if not open_world:
continue
if perm.get("IpProtocol") == "-1":
add("CRITICAL", f"sg:{region}/{sg['GroupId']}", "모든 포트가 인터넷에 공개", "출발지 CIDR 최소화")
continue
lo, hi = perm.get("FromPort", 0), perm.get("ToPort", 65535)
for port, svc in RISKY_PORTS.items():
if lo <= port <= hi:
add("CRITICAL", f"sg:{region}/{sg['GroupId']}",
f"{svc}({port}) 포트 인터넷 공개", "SSM Session Manager·VPN 경유로 전환")
# ⑥ CloudTrail 멀티 리전 로깅 여부 (Toyota 사례: 10년간 아무도 몰랐다)
def check_cloudtrail():
ct = boto3.client("cloudtrail")
trails = ct.describe_trails(includeShadowTrails=False)["trailList"]
ok = any(t.get("IsMultiRegionTrail") and
ct.get_trail_status(Name=t["TrailARN"]).get("IsLogging") for t in trails)
if not ok:
add("CRITICAL", "cloudtrail", "멀티 리전 CloudTrail 로깅 없음", "조직 단위 멀티 리전 트레일 생성")
def main():
account_id = boto3.client("sts").get_caller_identity()["Account"]
iam = boto3.client("iam")
print(f"[+] 계정 {account_id} 점검 시작 (읽기 전용)")
check_root(iam)
check_old_keys(iam)
check_s3(account_id)
check_cloudtrail()
regions = [r["RegionName"] for r in boto3.client("ec2").describe_regions()["Regions"]]
for region in regions:
check_ec2(region)
order = {"CRITICAL": 0, "HIGH": 1, "INFO": 2}
findings.sort(key=lambda f: order.get(f["severity"], 9))
for f in findings:
print(f"[{f['severity']:8}] {f['resource']:45} {f['issue']} → {f['fix']}")
stamp = datetime.datetime.now().strftime("%Y%m%d_%H%M")
with open(f"cspm_report_{stamp}.json", "w", encoding="utf-8") as fp:
json.dump(findings, fp, ensure_ascii=False, indent=2)
print(f"[+] 총 {len(findings)}건 — JSON 리포트 저장 완료")
# CI/CD 파이프라인 연동: CRITICAL 발견 시 종료 코드 1 → 배포 차단
sys.exit(1 if any(f["severity"] == "CRITICAL" for f in findings) else 0)
if __name__ == "__main__":
main()
💡 실전 팁: 이 스캐너는 AWS 관리형 정책 SecurityAudit(읽기 전용)만 붙인 전용 역할로 실행하세요. 점검 도구에 관리자 권한을 주는 순간, 점검 도구 자체가 가장 위험한 공격 대상이 됩니다. 매일 새벽 정기 실행으로 걸어두면 '10년간 아무도 몰랐던' Toyota 같은 상황을 하루 안에 잡을 수 있습니다.
![]() |
| 계정과 공개 노출부터 암호화와 위협 탐지까지 주요 클라우드 설정을 점검하세요. |
▶ 실전 코드 ② — 설정 실수 자동 교정 스크립트 (Bash · AWS CLI)
점검만 하고 고치지 않으면 의미가 없습니다. 이 스크립트는 계정 단위 S3 퍼블릭 차단, 전 리전 IMDSv2 강제와 신규 인스턴스 기본값 설정, EBS 기본 암호화를 일괄 적용합니다. 기본값은 변경 내용만 보여주는 DRY-RUN이고, 모든 실행 내역은 감사 로그 파일로 남습니다.
#!/usr/bin/env bash
# [실전 코드 ②] 설정 실수 자동 교정 스크립트 — 기본값은 DRY-RUN(미리보기)
# 사용법: ./cloud_hardening.sh → 변경 내용만 출력
# ./cloud_hardening.sh apply → 실제 적용
# ※ 보안: 운영 계정 적용 전 반드시 스테이징 계정에서 먼저 검증할 것
set -euo pipefail
MODE="${1:-dry-run}"
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
LOG="hardening_$(date +%Y%m%d_%H%M%S).log"
# 모든 변경 명령은 run()을 거치게 해 감사 로그를 남긴다
run() {
if [[ "$MODE" == "apply" ]]; then
echo "[APPLY] $*" | tee -a "$LOG"
"$@"
else
echo "[DRY-RUN] $*" | tee -a "$LOG"
fi
}
echo "== 계정 ${ACCOUNT_ID} / 모드: ${MODE} ==" | tee -a "$LOG"
# 1) 계정 전체 S3 퍼블릭 액세스 차단 — 버킷 하나하나 실수해도 계정 레벨에서 막힌다
run aws s3control put-public-access-block \
--account-id "$ACCOUNT_ID" \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
# 2) 활성화된 모든 리전 순회
for region in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do
echo "-- 리전: ${region}" | tee -a "$LOG"
# 2-1) IMDSv1 허용 인스턴스 → IMDSv2 강제 (SSRF 자격 증명 탈취 차단)
ids=$(aws ec2 describe-instances --region "$region" \
--filters Name=metadata-options.http-tokens,Values=optional \
Name=instance-state-name,Values=running,stopped \
--query 'Reservations[].Instances[].InstanceId' --output text)
for id in $ids; do
run aws ec2 modify-instance-metadata-options --region "$region" \
--instance-id "$id" --http-tokens required --http-endpoint enabled
done
# 2-2) 앞으로 생성될 인스턴스의 기본값도 IMDSv2로 고정
run aws ec2 modify-instance-metadata-defaults --region "$region" --http-tokens required
# 2-3) 신규 EBS 볼륨 기본 암호화
run aws ec2 enable-ebs-encryption-by-default --region "$region"
# 2-4) SSH/RDP 전체 공개 보안 그룹은 '자동 삭제' 대신 '보고'만 — 서비스 중단 방지
aws ec2 describe-security-groups --region "$region" \
--filters Name=ip-permission.cidr,Values=0.0.0.0/0 \
--query 'SecurityGroups[?IpPermissions[?FromPort==`22` || FromPort==`3389`]].[GroupId,GroupName]' \
--output text | while read -r gid gname; do
[[ -n "${gid:-}" ]] && echo "[REVIEW] ${region} ${gid} (${gname}) — SSH/RDP 인터넷 공개, 수동 검토 필요" | tee -a "$LOG"
done
done
echo "== 완료. 감사 로그: ${LOG} ==" | tee -a "$LOG"
⚠️ 주의: IMDSv2 강제 전에는 구버전 AWS SDK·에이전트를 쓰는 애플리케이션이 메타데이터 호출에 실패하지 않는지 반드시 확인하세요. 또한 SSH/RDP 공개 보안 그룹은 일부러 '자동 삭제'하지 않고 '검토 대상'으로만 출력하도록 설계했습니다. 운영 중인 접속 경로를 갑자기 끊으면 그 자체가 장애가 됩니다.
▶ 실전 코드 ③ — 저장소 비밀정보·SAS 토큰 유출 탐지기 (Python)
Microsoft 38TB 사례를 그대로 재현 점검하는 코드입니다. 코드 저장소를 훑어 Azure SAS URL을 찾아내고, 계정 전체 범위(Account SAS) 여부·쓰기/삭제 권한·만료 기간을 해석해 위험을 알려줍니다. AWS 액세스 키 패턴도 함께 탐지하며, 키 값은 로그에서도 마스킹합니다.
#!/usr/bin/env python3
# [실전 코드 ③] 저장소 내 클라우드 비밀정보 유출 탐지기
# Azure SAS 토큰(만료·권한·범위) + AWS 액세스 키 패턴을 커밋 전에 잡아낸다
# Microsoft 38TB 노출 사례(2051년 만료 Account SAS)를 재현 점검하는 용도
# 실행: python3 secret_guard.py ./my-repo (pre-commit 훅으로 등록 권장)
import os
import re
import sys
import datetime
from urllib.parse import urlsplit, parse_qs
SAS_URL = re.compile(
r"https://[a-z0-9]+\.(?:blob|file|queue|table|dfs)\.core\.windows\.net/[^\s\"'<>]*\?[^\s\"'<>]*sig=[^\s\"'<>]+")
AWS_KEY = re.compile(r"\b(AKIA|ASIA)[0-9A-Z]{16}\b")
MAX_SAS_DAYS = 7 # 사내 정책: SAS 유효기간 최대 7일
SKIP_DIRS = {".git", "node_modules", ".venv", "__pycache__"}
today = datetime.date.today()
def inspect_sas(url):
"""SAS 토큰 쿼리 파라미터를 해석해 위험 요소를 목록으로 반환"""
q = {k: v[0] for k, v in parse_qs(urlsplit(url).query).items()}
risks = []
if "ss" in q or "srt" in q:
risks.append("Account SAS — 스토리지 계정 전체 범위 (서비스 SAS로 축소 필요)")
perms = q.get("sp", "")
if any(p in perms for p in "wdc"):
risks.append(f"쓰기/삭제 권한 포함(sp={perms}) — 공급망 변조 위험")
exp = q.get("se", "")[:10]
try:
days = (datetime.date.fromisoformat(exp) - today).days
if days > MAX_SAS_DAYS:
risks.append(f"만료까지 {days}일 남음(se={exp}) — 정책 {MAX_SAS_DAYS}일 초과")
except ValueError:
risks.append("만료일(se) 해석 불가 — 수동 확인")
return risks
def scan(root):
hits = 0
for dirpath, dirnames, filenames in os.walk(root):
dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]
for fn in filenames:
path = os.path.join(dirpath, fn)
try:
with open(path, encoding="utf-8", errors="ignore") as fp:
for no, line in enumerate(fp, 1):
for m in SAS_URL.finditer(line):
risks = inspect_sas(m.group(0))
hits += 1
print(f"[SAS] {path}:{no}")
for r in risks or ["위험 요소 없음 — 그래도 코드 저장소 보관은 비권장"]:
print(f" - {r}")
for m in AWS_KEY.finditer(line):
hits += 1
# 로그에도 키 전체를 남기지 않도록 마스킹
print(f"[AWS] {path}:{no} {m.group(0)[:8]}************ — 즉시 비활성화·교체")
except (OSError, UnicodeError):
continue
return hits
if __name__ == "__main__":
target = sys.argv[1] if len(sys.argv) > 1 else "."
found = scan(target)
print(f"\n[+] 탐지 {found}건")
sys.exit(1 if found else 0) # 탐지 시 커밋/배포 차단
💡 실전 팁: 이 스크립트를 pre-commit 훅이나 CI 첫 단계에 넣어 두면 비밀정보가 저장소에 들어오기 전에 차단됩니다. 이미 커밋된 키라면 파일에서 지우는 것만으로는 부족합니다. Git 히스토리에 남아 있으므로 키를 즉시 비활성화하고 새로 발급하는 것이 원칙입니다. 전체 코드와 테스트 케이스는 GitHub 저장소를 참고하세요.
7. 30일 CSPM 도입 로드맵
"좋은 건 알겠는데 어디서부터 시작하죠?" 현업에서 실제로 적용해 효과를 본 30일 로드맵을 공유합니다. 핵심은 '완벽한 설계'보다 '빠른 가시화'입니다. 무엇이 열려 있는지 모르면 아무것도 지킬 수 없으니까요.
| 기간 | 목표 | 핵심 실행 항목 |
|---|---|---|
| 1주차 | 현황 가시화 | 실전 코드 ① 또는 Prowler로 전 계정 스캔, 클라우드 계정·자산 목록 확정 |
| 2주차 | 치명적 항목 제거 | 루트 MFA, 퍼블릭 스토리지, 관리 포트 공개 등 CRITICAL 전량 조치 |
| 3주차 | 재발 방지 가드레일 | 실전 코드 ② 적용, SCP·Azure Policy로 위험 설정 자체를 금지 |
| 4주차 | 상시 운영 체계 | 네이티브 CSPM 상시화, 실전 코드 ③ CI 연동, 심각도별 SLA·담당자 지정 |
- ☑ 사람이 아닌 정책이 막게 하라: "공개 버킷 만들지 마세요"라는 공지보다 계정 단위 차단 설정 한 줄이 훨씬 강력합니다.
- ☑ IaC 단계에서 먼저 잡아라: Terraform·CloudFormation 코드를 Checkov로 검사하면 잘못된 설정이 배포되기 전에 걸러집니다.
- ☑ 예외는 기한을 붙여 승인하라: 불가피하게 공개가 필요하면 사유·담당자·만료일을 태그로 남기고 만료 시 자동 재점검.
💡 실전 팁: 경영진 보고에는 "발견 건수"가 아니라 "CRITICAL 0건 유지 일수"와 "평균 조치 시간"을 쓰세요. 숫자가 줄어드는 그래프 하나가 다음 해 보안 예산을 지켜줍니다.
| 클라우드 보안은 접근 권한과 서비스 설정을 지속적으로 확인하는 데서 시작합니다. |
8. 자주 묻는 질문 (FAQ)
CSPM은 클라우드 '설정'을 점검하고, CWPP는 VM·컨테이너 같은 '워크로드 내부'(취약점, 런타임 위협)를 보호합니다. CNAPP은 여기에 IaC 점검·권한 분석(CIEM) 등을 묶은 통합 플랫폼입니다. 처음이라면 CSPM부터 시작하는 것이 가장 효과 대비 비용이 좋습니다. 사고의 출발점이 대부분 설정이기 때문입니다.
오히려 작은 조직일수록 필요합니다. 보안 전담 인력이 없으니 사람이 일일이 콘솔을 확인할 수 없거든요. 무료인 Prowler나 본문의 실전 코드 ①을 주 1회만 돌려도 출발점으로 충분합니다. 투자 유치나 대기업 납품 시 보안 실사에서도 큰 도움이 됩니다.
2026년 조사에서도 클라우드별로 흔한 설정 실수가 거의 겹치지 않는다는 결과가 나왔습니다. 그래서 '하나의 공통 정책 + 클라우드별 점검 규칙' 구조가 필요합니다. Prowler처럼 AWS·Azure·GCP를 함께 지원하는 도구로 기준을 통일하고, 규모가 커지면 통합 CNAPP을 검토하세요.
맞습니다, 무분별한 자동 교정은 장애를 부릅니다. 저는 '되돌려도 서비스 영향이 없는 항목'(퍼블릭 차단, 기본 암호화, 신규 인스턴스 IMDSv2 기본값)만 자동화하고, 접속 경로나 권한 변경은 승인 후 적용하도록 나눕니다. 실전 코드 ②가 DRY-RUN을 기본값으로 둔 이유도 같습니다.
네. 개인정보보호법은 안전성 확보 조치를 요구하고, ISMS-P 인증 심사에서도 클라우드 접근 통제·로그 관리·암호화가 주요 점검 대상입니다. 설정 실수로 개인정보가 노출되면 '해킹 피해자'가 아니라 '관리 소홀'로 판단될 수 있습니다. 정기 CSPM 리포트는 그 자체로 관리 노력을 입증하는 증적이 됩니다. 더 궁금한 점은 댓글로 남겨주세요!
9. 마무리 요약
✅ 클라우드 보안의 적은 해커보다 '방치된 설정'입니다
Gartner의 99% 전망, CSA 위협 순위 1위, Red Hat 조사의 78%. 숫자는 모두 같은 방향을 가리킵니다. Capital One은 과도한 권한, Toyota는 공개 설정, Microsoft는 무기한 공유 토큰, Snowflake 고객사는 MFA 부재에서 무너졌습니다. 어느 것도 첨단 해킹이 아니었습니다.
해법도 명확합니다. 공유 책임 모델을 이해하고, CSPM으로 설정을 매일 자동 점검하고, 사람이 아닌 정책으로 위험한 설정 자체를 막는 것. 오늘 소개한 실전 코드 3종으로 지금 바로 첫 스캔을 시작해 보세요.
오늘 딱 한 가지만 실천해 보세요 — 우리 회사 클라우드 계정의 루트 MFA와 S3 계정 단위 퍼블릭 차단 설정을 확인하는 것. 5분이면 충분하고, 그 5분이 Toyota의 10년을 막을 수 있습니다. 여러분 조직에서는 어떤 설정 실수를 가장 자주 발견하시나요? 댓글로 공유해 주시면 함께 고민해 드리겠습니다. 다음 포스팅에서는 AWS·Azure·GCP 보안 설정 완전 비교 — 멀티클라우드 핵심 점검 포인트를 다룰 예정이니 기대해 주세요!


댓글
댓글 쓰기