📚 네트워크 · 패킷 분석 › 06. 방화벽 · IDS 기초 — 290편
이전 글: 289. Suricata란 무엇인가 · 다음 글: 291. Port Scan 탐지
Suricata Rule은 Snort 규칙 형식을 바탕으로 응용 계층 검사 기능을 확장한 규칙입니다. 공통 구조(헤더·옵션, sid·rev·classtype)는 282. Signature Rule에서, Snort 문법 차이는 288. Snort Rule 기초에서 다뤘습니다. 이 글은 Suricata에서 특히 자주 보는 문법을 다룹니다.
가장 큰 특징은 sticky buffer입니다. 검사할 필드를 먼저 지정하면, 그 뒤의 content는 모두 그 필드 안에서만 비교됩니다. Suricata 5 이후에는 점(.)으로 구분한 이름을 씁니다.
| sticky buffer | 검사 대상 |
|---|---|
http.method, http.uri, http.uri.raw | HTTP 메서드, 정규화된 URI, 원본 URI |
http.host, http.user_agent, http.request_body | Host 헤더, User-Agent, 요청 본문 |
http.stat_code, http.response_body | 응답 코드, 응답 본문 |
dns.query | DNS 질의 이름 |
tls.sni, tls.cert_subject | TLS SNI, 인증서 주체 |
file.data | 전송 파일·본문 데이터 |
이전 버전의 수정자 방식(content:"..."; http_uri;)도 호환을 위해 인식되지만 새 규칙은 sticky buffer 방식이 권장됩니다. 키워드 이름은 버전마다 추가·변경되므로 사용 중인 버전의 문서를 확인합니다.
sticky buffer 규칙이 평가되는 방식입니다(형식 예시, 값은 환경마다 다름).
alert dns $HOME_NET any -> any any (msg:"LOCAL DNS query for blocklisted test domain"; dns.query; content:"bad-example.test"; nocase; endswith; classtype:bad-unknown; sid:1001401; rev:1;)
헤더: 프로토콜 dns → 포트와 무관하게 DNS로 식별된 트래픽만
↓
dns.query → 이후 조건은 DNS 질의 이름 필드 안에서만
↓
content "bad-example.test" + nocase + endswith
→ 질의 이름이 이 문자열로 "끝나는" 경우 (하위 도메인 포함)
↓
Alert: sid 1001401, classtype bad-unknown
content 위치 조건을 함께 읽는 법입니다.
| 옵션 | 의미 | 예 |
|---|---|---|
nocase | 대소문자 무시 | content:"select"; nocase; |
depth / offset | 버퍼 시작 기준 검사 범위 / 시작 위치 | depth:4; 앞 4바이트 안 |
distance / within | 앞 content 뒤 상대 위치 / 그 범위 | 두 문자열의 순서·간격 조건 |
startswith / endswith | 버퍼의 시작 / 끝과 일치 | Suricata 5 이후 |
fast_pattern | MPM 사전 검사에 쓸 content 지정 | 성능 조정 |
여러 패킷이나 반복을 다루는 옵션입니다.
| 옵션 | 역할 | 예 |
|---|---|---|
flowbits | 같은 흐름 안에서 상태 표시를 설정·확인 | 로그인 요청 후 특정 응답이 오면 Alert |
threshold | Alert 기록 방식: limit / threshold / both | threshold:type limit, track by_src, count 1, seconds 60; |
detection_filter | N회를 넘긴 뒤부터 Alert | 반복 시도 탐지 |
target | 공격 대상 쪽(src_ip·dest_ip) 표시 | target:dest_ip; → EVE에 source·target 정보 추가 |
app-layer-protocol | 식별된 프로토콜 조건 | 포트 80인데 HTTP가 아닌 경우 등 |
dsize | 페이로드 크기 조건 | 비정상적으로 큰·작은 패킷 |
flowbits는 여러 단계의 조건을 하나의 Alert로 묶을 때 씁니다. 첫 규칙은 flowbits:set,이름; flowbits:noalert;로 표시만 하고 Alert를 남기지 않으며, 두 번째 규칙이 flowbits:isset,이름;으로 표시를 확인한 뒤 Alert를 만듭니다. 따라서 Alert 규칙만 보면 조건의 절반이 안 보일 수 있으므로, 같은 flowbit 이름을 설정하는 규칙도 찾아 읽어야 합니다.
flowbits로 두 단계를 묶은 규칙의 형식 예시입니다(값은 환경마다 다름, sid 로컬 범위).
# 형식 예시 — 1단계: 외부에서 관리 로그인 요청 (표시만, Alert 없음)
alert http $EXTERNAL_NET any -> $HOME_NET any (msg:"LOCAL admin login request (set)"; flow:established,to_server; http.method; content:"POST"; http.uri; content:"/admin/login"; startswith; flowbits:set,local.admin.login; flowbits:noalert; sid:1001402; rev:1;)
# 형식 예시 — 2단계: 같은 흐름에서 로그인 후 리다이렉트 응답이 오면 Alert
alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"LOCAL external admin login followed by redirect"; flow:established,to_client; flowbits:isset,local.admin.login; http.stat_code; content:"302"; target:src_ip; classtype:policy-violation; sid:1001403; rev:1;)
분석 방법 — 2단계 규칙의 Alert는 "외부에서 관리 페이지 로그인 요청이 있었고, 서버가 302로 응답했다"는 뜻입니다. 302는 로그인 성공 후 이동일 수도, 실패 후 로그인 페이지로 되돌리는 것일 수도 있으므로 응답 헤더(Location)와 웹 서버 로그로 결과를 확인합니다. 두 번째 규칙은 응답 방향이라 src_ip가 서버이므로 target:src_ip로 대상을 표시했습니다.
http.uri는 정규화된 값이므로 인코딩 변형에 강하고, http.uri.raw는 원본 그대로라 인코딩 자체를 탐지할 때 씁니다. 어떤 버퍼를 썼는지가 탐지 범위를 결정합니다.limit은 Alert를 줄여 주지만, 기간 내 두 번째 이후 행위는 기록되지 않습니다. 건수가 중요한 규칙에는 신중히 씁니다.관제자가 Suricata 규칙을 읽을 때 질문
flowbits:isset이 있다면, 그 표시를 설정하는 규칙은 무엇인가?threshold나 detection_filter가 있다면, Alert 1건이 의미하는 실제 행위 수는?target 정보가 있다면 공격 대상은 어느 쪽인가?Alert의 sid 조회 → 규칙 원문
↓ 헤더 프로토콜·sticky buffer → 무엇을 검사했는가
↓ flowbits → 연결된 선행 규칙 찾기
↓ threshold / detection_filter → 건수 해석
↓ 같은 flow_id 메타데이터로 실제 값 확인
오탐 주의: endswith 없이 짧은 도메인 문자열만 content로 쓰면 이름이 비슷한 정상 도메인도 일치합니다. 규칙 시험 절차는 04 영역 192. IDS Rule을 참고합니다. 이 문법을 활용한 탐지 사례는 291. Port Scan 탐지부터 이어집니다.