OWASP CRS 규칙 묶음을 얹은 ModSecurity는 10분이면 켜지고 사흘 뒤에 꺼집니다 — 관리 영역에서 글이 저장되지 않고, 파일 업로드가 깨지고, 주소에 아포스트로피가 들어 있다는 이유로 고객이 주문을 넣지 못한 뒤에요. 「WAF가 업무를 방해한다」는 결론은 저절로 떠오르지만 그것은 틀렸습니다. 그 차단들은 거의 전부 서너 개의 정확한 예외로 해결되며, 요령의 전부는 그것들을 제대로 찾아내는 데 있습니다.
차단을 바로 켜지 마세요
첫 주는 관찰만 합니다. /etc/modsecurity/modsecurity.conf에:
SecRuleEngine DetectionOnly
이 모드에서 WAF는 차단했을 모든 것을 기록하고 아무것도 차단하지 않습니다. 실제 트래픽 일주일 — 관리 영역에서의 자기 작업, 이미지 업로드, 주문 넣기까지 포함해서 — 이 가정이 아니라 진짜 오탐의 목록을 줍니다. On으로 바꾸는 것은 그 목록을 처리한 뒤에야 의미가 있습니다.
CRS의 작동 방식: 규칙 하나가 아니라 합계
이것이 이후 모든 것의 열쇠입니다. CRS가 단일 규칙으로 요청을 차단하는 일은 거의 없습니다. 발동한 각 규칙이 요청에 이상 점수를 더하고, 합계가 임계값을 넘을 때 차단이 일어납니다. 그래서 로그에 한 줄이 아니라 여러 줄이 나오고, 그중 마지막이 식별자 949110의 규칙 — 합계를 더하는 바로 그것입니다.
실무적 결론: 예외는 점수를 더한 규칙에 대해 만들어야 하며, 949110에 대해서가 아닙니다. 합산 규칙을 끄면 묶음 전체가 꺼지고, WAF는 설정 파일의 한 줄로만 남습니다.
두 번째 결론은 paranoia 수준입니다. 기본값은 1이고 그것이 옳은 선택입니다. 수준 2와 3은 평범한 사이트에서 설계상 오탐을 내는 규칙을 더하며, 수준 1을 완전히 다듬은 뒤에야 켤 가치가 있습니다.
원인이 된 규칙 찾기
필요한 것은 전부 감사 로그(/var/log/modsec_audit.log)와 웹 서버의 오류 로그에 있습니다. 차단이 일어난 시각으로 찾으세요.
sudo grep -o 'id "[0-9]*"' /var/log/modsec_audit.log | sort | uniq -c | sort -rn | head
이것이 발동한 규칙의 빈도 목록입니다. 그다음 특정 식별자에 대해 정확히 무엇이 그것을 발동시켰는지 봅니다.
sudo grep -A5 'id "942100"' /var/log/modsec_audit.log | head -40
세 가지가 필요합니다. 규칙 식별자, 매개변수 이름(ARGS:content, ARGS:comment), 그리고 요청 경로. 예외는 그것들로 만들어집니다.
흔한 용의자들
이 명단은 사이트마다 되풀이됩니다.
- 942100 — SQL 인젝션. 내용이 긴 텍스트 필드에서 발동합니다. 글 본문, 상품 설명, 댓글. 평범한 산문 속의 따옴표, 괄호,
select같은 낱말이 탐지기에는 수상해 보입니다; - 941100 및 941xxx 계열 — XSS. 시각적 편집기와 함께 옵니다. 필드에 HTML 태그가 들어 있는 것이 곧 그 기능의 의미 전부입니다;
- 920420 — 허용되지 않은 Content-Type. API와 파일 업로드를 망가뜨립니다. 기본으로 허용되는 타입 집합이 좁고, 옛 버전에는
application/json이 아예 없었습니다; - 913100 — User-Agent 기반 스캐너 판정. 스캐너와 함께 정당한 도구도 잡습니다. 가용성 모니터링, 자기 스크립트 속의 curl 같은 것들;
- 200002, 200004 — 요청 본문 파싱 오류. 대개 공격이 아니라 본문 크기 제한 초과, 즉 큰 파일 업로드를 뜻합니다.
예외를 만드는 세 가지 방법
거친 정도가 커지는 순서로. 모두 CRS 자체의 파일이 아니라 자신의 파일(예: /etc/modsecurity/crs/REQUEST-900-EXCLUSION-RULES.conf)에 씁니다. 규칙 묶음은 갱신되고 당신의 수정은 그와 함께 사라지니까요.
규칙 하나에서 매개변수 하나를 빼기. 가장 정밀한 선택지이며 지향해야 할 방법입니다.
SecRuleUpdateTargetById 942100 "!ARGS:content"
한 경로에서만 규칙 끄기. 특정 페이지가 시끄러울 때 딱 맞습니다. 편집기, 가져오기, 리뷰 폼 같은 것들.
SecRule REQUEST_URI "@beginsWith /admin/post" \
"id:1001,phase:1,pass,nolog,ctl:ruleRemoveById=942100"
규칙을 통째로 끄기. 마지막 수단이며 거의 언제나 원인을 찾지 못했다는 표시입니다.
SecRuleRemoveById 942100
첫 번째와 세 번째의 차이는 큽니다. 첫 번째에서는 필드 하나 — 글 본문 — 가 규칙 하나에 의한 SQL 인젝션 검사에서 빠질 뿐이고, 두 번째와 세 번째에서는 사이트 전체가 검사되지 않게 됩니다. 드는 수고의 차이는 5분쯤입니다.
수정 뒤에는 설정을 확인하고 부드럽게 다시 읽히세요.
sudo apachectl configtest && sudo systemctl reload apache2
sudo nginx -t && sudo systemctl reload nginx
일주일을 아껴 주는 순서
DetectionOnly로 일주일. 사이트, 관리 영역, 업로드를 포함한 평소 작업을 하면서.- 감사 로그에서 규칙의 빈도 목록을 뽑습니다. 위에서부터 처리하세요 — 처음 서너 개가 소음의 90%를 차지합니다.
- 각각에 대해: 어떤 매개변수이고 어느 페이지인지 밝힙니다. 예외는 규칙 단위가 아니라 매개변수 단위로.
- 그러고 나서야
SecRuleEngine On. - 한 달에 한 번 차단 기록을 들여다보세요. 사이트가 바뀌었으니 새 오탐이 생깁니다.
대개 무너지는 것은 바로 이 마지막 항목입니다. 감사 로그는 기가바이트 단위의 텍스트이고 손으로 읽을 사람은 없습니다. 모아 놓은 패널의 의미는, 발동한 규칙 목록과 차단된 요청과 적용 중인 규칙 묶음이 grep으로 캐내는 것이 아니라 눈앞에 있게 하는 것입니다. 그것이 어떤 모습인지는 아래 데모 페이지에서.