기본 원리
클라우드 보안은 **공동 책임 모델(Shared Responsibility Model)**에 기반한다. 클라우드 제공자(AWS, GCP, Azure)는 인프라 보안을 책임지고, 사용자는 그 위에서 운영하는 데이터, 애플리케이션, 접근 제어를 책임진다.
┌─────────────────────────────────────────┐
│ 고객 책임 영역 │
│ 데이터, 애플리케이션, 접근 제어 │
│ 네트워크 설정, 운영체제 패치 │
├─────────────────────────────────────────┤
│ 클라우드 제공자 책임 │
│ 물리적 인프라, 하이퍼바이저 │
│ 네트워크 장비, 데이터센터 보안 │
└─────────────────────────────────────────┘
클라우드 보안 침해의 약 99%는 고객 설정 오류에서 비롯된다(Gartner). 유명 사례로는 Capital One 데이터 유출(2019, SSRF + 과도한 IAM 권한), 2억 명 이상의 개인정보가 담긴 S3 버킷 노출(Exactis 2018), Microsoft Power Apps 포털 오설정으로 3,800만 건 개인정보 노출(2021) 등이 있다.
취약점 1: S3 퍼블릭 버킷
문제 원인
AWS S3 버킷은 기본적으로 비공개이지만, 잘못된 버킷 정책이나 ACL 설정으로 누구나 접근 가능해질 수 있다.
# 퍼블릭 버킷 탐색 (공격자 관점)
# 버킷 이름 추측 - 기업명 + 일반적인 접미사
for name in backup logs data assets static media; do
bucket="company-name-$name"
response=$(curl -s -o /dev/null -w "%{http_code}" \
"https://$bucket.s3.amazonaws.com/")
if [ "$response" == "200" ] || [ "$response" == "403" ]; then
echo "Exists: $bucket (HTTP $response)"
fi
done
# AWS CLI로 버킷 내용 열람 (인증 불필요)
aws s3 ls s3://target-company-backup --no-sign-request
# 퍼블릭 버킷 파일 다운로드
aws s3 sync s3://target-company-backup ./loot --no-sign-request
// ❌ 취약한 버킷 정책 - 퍼블릭 읽기 허용
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*", // 모든 사람 허용
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::company-data/*"
}
]
}
// ✅ 안전한 버킷 정책 - 특정 IAM 역할만 허용
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/AppRole"
},
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::company-data/*",
"Condition": {
"Bool": {
"aws:SecureTransport": "true" // HTTPS만 허용
}
}
},
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::company-data",
"arn:aws:s3:::company-data/*"
],
"Condition": {
"Bool": {
"aws:SecureTransport": "false" // HTTP 차단
}
}
}
]
}
# S3 퍼블릭 액세스 차단 설정 (계정 수준)
aws s3api put-public-access-block \
--account-id 123456789012 \
--public-access-block-configuration \
"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
# 특정 버킷에 적용
aws s3api put-public-access-block \
--bucket company-data \
--public-access-block-configuration \
"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
# 버킷 암호화 강제
aws s3api put-bucket-encryption \
--bucket company-data \
--server-side-encryption-configuration '{
"Rules": [{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "arn:aws:kms:us-east-1:123456789012:key/..."
},
"BucketKeyEnabled": true
}]
}'
취약점 2: IAM 과도한 권한 (Overly Permissive IAM)
문제 원인
최소 권한 원칙(Principle of Least Privilege)을 무시하고 편의를 위해 과도한 권한을 부여하는 경우.
// ❌ 최악의 패턴: AdministratorAccess를 EC2 인스턴스에 부여
{
"Effect": "Allow",
"Action": "*", // 모든 AWS 서비스
"Resource": "*" // 모든 리소스
}
# IAM 권한 열거 (공격자가 EC2에 접근한 후)
# 현재 자격증명 확인
aws sts get-caller-identity
# 출력:
# {
# "UserId": "AROAEXAMPLE",
# "Account": "123456789012",
# "Arn": "arn:aws:iam::123456789012:assumed-role/EC2Role/i-1234567890"
# }
# 권한 열거 도구 사용
pip install enumerate-iam
python enumerate-iam.py \
--access-key <KEY> \
--secret-key <SECRET> \
--session-token <TOKEN>
# Pacu (AWS 침투 테스트 프레임워크)
# pacu> run iam__bruteforce_permissions
# 안전한 EC2 인스턴스 프로파일 - 최소 권한
# 특정 S3 버킷 읽기만 허용
aws iam create-policy --policy-name EC2MinimalPolicy --policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-app-bucket",
"arn:aws:s3:::my-app-bucket/*"
]
},
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue"
],
"Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:my-app/*"
}
]
}'
# Access Analyzer로 실제 사용된 권한 확인
aws accessanalyzer start-policy-generation \
--policy-generation-details '{"principalArn": "arn:aws:iam::123456789012:role/EC2Role"}' \
--cloud-trail-details '{
"accessRole": "arn:aws:iam::123456789012:role/AccessAnalyzerRole",
"startTime": "2026-04-01T00:00:00Z",
"endTime": "2026-05-01T00:00:00Z",
"trails": [{"cloudTrailArn": "arn:aws:cloudtrail:..."}]
}'
IAM 자격증명 유출 (하드코딩)
# ❌ 절대 금지: 코드에 자격증명 하드코딩
import boto3
# 이 코드가 GitHub에 푸시되면 수분 내 자동 스캔봇이 탐지
s3 = boto3.client(
's3',
aws_access_key_id='AKIAIOSFODNN7EXAMPLE',
aws_secret_access_key='wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
region_name='us-east-1'
)
# ✅ 올바른 방법 1: IAM Role (EC2/Lambda/ECS)
s3 = boto3.client('s3') # 인스턴스 메타데이터에서 자동으로 자격증명 획득
# ✅ 올바른 방법 2: 환경 변수 + AWS Secrets Manager
import os
from botocore.exceptions import ClientError
def get_secret(secret_name):
client = boto3.client('secretsmanager')
try:
response = client.get_secret_value(SecretId=secret_name)
return response['SecretString']
except ClientError as e:
raise e
db_password = get_secret('prod/myapp/db-password')
취약점 3: 메타데이터 서비스(IMDS) 악용
문제 원인
AWS EC2, GCP Compute Engine, Azure VM 등 모든 주요 클라우드는 인스턴스 메타데이터 서비스(IMDS)를 제공한다. 이 서비스는 169.254.169.254라는 링크-로컬 IP를 통해 IAM 자격증명 등 민감한 정보를 반환한다.
SSRF(Server-Side Request Forgery) 취약점이 웹 애플리케이션에 있으면, 공격자는 서버를 통해 IMDS에 접근해 IAM 자격증명을 탈취할 수 있다.
# EC2 인스턴스에서 메타데이터 접근 (공격자가 SSRF 악용 또는 인스턴스 침해 시)
# IMDSv1 (취약) - 인증 없이 직접 접근
curl http://169.254.169.254/latest/meta-data/
# IAM 역할 이름 확인
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
# 임시 자격증명 탈취
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/EC2Role
# {
# "AccessKeyId": "ASIA...",
# "SecretAccessKey": "...",
# "Token": "...", ← 임시 토큰
# "Expiration": "2026-05-04T12:00:00Z"
# }
# 탈취한 자격증명으로 AWS CLI 사용
export AWS_ACCESS_KEY_ID=ASIA...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=...
aws sts get-caller-identity
aws s3 ls # 모든 버킷 목록
SSRF를 통한 IMDS 공격 (Capital One 사례 유사)
# 취약한 웹 애플리케이션 (SSRF)
import requests
from flask import Flask, request
app = Flask(__name__)
@app.route('/proxy')
def proxy():
# ❌ 취약: 사용자 입력 URL을 검증 없이 요청
url = request.args.get('url')
response = requests.get(url) # SSRF 취약점
return response.text
# 공격:
# GET /proxy?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/EC2Role
# ✅ SSRF 방어: URL 화이트리스트 + IMDSv2 강제
import re
import ipaddress
from urllib.parse import urlparse
BLOCKED_HOSTS = [
'169.254.169.254', # AWS/GCP/Azure IMDS
'169.254.170.2', # AWS ECS 메타데이터
'metadata.google.internal', # GCP
'100.100.100.200', # Alibaba Cloud IMDS
]
def is_safe_url(url: str) -> bool:
parsed = urlparse(url)
hostname = parsed.hostname
if not hostname:
return False
# 블랙리스트 확인
if hostname in BLOCKED_HOSTS:
return False
# IP 주소 범위 확인
try:
ip = ipaddress.ip_address(hostname)
if ip.is_private or ip.is_link_local or ip.is_loopback:
return False
except ValueError:
pass # 도메인명은 패스
# 화이트리스트만 허용
allowed_domains = ['api.trusted-partner.com', 'cdn.example.com']
if not any(hostname.endswith(d) for d in allowed_domains):
return False
return True
# IMDSv2 강제 적용 (가장 중요한 방어책)
# 기존 EC2 인스턴스에 IMDSv2 강제
aws ec2 modify-instance-metadata-options \
--instance-id i-1234567890abcdef0 \
--http-tokens required \
--http-put-response-hop-limit 1 \
--http-endpoint enabled
# IMDSv2는 토큰 기반 인증 필요
# SSRF 공격자는 PUT 요청으로 토큰 획득이 어려움
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/
# 계정 기본값으로 IMDSv2 강제 설정
aws ec2 modify-instance-metadata-defaults \
--region us-east-1 \
--http-tokens required \
--http-put-response-hop-limit 2
취약점 4: 노출된 스토리지 및 데이터베이스
# 공개 RDS 스냅샷 탐색
aws rds describe-db-snapshots \
--snapshot-type public \
--region us-east-1 \
--query 'DBSnapshots[?Engine==`mysql`].[DBSnapshotIdentifier,SnapshotCreateTime]'
# GCP Cloud Storage 공개 버킷 탐색
gsutil ls -r gs://company-name-* 2>/dev/null
# Azure Blob Storage 공개 컨테이너
az storage container list \
--account-name targetaccount \
--public-access blob
# Shodan으로 공개된 Elasticsearch 인스턴스
# shodan search "product:Elastic port:9200"
취약점 5: 클라우드 로깅/모니터링 부재
# AWS CloudTrail 활성화
aws cloudtrail create-trail \
--name company-audit-trail \
--s3-bucket-name audit-logs-bucket \
--include-global-service-events \
--is-multi-region-trail \
--enable-log-file-validation
aws cloudtrail start-logging --name company-audit-trail
# S3 데이터 이벤트 로깅 (버킷 접근 감사)
aws cloudtrail put-event-selectors \
--trail-name company-audit-trail \
--event-selectors '[{
"ReadWriteType": "All",
"IncludeManagementEvents": true,
"DataResources": [{
"Type": "AWS::S3::Object",
"Values": ["arn:aws:s3:::sensitive-data-bucket/"]
}]
}]'
방어 방법
Infrastructure as Code (IaC) 보안 스캔
# Terraform 설정 스캔
pip install checkov
# S3 버킷 설정 예시 (Terraform)
cat > s3.tf << 'EOF'
resource "aws_s3_bucket" "data" {
bucket = "company-sensitive-data"
}
resource "aws_s3_bucket_public_access_block" "data" {
bucket = aws_s3_bucket.data.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_versioning" "data" {
bucket = aws_s3_bucket.data.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
bucket = aws_s3_bucket.data.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.s3.arn
}
bucket_key_enabled = true
}
}
EOF
# checkov로 Terraform 스캔
checkov -f s3.tf
# 또는 디렉토리 전체
checkov -d ./terraform/
# tfsec으로 추가 스캔
brew install tfsec
tfsec ./terraform/ --format json | jq '.results[] | select(.severity == "HIGH")'
AWS Security Hub + Config 규칙
# AWS Config 규칙 활성화 - 자동 컴플라이언스 검사
aws configservice put-config-rule --config-rule '{
"ConfigRuleName": "s3-bucket-public-read-prohibited",
"Source": {
"Owner": "AWS",
"SourceIdentifier": "S3_BUCKET_PUBLIC_READ_PROHIBITED"
}
}'
aws configservice put-config-rule --config-rule '{
"ConfigRuleName": "iam-root-access-key-check",
"Source": {
"Owner": "AWS",
"SourceIdentifier": "IAM_ROOT_ACCESS_KEY_CHECK"
}
}'
# Security Hub 활성화
aws securityhub enable-security-hub
aws securityhub batch-enable-standards \
--standards-subscription-requests '[
{"StandardsArn": "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"},
{"StandardsArn": "arn:aws:securityhub:us-east-1::standards/aws-foundational-security-best-practices/v/1.0.0"}
]'
멀티 클라우드 CSPM 도구
# ScoutSuite - 멀티 클라우드 보안 감사
pip install scoutsuite
# AWS 감사
scout aws --report-dir ./reports
# GCP 감사
scout gcp --user-account --report-dir ./reports
# Azure 감사
scout azure --cli --report-dir ./reports
탐지 방법
CloudTrail 이상 탐지
# 비정상적인 루트 계정 사용 탐지
import boto3
import json
from datetime import datetime, timedelta
def detect_root_usage():
"""루트 계정 API 호출 탐지"""
cloudtrail = boto3.client('cloudtrail', region_name='us-east-1')
response = cloudtrail.lookup_events(
LookupAttributes=[{
'AttributeKey': 'Username',
'AttributeValue': 'root'
}],
StartTime=datetime.now() - timedelta(hours=24),
EndTime=datetime.now()
)
if response['Events']:
for event in response['Events']:
print(f"[ALERT] Root account used!")
print(f" Time: {event['EventTime']}")
print(f" Action: {event['EventName']}")
print(f" IP: {json.loads(event['CloudTrailEvent'])['sourceIPAddress']}")
def detect_credential_exfil():
"""자격증명 탈취 후 이상 행동 탐지 패턴"""
# 1. 새로운 지역에서 API 호출
# 2. 새로운 IP에서 급격히 많은 서비스 목록 조회
# 3. IAM 권한 열거 패턴 (list*, describe*, get*)
suspicious_actions = [
'iam:ListRoles', 'iam:ListUsers', 'iam:ListPolicies',
's3:ListBuckets', 'ec2:DescribeInstances',
'sts:AssumeRole'
]
cloudtrail = boto3.client('cloudtrail')
for action in suspicious_actions:
service, api_call = action.split(':')
events = cloudtrail.lookup_events(
LookupAttributes=[{
'AttributeKey': 'EventName',
'AttributeValue': api_call
}],
StartTime=datetime.now() - timedelta(hours=1),
EndTime=datetime.now()
)
if len(events['Events']) > 10:
print(f"[WARN] Suspicious activity: {action} called {len(events['Events'])} times in 1hr")
GuardDuty 통합
# AWS GuardDuty 활성화
aws guardduty create-detector --enable --finding-publishing-frequency FIFTEEN_MINUTES
# GuardDuty 주요 탐지 유형
# - UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration
# → EC2 메타데이터로 얻은 자격증명을 외부에서 사용
# - Recon:IAMUser/MaliciousIPCaller
# → 악성 IP에서 IAM 정찰 행동
# - Policy:S3/BucketPublicAccessGranted
# → S3 버킷 퍼블릭 접근 권한 부여
# - CryptoCurrency:EC2/BitcoinTool.B
# → 암호화폐 채굴 탐지
# SNS를 통한 자동 알림
aws events put-rule \
--name GuardDutyHighSeverity \
--event-pattern '{
"source": ["aws.guardduty"],
"detail-type": ["GuardDuty Finding"],
"detail": {"severity": [{"numeric": [">=", 7]}]}
}'
참고 도구 및 자원
| 도구/자원 | 분류 | 설명 |
|---|---|---|
| ScoutSuite | CSPM | 멀티 클라우드 보안 감사 오픈소스 도구 |
| Prowler | AWS 감사 | AWS 보안 모범 사례 및 컴플라이언스 검사 |
| Checkov | IaC 스캔 | Terraform/CloudFormation/K8s 설정 보안 스캔 |
| tfsec | IaC 스캔 | Terraform 정적 분석 보안 스캐너 |
| AWS Security Hub | CSPM | AWS 보안 설정 중앙 집중 관리 |
| AWS GuardDuty | 위협 탐지 | ML 기반 AWS 위협 탐지 서비스 |
| AWS Config | 컴플라이언스 | AWS 리소스 설정 변경 추적 및 규칙 검사 |
| Pacu | 침투 테스트 | AWS 침투 테스트 프레임워크 (연구용) |
| CloudMapper | 시각화 | AWS 네트워크 토폴로지 시각화 |
| CartographyAI | 자산 관리 | 클라우드 자산 그래프 DB 시각화 |
| CNAPP 솔루션 | 통합 보안 | Wiz, Lacework, Orca Security 등 |
| CIS Benchmarks | 가이드라인 | AWS/GCP/Azure CIS 보안 기준선 |
⚠️ 주의사항: 클라우드 보안 테스트는 반드시 자신이 소유한 계정에서만 수행해야 한다. 타인의 AWS 계정이나 S3 버킷에 무단으로 접근하는 것은 미국 CFAA(Computer Fraud and Abuse Act), 한국 정보통신망법 등에 따라 중대한 형사 범죄다. 공개된 S3 버킷을 발견했더라도 무단으로 데이터를 열람하거나 다운로드하면 법적 책임이 발생할 수 있다. 취약한 버킷을 발견한 경우 responsible disclosure(책임 있는 공개) 절차에 따라 소유자에게 통보하고, 추가 접근을 즉시 중단한다. 또한 프로덕션 클라우드 환경에서의 설정 변경 실습은 서비스 중단을 유발할 수 있으므로 항상 별도의 테스트 계정을 사용한다.