ruleset 문법.html
20.35 KB · text/html · 업로드 2026-07-16 11:39
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Snort 룰셋 핵심 옵션 가이드</title>
<style>
:root{
--bg:#f3f6fb;--card:#fff;--text:#172033;--muted:#667085;--line:#d9e1ec;
--primary:#3157d5;--primary2:#223d9f;--soft:#eef2ff;--code:#101828;
--green:#027a48;--orange:#b54708;--red:#b42318;
}
*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{margin:0;background:var(--bg);color:var(--text);font-family:"Noto Sans KR","Malgun Gothic",sans-serif;line-height:1.7}
.wrap{max-width:1180px;margin:auto;padding:32px 18px 70px}
.hero{padding:42px;border-radius:24px;color:#fff;background:linear-gradient(135deg,#172033,#3157d5);box-shadow:0 18px 40px #1018282e}
.hero h1{margin:0 0 9px;font-size:clamp(29px,4vw,41px)}
.hero p{margin:0;opacity:.9}
.flow-map{margin-top:23px;display:flex;flex-wrap:wrap;align-items:center;gap:8px}
.flow-map span{padding:7px 11px;background:#ffffff18;border:1px solid #ffffff35;border-radius:999px;font-size:13px;font-weight:700}
.flow-map b{opacity:.65}
.tabs{position:sticky;top:8px;z-index:10;margin:20px 0;display:grid;grid-template-columns:repeat(5,1fr);gap:9px;padding:10px;background:#f3f6fbeF;backdrop-filter:blur(10px);border-radius:17px}
.tab{border:1px solid var(--line);background:#fff;color:var(--text);border-radius:12px;padding:12px 8px;font-weight:750;cursor:pointer;transition:.18s}
.tab:hover{border-color:var(--primary);color:var(--primary);transform:translateY(-1px)}
.tab.active{background:var(--primary);color:#fff;border-color:var(--primary);box-shadow:0 8px 18px #3157d533}
.panel{display:none;background:var(--card);border:1px solid var(--line);border-radius:21px;padding:30px;box-shadow:0 9px 28px #10182810;animation:fade .2s ease}
.panel.active{display:block}
@keyframes fade{from{opacity:.3;transform:translateY(4px)}to{opacity:1;transform:none}}
.badge{display:inline-block;background:var(--soft);color:var(--primary);font-weight:800;font-size:13px;padding:5px 11px;border-radius:999px}
h2{font-size:27px;margin:8px 0 4px}
h3{font-size:19px;margin:27px 0 8px}
.lead{color:var(--muted);margin-top:4px}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(245px,1fr));gap:14px;margin:16px 0}
.card{border:1px solid var(--line);background:#fbfcfe;border-radius:14px;padding:17px}
.card strong{color:var(--primary)}
.codebox{position:relative;margin:17px 0}
pre{margin:0;background:var(--code);color:#f8fafc;border-radius:14px;padding:46px 18px 18px;overflow:auto;font-size:14px;line-height:1.62}
code{font-family:Consolas,Monaco,monospace}
.copy{position:absolute;right:10px;top:10px;border:1px solid #ffffff35;background:#ffffff16;color:#fff;border-radius:9px;padding:7px 11px;cursor:pointer;font-size:12px}
.copy:hover{background:#ffffff29}.copy.done{background:#039855;border-color:#039855}
.note{border-left:5px solid var(--primary);background:var(--soft);padding:14px 16px;border-radius:10px;margin:16px 0}
.note.warn{border-color:#f79009;background:#fff7ed}.note.good{border-color:#12b76a;background:#ecfdf3}.note.bad{border-color:#f04438;background:#fff1f0}
.diagram{white-space:pre-line;text-align:center;font-family:Consolas,monospace;background:#f8fafc;border:1px dashed #98a2b3;padding:19px;border-radius:14px;margin:16px 0}
table{width:100%;border-collapse:collapse;margin:14px 0;font-size:14px}
th,td{border:1px solid var(--line);padding:11px;text-align:left;vertical-align:top}
th{background:#f2f4f7}
.pill{display:inline-block;padding:2px 8px;border-radius:999px;font-size:12px;font-weight:800}
.sn2{background:#fff4e5;color:#b54708}.sn3{background:#eef2ff;color:#3157d5}.suri{background:#ecfdf3;color:#027a48}
.option-list{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:13px}
.option{border:1px solid var(--line);border-radius:14px;padding:16px}
.option h4{margin:0 0 4px;color:var(--primary)}
.rule-parts{display:grid;grid-template-columns:repeat(auto-fit,minmax(155px,1fr));gap:9px;margin:17px 0}
.rule-parts div{padding:13px;border-radius:12px;background:#f8fafc;border:1px solid var(--line);text-align:center}
footer{text-align:center;color:var(--muted);font-size:13px;margin-top:25px}
@media(max-width:820px){.tabs{grid-template-columns:repeat(2,1fr);position:static}.hero{padding:28px}.panel{padding:21px}}
</style>
</head>
<body>
<div class="wrap">
<header class="hero">
<h1>Snort 룰셋 핵심 옵션 가이드</h1>
<p>룰 구조부터 탐지 옵션, 관리 옵션, Snort 2·Snort 3·Suricata 문법 차이까지</p>
<div class="flow-map">
<span>① Rule 구조</span><b>→</b><span>② 네트워크 변수</span><b>→</b><span>③ Content</span><b>→</b>
<span>④ Sticky Buffer</span><b>→</b><span>⑤ Flow</span><b>→</b><span>⑥ Flowbits</span><b>→</b>
<span>⑦ 기타 옵션</span><b>→</b><span>⑧ 관리 옵션</span>
</div>
</header>
<nav class="tabs">
<button class="tab active" data-id="structure">1. Rule 구조</button>
<button class="tab" data-id="network">2. 네트워크 변수</button>
<button class="tab" data-id="content">3. Content</button>
<button class="tab" data-id="sticky">4. Sticky Buffer</button>
<button class="tab" data-id="flow">5. Flow</button>
<button class="tab" data-id="flowbits">6. Flowbits</button>
<button class="tab" data-id="extra">7. 기타 옵션</button>
<button class="tab" data-id="manage">8. 관리 옵션</button>
<button class="tab" data-id="versions">9. 버전별 차이</button>
</nav>
<main>
<section class="panel active" id="structure">
<span class="badge">① Rule 구조</span>
<h2>룰은 Header와 Options로 구성</h2>
<p class="lead">괄호 앞은 어떤 트래픽을 볼지 정하고, 괄호 안은 어떤 조건으로 탐지할지 정합니다.</p>
<div class="codebox"><button class="copy">복사</button><pre><code>alert http $EXTERNAL_NET any -> $HOME_NET any (
msg:"HTTP POST Login";
flow:established,to_server;
http_method;
content:"POST";
sid:2000001;
rev:1;
)</code></pre></div>
<div class="rule-parts">
<div><strong>alert</strong><br>Action</div>
<div><strong>http</strong><br>Protocol</div>
<div><strong>$EXTERNAL_NET any</strong><br>Source</div>
<div><strong>→</strong><br>Direction</div>
<div><strong>$HOME_NET any</strong><br>Destination</div>
<div><strong>( ... )</strong><br>Rule Options</div>
</div>
<table>
<tr><th>구분</th><th>역할</th><th>예시</th></tr>
<tr><td>Action</td><td>매칭 시 수행할 동작</td><td><code>alert</code>, <code>drop</code>, <code>pass</code></td></tr>
<tr><td>Protocol</td><td>검사할 프로토콜</td><td><code>tcp</code>, <code>udp</code>, <code>http</code></td></tr>
<tr><td>Source / Destination</td><td>출발지·목적지 IP와 포트</td><td><code>$EXTERNAL_NET any -> $HOME_NET 80</code></td></tr>
<tr><td>Options</td><td>문자열, 방향, 상태, 식별 정보</td><td><code>content</code>, <code>flow</code>, <code>sid</code></td></tr>
</table>
</section>
<section class="panel" id="network">
<span class="badge">② HOME_NET / EXTERNAL_NET</span>
<h2>보호 대상과 외부 네트워크 정의</h2>
<div class="codebox"><button class="copy">복사</button><pre><code>alert tcp $EXTERNAL_NET any -> $HOME_NET 80 (
msg:"HTTP Access";
sid:2000001;
rev:1;
)</code></pre></div>
<div class="grid">
<div class="card"><strong>$HOME_NET</strong><p>보호하려는 내부망, 서버망 또는 DMZ 대역입니다.</p></div>
<div class="card"><strong>$EXTERNAL_NET</strong><p>외부 인터넷 또는 HOME_NET을 제외한 네트워크입니다.</p></div>
</div>
<h3>Snort 3 설정</h3>
<p><code>/usr/local/snort/etc/snort/snort.lua</code></p>
<div class="codebox"><button class="copy">복사</button><pre><code>HOME_NET = '6.10.64.0/24'
EXTERNAL_NET = 'any'
include 'snort_defaults.lua'</code></pre></div>
<h3>Security Onion / Snort 2 설정</h3>
<p><code>/etc/nsm/[센서명-인터페이스]/snort.conf</code></p>
<div class="codebox"><button class="copy">복사</button><pre><code>ipvar HOME_NET 6.10.64.0/24
ipvar EXTERNAL_NET !$HOME_NET
ipvar HTTP_SERVERS 6.10.64.30</code></pre></div>
<div class="note"><strong>핵심:</strong> <code>EXTERNAL_NET any</code>는 내부 통신도 포함할 수 있습니다. 외부만 의미하게 하려면 <code>!$HOME_NET</code> 구성이 더 명확합니다.</div>
</section>
<section class="panel" id="content">
<span class="badge">③ Content</span>
<h2>현재 검사 버퍼에서 문자열 검색</h2>
<div class="codebox"><button class="copy">복사</button><pre><code>alert tcp $EXTERNAL_NET any -> $HOME_NET 80 (
msg:"POST String Detected";
content:"POST";
sid:2000002;
rev:1;
)</code></pre></div>
<p><code>content</code>는 현재 선택된 버퍼에서 문자열 또는 16진수 패턴을 찾습니다. 별도 Sticky Buffer를 선택하지 않으면 일반 패킷 데이터가 기준입니다.</p>
<div class="grid">
<div class="card"><strong>문자열</strong><p><code>content:"POST";</code></p></div>
<div class="card"><strong>16진수</strong><p><code>content:"|50 4F 53 54|";</code></p></div>
<div class="card"><strong>혼합 표현</strong><p><code>content:"Location|3A 20|";</code></p></div>
</div>
<div class="note good"><code>content:"POST";</code>와 <code>content:"|50 4F 53 54|";</code>는 같은 바이트를 탐지합니다.</div>
</section>
<section class="panel" id="sticky">
<span class="badge">④ Sticky Buffer</span>
<h2>HTTP에서 필요한 영역만 선택</h2>
<div class="codebox"><button class="copy">복사</button><pre><code>alert http $EXTERNAL_NET any -> $HOME_NET any (
msg:"HTTP POST";
http_method;
content:"POST";
sid:2000003;
rev:1;
)</code></pre></div>
<div class="grid">
<div class="card"><strong>http_method</strong><p>GET, POST 등 메서드</p></div>
<div class="card"><strong>http_uri</strong><p>/dvwa/login.php 등 URI</p></div>
<div class="card"><strong>http_header</strong><p>Host, Cookie, User-Agent 등 헤더</p></div>
<div class="card"><strong>http_client_body</strong><p>POST 요청 본문</p></div>
<div class="card"><strong>http_stat_code</strong><p>200, 302, 404 등 응답 코드</p></div>
</div>
<h3>Snort 3 예시</h3>
<div class="codebox"><button class="copy">복사</button><pre><code>http_method;
content:"POST";
http_uri;
content:"/dvwa/login.php";</code></pre></div>
<div class="note warn"><strong>버전 주의:</strong> Snort 2는 <code>content</code> 뒤에 HTTP modifier를 붙이고, Snort 3는 Sticky Buffer를 먼저 선언합니다. Suricata는 점 표기법을 사용합니다.</div>
</section>
<section class="panel" id="flow">
<span class="badge">⑤ Flow</span>
<h2>세션 상태와 통신 방향 제한</h2>
<div class="codebox"><button class="copy">복사</button><pre><code>alert http $EXTERNAL_NET any -> $HOME_NET any (
msg:"HTTP Request";
flow:established,to_server;
sid:2000004;
rev:1;
)</code></pre></div>
<div class="grid">
<div class="card"><strong>established</strong><p>연결이 성립된 TCP 세션에 속한 패킷만 검사합니다.</p></div>
<div class="card"><strong>to_server</strong><p>클라이언트에서 서버로 가는 요청 방향입니다.</p></div>
<div class="card"><strong>to_client</strong><p>서버에서 클라이언트로 가는 응답 방향입니다.</p></div>
</div>
<div class="diagram">Client Server
SYN ------------------------->
<-------------------- SYN/ACK
ACK ------------------------->
HTTP Request = to_server
HTTP Response = to_client</div>
<p>Snort의 TCP 세션 추적기는 SYN, SYN/ACK, ACK와 양방향 패킷을 바탕으로 세션 상태와 클라이언트·서버 방향을 관리합니다.</p>
<div class="note warn">SPAN에서 한쪽 방향만 수집하거나 세션 중간부터 보기 시작하면 <code>established</code> 판단이 제한될 수 있습니다.</div>
</section>
<section class="panel" id="flowbits">
<span class="badge">⑥ Flowbits</span>
<h2>같은 TCP 세션에서 앞선 상태 기억</h2>
<h3>Rule 1 — 로그인 시도 저장</h3>
<div class="codebox"><button class="copy">복사</button><pre><code>alert http $EXTERNAL_NET any -> $HOME_NET any (
msg:"Login Attempt";
flow:established,to_server;
http_method;
content:"POST";
flowbits:set,dvwa.login;
flowbits:noalert;
sid:2000005;
rev:1;
)</code></pre></div>
<h3>Rule 2 — 로그인 실패 응답 확인</h3>
<div class="codebox"><button class="copy">복사</button><pre><code>alert http $HOME_NET any -> $EXTERNAL_NET any (
msg:"Login Failed";
flow:established,to_client;
flowbits:isset,dvwa.login;
http_stat_code;
content:"302";
flowbits:unset,dvwa.login;
sid:2000006;
rev:1;
)</code></pre></div>
<table>
<tr><th>옵션</th><th>역할</th></tr>
<tr><td><code>set</code></td><td>현재 세션에 상태를 저장</td></tr>
<tr><td><code>isset</code></td><td>상태가 저장되어 있는지 확인</td></tr>
<tr><td><code>unset</code></td><td>저장된 상태 제거</td></tr>
<tr><td><code>noalert</code></td><td>상태만 저장하고 첫 번째 룰의 Alert는 숨김</td></tr>
</table>
<div class="diagram">POST 로그인 시도 → set
302 응답 → isset 확인 → Alert → unset</div>
</section>
<section class="panel" id="extra">
<span class="badge">⑦ 기타 탐지 옵션</span>
<h2>문자열 조건을 더 정확하게 제한</h2>
<div class="option-list">
<div class="option"><h4>nocase</h4><p>영문 대소문자를 구분하지 않습니다.</p><code>content:"admin"; nocase;</code></div>
<div class="option"><h4>pcre</h4><p>정규표현식으로 복잡한 패턴을 검사합니다.</p><code>pcre:"/user(name)?=/i";</code></div>
<div class="option"><h4>offset</h4><p>버퍼 시작점에서 몇 바이트 뒤부터 검색할지 지정합니다.</p><code>content:"POST"; offset:0;</code></div>
<div class="option"><h4>depth</h4><p>검색 시작점부터 최대 몇 바이트까지 볼지 제한합니다.</p><code>content:"POST"; depth:4;</code></div>
<div class="option"><h4>distance</h4><p>이전 content가 끝난 지점에서 몇 바이트 뒤부터 다음 검색을 시작할지 지정합니다.</p><code>distance:0;</code></div>
<div class="option"><h4>within</h4><p>이전 content 이후 몇 바이트 안에서 다음 content를 찾을지 지정합니다.</p><code>within:100;</code></div>
</div>
<h3>offset / depth 예시</h3>
<div class="codebox"><button class="copy">복사</button><pre><code>content:"POST";
offset:0;
depth:4;</code></pre></div>
<p>버퍼의 첫 번째 바이트부터 4바이트 범위 안에서 <code>POST</code>를 찾습니다.</p>
<h3>distance / within 예시</h3>
<div class="codebox"><button class="copy">복사</button><pre><code>content:"Location|3A 20|";
content:"login.php";
distance:0;
within:100;</code></pre></div>
<p><code>Location: </code>이 끝난 직후부터 100바이트 안에서 <code>login.php</code>를 찾습니다.</p>
<div class="note"><strong>구분:</strong> <code>offset/depth</code>는 버퍼 시작점을 기준으로 하고, <code>distance/within</code>은 이전 content 매칭 지점을 기준으로 합니다.</div>
</section>
<section class="panel" id="manage">
<span class="badge">⑧ 관리 옵션</span>
<h2>Alert 식별과 룰 버전 관리</h2>
<div class="codebox"><button class="copy">복사</button><pre><code>alert tcp any any -> any 80 (
msg:"HTTP Access Detected";
classtype:web-application-activity;
sid:2000101;
rev:2;
)</code></pre></div>
<table>
<tr><th>옵션</th><th>의미</th><th>운영 방법</th></tr>
<tr><td><code>msg</code></td><td>Alert에 표시되는 설명</td><td>무엇을 탐지했는지 한눈에 알 수 있게 작성</td></tr>
<tr><td><code>sid</code></td><td>룰의 고유 식별 번호</td><td>다른 룰과 중복되지 않게 관리</td></tr>
<tr><td><code>rev</code></td><td>룰 수정 버전</td><td>룰 내용 변경 시 1씩 증가</td></tr>
<tr><td><code>classtype</code></td><td>공격 유형 분류</td><td>Alert 우선순위와 분류에 활용</td></tr>
</table>
<div class="note good"><strong>예시 정책:</strong> <code>1xxxxxx</code> 일반 상황 · <code>2xxxxxx</code> 웹 서버 · <code>3xxxxxx</code> 네트워크 탐지</div>
</section>
<section class="panel" id="versions">
<span class="badge">⑨ Snort 2 / Snort 3 / Suricata</span>
<h2>같은 의미, 다른 HTTP 버퍼 문법</h2>
<p class="lead">가장 큰 차이는 HTTP 전용 버퍼의 이름과 <code>content</code> 앞뒤 순서입니다.</p>
<h3>POST Method 탐지</h3>
<div class="grid">
<div class="card"><span class="pill sn2">Snort 2.9</span>
<div class="codebox"><button class="copy">복사</button><pre><code>alert tcp any any -> any 80 (
content:"POST";
http_method;
sid:1001;
rev:1;
)</code></pre></div></div>
<div class="card"><span class="pill sn3">Snort 3</span>
<div class="codebox"><button class="copy">복사</button><pre><code>alert http any any -> any any (
http_method;
content:"POST";
sid:1001;
rev:1;
)</code></pre></div></div>
<div class="card"><span class="pill suri">Suricata</span>
<div class="codebox"><button class="copy">복사</button><pre><code>alert http any any -> any any (
http.method;
content:"POST";
sid:1001;
rev:1;
)</code></pre></div></div>
</div>
<h3>URI 탐지</h3>
<table>
<tr><th>엔진</th><th>문법</th></tr>
<tr><td><span class="pill sn2">Snort 2</span></td><td><code>content:"/dvwa/login.php"; http_uri;</code></td></tr>
<tr><td><span class="pill sn3">Snort 3</span></td><td><code>http_uri; content:"/dvwa/login.php";</code></td></tr>
<tr><td><span class="pill suri">Suricata</span></td><td><code>http.uri; content:"/dvwa/login.php";</code></td></tr>
</table>
<h3>HTTP 상태 코드 탐지</h3>
<table>
<tr><th>엔진</th><th>문법</th></tr>
<tr><td><span class="pill sn2">Snort 2</span></td><td><code>content:"302"; http_stat_code;</code></td></tr>
<tr><td><span class="pill sn3">Snort 3</span></td><td><code>http_stat_code; content:"302";</code></td></tr>
<tr><td><span class="pill suri">Suricata</span></td><td><code>http.stat_code; content:"302";</code></td></tr>
</table>
<h3>요청 Body 탐지</h3>
<table>
<tr><th>엔진</th><th>문법</th></tr>
<tr><td><span class="pill sn2">Snort 2</span></td><td><code>content:"username="; http_client_body;</code></td></tr>
<tr><td><span class="pill sn3">Snort 3</span></td><td><code>http_client_body; content:"username=";</code></td></tr>
<tr><td><span class="pill suri">Suricata</span></td><td><code>http.request_body; content:"username=";</code></td></tr>
</table>
<div class="note bad">
<strong>외우는 법:</strong><br>
Snort 2 = <strong>content 먼저, modifier 나중</strong><br>
Snort 3 = <strong>언더바 Sticky Buffer 먼저</strong><br>
Suricata = <strong>점(.) Sticky Buffer 먼저</strong>
</div>
<h3>로그인 POST 룰 전체 비교</h3>
<div class="grid">
<div class="card"><span class="pill sn2">Snort 2.9</span>
<div class="codebox"><button class="copy">복사</button><pre><code>alert tcp $EXTERNAL_NET any -> $HOME_NET 80 (
flow:established,to_server;
content:"POST"; http_method;
content:"/dvwa/login.php"; http_uri;
sid:2100001; rev:1;
)</code></pre></div></div>
<div class="card"><span class="pill sn3">Snort 3</span>
<div class="codebox"><button class="copy">복사</button><pre><code>alert http $EXTERNAL_NET any -> $HOME_NET any (
flow:established,to_server;
http_method; content:"POST";
http_uri; content:"/dvwa/login.php";
sid:2100001; rev:1;
)</code></pre></div></div>
<div class="card"><span class="pill suri">Suricata</span>
<div class="codebox"><button class="copy">복사</button><pre><code>alert http $EXTERNAL_NET any -> $HOME_NET any (
flow:established,to_server;
http.method; content:"POST";
http.uri; content:"/dvwa/login.php";
sid:2100001; rev:1;
)</code></pre></div></div>
</div>
</section>
</main>
<footer>발표용 Snort 룰 옵션 인터랙티브 가이드</footer>
</div>
<script>
const tabs=document.querySelectorAll('.tab'), panels=document.querySelectorAll('.panel');
tabs.forEach(t=>t.onclick=()=>{
tabs.forEach(x=>x.classList.remove('active')); panels.forEach(x=>x.classList.remove('active'));
t.classList.add('active'); document.getElementById(t.dataset.id).classList.add('active');
window.scrollTo({top:0,behavior:'smooth'});
});
document.querySelectorAll('.copy').forEach(b=>b.onclick=async()=>{
const text=b.nextElementSibling.innerText;
try{await navigator.clipboard.writeText(text)}catch(e){
const ta=document.createElement('textarea');ta.value=text;document.body.appendChild(ta);ta.select();document.execCommand('copy');ta.remove();
}
b.textContent='복사됨';b.classList.add('done');
setTimeout(()=>{b.textContent='복사';b.classList.remove('done')},1400);
});
</script>
</body>
</html>