AWS WAF, 막기 전에 세어 보기
WAF 규칙을 처음부터 Block으로 켜면 정상 요청까지 막힐 수 있고, 막힌 요청은 애플리케이션 로그에 남지 않는다.
그래서 새 규칙은 먼저 Count로 붙여 어떤 요청이 걸리는지 확인한 뒤에 막는다.
이 글의 목차WAF는 요청의 내용을 본다6
AWS WAF에 규칙을 추가하면 규칙마다 동작(action)을 고르는 칸이 나온다.
고를 수 있는 것은 Allow, Block, Count, 그리고 요청을 보낸 쪽이 사람이 쓰는 브라우저인지 확인하는 CAPTCHA와 Challenge다.
AWS가 미리 만들어 둔 규칙 묶음을 추가할 때는 안에 든 규칙을 전부 Count로 바꾸는 선택지도 보인다.
공격을 막으려고 붙이는 도구에 아무것도 막지 않는 Count는 왜 있을까.
WAF가 무엇을 보고 판단하는지부터 정리한다.
WAF는 요청의 내용을 본다
브라우저가 웹 서버에 보내는 HTTP 요청은 여러 부분으로 이뤄진다.
/login 같은 경로, 주소 뒤에 붙는 쿼리 문자열, 브라우저 정보나 쿠키를 담는 헤더, 그리고 폼에 쓴 글이나 업로드한 파일이 실리는 본문(body)이다.
보안 그룹은 요청이 어느 IP에서 어느 포트로 오는지만 본다.
WAF(Web Application Firewall)는 그 안쪽을 열어 경로와 헤더와 본문에서 공격처럼 생긴 패턴을 찾는다.
WAF는 CloudFront 배포, Application Load Balancer, API Gateway의 REST API 같은 자원 앞에 붙는다.
지원하는 자원 목록에 EC2 인스턴스는 없다.
서버 한 대만 운영한다면 그 앞에 로드밸런서나 CloudFront를 두어야 WAF를 쓸 수 있다.
규칙은 순서대로, Allow와 Block에서 멈춘다
WAF 설정의 중심은 웹 ACL이다.
웹 ACL은 규칙 목록과 함께, 어느 규칙에서도 결론이 나지 않은 요청에 적용할 기본 동작을 담는다.
새 콘솔은 웹 ACL을 보호 팩(protection pack)이라는 이름으로 보여 주지만 동작 방식은 같다.
규칙마다 우선순위 번호가 있고, 번호가 낮은 규칙부터 검사한다.
Allow와 Block은 검사를 끝내는 동작이다.
요청이 이 두 동작의 규칙에 걸리면 그 자리에서 결론이 나고, 뒤의 규칙은 보지 않는다.
Count는 검사를 끝내지 않는다.
일치한 요청을 하나 세어 두고 다음 규칙으로 넘긴다.
끝까지 어느 규칙에서도 결론이 나지 않으면 웹 ACL의 기본 동작이 적용된다.
그래서 Count에 집계된 요청이 최종적으로 통과했다고 볼 수는 없다.
뒤에 있는 Block 규칙이 같은 요청을 막았을 수도 있다.
같은 예시 요청에 첫 규칙과 뒤 규칙을 다르게 적용하면 결과가 달라진다.
- 첫 규칙Count · 1회 집계
- 다음 규칙일치 없음
- 앱 도착허용
- 첫 규칙Count · 1회 집계
- 다음 규칙Block · 검사 종료
- 앱 도착차단
- 첫 규칙Block · 검사 종료
- 다음 규칙검사하지 않음
- 앱 도착차단
Count는 요청을 세고 다음 규칙으로 보낸다. 뒤의 Block에 걸리면 앱에는 도착하지 않는다.
Count 뒤에도 검사는 이어진다. 이 경우에는 뒤 차단 규칙이 없고 기본 동작이 허용이라 통과한다.
기본 동작은 Allow이며 일치 여부를 미리 정한 예시다. 실제 관리형 규칙이나 탐지 정확도를 재현하지 않는다.
관리형 규칙이 정상 요청을 막을 때
규칙을 처음부터 직접 쓸 수도 있지만, 대개는 AWS가 만들어 관리하는 규칙 묶음인 관리형 규칙 그룹으로 시작한다.
그중 핵심 규칙 세트(Core rule set)는 웹 애플리케이션에 두루 해당하는 규칙을 모은 것이어서, AWS도 어떤 경우든 고려해 보라고 권한다.
그런데 이 규칙들은 우리 서비스의 정상 요청이 어떻게 생겼는지 모른다.
정상 요청을 공격으로 잘못 판단하는 것을 오탐(false positive)이라고 한다.
핵심 규칙 세트의 규칙 두 개만 봐도 감이 온다.
SizeRestrictions_BODY 규칙은 본문이 8KB를 넘는 요청을 막고, NoUserAgent_HEADER 규칙은 User-Agent 헤더가 없는 요청을 막는다.
User-Agent는 요청을 보낸 프로그램이 자기 이름을 적는 헤더다.
브라우저는 늘 보내지만, 서버끼리 주고받는 요청이나 간단한 스크립트는 빠뜨리기도 한다.
그러면 긴 글을 한 번에 저장하는 폼, 본문에 파일을 실어 보내는 업로드, User-Agent 없이 호출하는 연동이 규칙을 켠 첫날부터 막힐 수 있다.
셋 다 공격과는 아무 관계가 없는 요청이다.
사용자가 SQL 문장을 글로 올리는 개발자 커뮤니티라면 SQL 삽입을 찾는 규칙도 문제가 될 수 있다.
SQL 삽입은 입력값에 데이터베이스 명령을 섞어 넣어 서버가 그대로 실행하게 만드는 공격이다.
이 공격을 찾는 규칙은 문자열의 모양을 볼 뿐, 그 글을 쓴 사람의 의도는 알지 못한다.
그래서 평범한 질문 글이 공격처럼 보이기도 한다.
막힌 요청은 애플리케이션 로그에 남지 않는다
오탐은 잘 드러나지 않아서 더 위험하다.
Block으로 막힌 요청은 애플리케이션 서버까지 가지 않는다.
서버는 그 요청을 받은 적이 없으니 서버 로그에도, 서버의 오류율에도 흔적이 없다.
사용자 쪽에는 기본값으로 HTTP 403(Forbidden) 응답이 돌아간다.
사용자가 알려 주지 않으면 운영하는 쪽에서는 무엇이 막혔는지 모르고 지나가기 쉽다.
흔적은 WAF 쪽에 남는다.
요청 샘플링을 켜 둔 웹 ACL이라면 콘솔의 샘플 요청 탭에서 최근 3시간 동안 검사한 요청 가운데 일부를 표본으로 볼 수 있다.
요청마다 어떤 규칙에 걸렸는지도 함께 나온다.
요청을 빠짐없이 남기려면 WAF 로그를 따로 켜고, CloudWatch Logs 로그 그룹이나 S3 버킷, Data Firehose 가운데 보낼 곳을 정해야 한다.
로그에는 요청의 세부 내용과 일치한 규칙이 함께 기록된다.
먼저 Count로 붙여 보는 이유
AWS의 WAF 시험·조정 안내는 웹 ACL을 운영 트래픽에 적용하기 전에 시험 환경에서 먼저 조정하고, 그다음 운영 트래픽에서 Count 모드로 시험하라고 권한다.
실제 사용자의 요청이 어떤 규칙에 걸리는지는 운영 트래픽을 흘려 보기 전에는 알 수 없다.
Count로 붙인 규칙은 아무것도 막지 않으니 사용자는 영향을 받지 않는다.
그동안 로그에는 이 규칙이 Block이었다면 막혔을 요청들이 쌓인다.
시험 기간에는 그 목록에 정상 요청이 섞였는지 확인한다.
관리형 규칙 그룹은 그룹 안의 규칙 하나하나의 동작을 Count로 재정의(override)할 수 있다.
그룹 자체는 그대로 두고, 내 웹 ACL에서 쓸 때만 동작을 바꾸는 설정이다.
재정의는 대개 이렇게 시험할 때 쓰고, 오탐을 내는 규칙이 있으면 그 규칙에만 Count 재정의를 남겨 둔다.
그룹 전체의 결과를 Count로 바꾸는 방식은 시험에 맞지 않는다.
그룹 안의 평가 방식은 그대로여서 처음 걸린 규칙에서 평가가 끝나고, 그 뒤 규칙들이 무엇을 잡았을지는 알 수 없다.
시험을 마치면 Count로 둔 규칙을 원래 동작으로 되돌린다.
정상 요청과 겹친 규칙은 Count로 남기고, 나머지 규칙은 각자의 동작으로 돌린다.
| 언제 막나 | 잡는 것 | 함께 잃는 것 |
|---|---|---|
| 바로 차단으로 켠다 | 그날부터 공격을 막는다 | 정상 요청이 막혀도 서버 쪽에서는 보이지 않는다 |
| 세어 보고 나서 막는다 | 겹치는 규칙을 미리 찾는다 | 그 기간 동안은 막지 못한다 |
| 세기만 하고 그대로 둔다 | 아무도 막히지 않는다 | 방어라고 부를 수 없다 |
Block으로 넘긴 다음에 볼 것
Count 기간은 끝나는 날을 미리 정해 두는 편이 좋다.
Count로 둔 규칙은 공격 요청을 만나도 막지 않는다.
결과를 볼 사람과 날짜가 정해지지 않으면 시험 상태가 기본값처럼 굳어 버린다.
Block으로 넘긴 뒤에도 할 일은 남는다.
실제로 막힌 요청의 지표와 로그를 지켜보면서, 트래픽이 바뀌는 데 맞춰 규칙을 계속 조정한다.
요청의 모양은 서비스에 기능이 붙을 때마다 바뀐다.
큰 파일 업로드나 새 외부 연동처럼 본문 크기나 헤더가 달라지는 기능을 내보낼 때는, 그 경로에 새로 생긴 Block 기록이 없는지 WAF 로그에서 함께 확인한다.