Journey to Security/네트워크

SOAR 구축 실습: Snort 경보 기반 자동 침해대응 플레이북 엔진

Cordilog 2026. 6. 18. 20:00

Snort IPS가 공격을 탐지하면 로그를 확인해서 수동으로 차단 규칙을 넣고, 팀에 알리고, 인시던트 티켓을 만드는일반적인 운영 흐름을 SOAR(Security Orchestration, Automation and Response)로 자동화할 수 있다. 

 

이번 실습에서는 Python Flask 기반의 "Mini-SOAR" 플레이북 엔진을 구축해 본다.

Snort alert가 발생하면 자동으로 iptables 차단, Slack 알림, Notion 인시던트 티켓 생성까지 이어지는 전체 파이프라인을 단계별로 실습한다.

 

1. 전체 아키텍처와 네트워크 구성

실습의 전체 아키텍처는 아래와 같다.

 

Kali(공격자)가 외부에서 DMZ(웹서버)을 공격하면, 방화벽/IPS 서버의 Snort IPS가 이를 탐지하고 alert 로그를 생성한다. IPS 서버에 배치된 전달 스크립트(snort_to_soar.sh)가 이 alert를 SOAR 서버의 Flask API(/api/alerts)로 POST 전송한다. SOAR 서버는 수신한 alert를 파싱하여 플레이북 조건과 매칭하고, 조건에 맞으면 SSH로 fwnids에 접속해 iptables FORWARD 체인에 차단 규칙을 삽입하고, Slack Webhook으로 보안팀에 알림을 보내고, Notion API로 인시던트 티켓을 자동 생성한다.

 

1️⃣ VM 구성과 네트워크 대역

VM IP주소 역할
Kali 192.168.100.3 공격자
IPS 192.168.100.6 (ens160, 외부) / 10.10.11.253 (ens224, DMZ) 방화벽 + Snort IPS
DMZ 10.10.11.10 웹서버 (DMZ)
SOAR 192.168.100.35 Mini-SOAR 엔진

Kali → DMZ 트래픽은 fwnids의 PREROUTING에서 DNAT(192.168.100.10 → 10.10.11.10)된 후 FORWARD 체인을 통과한다.

따라서 차단 규칙은 iptables -I FORWARD -s <공격자IP> -j DROP으로 삽입해야 한다.

 

2️⃣ IPS 서버의 기존 iptables 규칙

현재 IPS 서버에 적용되어 있는 규칙은 다음과 같다.

# filter 테이블
*filter
-A INPUT -m state --state INVALID -j DROP
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 443 -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT
-A INPUT -s 192.168.100.1/32 -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -j DROP

# nat 테이블
*nat
-A PREROUTING -d 192.168.100.10/32 -j DNAT --to-destination 10.10.11.10
-A POSTROUTING -s 10.10.11.10/32 -j SNAT --to-source 192.168.100.10

INPUT 체인은 HTTPS, HTTP, 특정 IP의 SSH만 허용하고 나머지는 모두 DROP하는 화이트리스트 구조다.

NAT 테이블은 외부에서 192.168.100.10으로 오는 트래픽을 DMZ의 10.10.11.10으로 DNAT하고, 반대 방향은 SNAT으로 처리한다.

 

2. SOAR 서버 → IPS 서버 SSH 연동

SOAR가 fwnids의 iptables를 원격으로 조작하려면, SSH 접속이 자동으로(패스워드 입력 없이) 이루어져야 한다.

공개키 인증을 설정하고, 보안을 위해 실행 가능한 명령을 제한하는 것이 이 단계의 목표다.

1️⃣ IPS에 SSH 접근 허용 규칙 추가

기존 INPUT 체인의 마지막이 -j DROP이므로, 그 앞에 SOAR VM의 SSH 접속 허용 규칙을 삽입해야 한다.

# DROP 규칙이 5번째이므로 5번 위치에 삽입
iptables -I INPUT 5 -s 192.168.100.35/32 -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
iptables-save > /etc/sysconfig/iptables

2️⃣ 공개키 인증 설정

SOAR VM에서 SSH 키 쌍을 생성하고, IPS 서버에 공개키를 복사한다.

# SOAR 서버에서 실행
ssh-keygen -t rsa -b 4096 -N "" -f ~/.ssh/id_rsa
ssh-copy-id root@192.168.100.6

-N "" 옵션: 패스프레이즈를 설정하면 SSH 접속 시마다 입력이 필요한데, SOAR는 자동으로 IPS 서버에 접속해야 하므로 비워둔다.

복사 후 패스워드 없이 접속되는지 확인한다.

3️⃣ 제한된 명령 스크립트 배포

공개키 인증만 되면 기본적으로는 SOAR 머신이 IPS 서버에서 아무 명령이나 실행할 수 있다.

보안을 위해 authorized_keys의 command= 옵션으로 실행 가능한 명령을 단 하나의 스크립트로 제한한다.

먼저 IPS 서버에 IP 차단 전용 스크립트를 생성한다.

# fwnids에서 실행
cat > /usr/local/bin/soar_iptables.sh << 'EOF'
#!/bin/bash
IP=$(echo "$SSH_ORIGINAL_COMMAND" | grep -oP '^\d+\.\d+\.\d+\.\d+$')
if [ -n "$IP" ]; then
    /sbin/iptables -I FORWARD -s "$IP" -j DROP
    echo "Blocked $IP in FORWARD chain"
else
    echo "Invalid command" >&2
    exit 1
fi
EOF
chmod 755 /usr/local/bin/soar_iptables.sh

이 스크립트는 SSH_ORIGINAL_COMMAND(SSH 접속 시 전달된 명령)에서 IP 주소 형태의 문자열만 추출하여 FORWARD 체인에 DROP 규칙을 삽입한다.

IP 형태가 아닌 입력은 모두 거부한다.

다음으로 authorized_keyscommand= 제한을 적용한다.

sed -i 's|^ssh-rsa|command="/usr/local/bin/soar_iptables.sh",no-port-forwarding,no-X11-forwarding ssh-rsa|' /root/.ssh/authorized_keys

#적용 후 확인
cat /root/.ssh/authorized_keys

▲ 적용 후 — 키 앞에 command 제한과 no-port-forwarding/no-X11-forwarding 옵션이 붙음

 

이렇게 하면 SOAR VM에서 SSH로 접속할 때 어떤 명령을 전달하든, 실제로는 soar_iptables.sh만 실행된다.

no-port-forwarding, no-X11-forwarding포트 포워딩과 X11 포워딩도 차단하여 SSH 터널링 남용을 방지한다.

 

SOAR 머신에서 테스트한다.

ssh root@192.168.100.6 "1.2.3.4"
# Blocked 1.2.3.4 in FORWARD chain 출력 시 성공

▲ SOAR → fwnids 차단 명령 — "Blocked 1.2.3.4 in FORWARD chain" 출력으로 자동화 성공

테스트 후 IPS 서버에서 규칙을 정리한다.

iptables -D FORWARD -s 1.2.3.4 -j DROP
iptables-save > /etc/sysconfig/iptables

3. Claude Code로 Mini-SOAR 엔진 생성

SOAR 엔진의 코드 골격은 Claude Code로 생성했다.

실습 환경, 네트워크 구조, SSH 연동 상태, 필요 기능을 상세히 기술한 프롬프트를 전달해, 디렉터리 구조와 기본 코드를 한 번에 만들도록 했다.

 

Claude Code Shellshock·SQL Injection·Port Scan 세 가지 플레이북을 YAML로 생성했다.

각 플레이북은 시그니처 정규식·심각도 조건과 실행할 액션 목록을 담는다.

 

SOAR 머신에 구축할 Mini-SOAR 프로젝트의 디렉터리 구조는 다음과 같다.

mini-soar/
├── app.py               ← Flask API (5000 포트)
├── snort_parser.py      ← Snort 경보 파싱 (fast/full 포맷)
├── playbook_engine.py   ← YAML 조건 매칭 + 액션 실행
├── actions/
│   ├── __init__.py      ← 액션 디스패처
│   ├── block.py         ← SSH → fwnids iptables 차단
│   ├── slack.py         ← Slack Webhook POST
│   └── notion.py        ← Notion API 인시던트 티켓 생성
├── playbooks/
│   ├── shellshock.yml   ← 차단 + Slack + Notion
│   ├── sql_injection.yml← Slack + Notion
│   └── port_scan.yml    ← 로그 + Slack
├── requirements.txt
└── .env.example

각 모듈의 역할을 정리하면 이렇다.

 
 
  • app.py : Flask REST API 서버로, /api/alerts 엔드포인트에서 Snort alert 텍스트를 수신한다.
  • snort_parser.py : 수신한 alert 문자열을 파싱하여 공격자 IP, 대상 IP, SID(시그니처 ID), 심각도(priority) 등을 구조화된 데이터로 추출한다.
  • playbook_engine.py : 파싱된 데이터를 playbooks/ 디렉터리의 YAML 파일들과 매칭한다. 시그니처 이름의 regex 패턴, 심각도, 대상 IP 등의 조건이 맞으면 해당 플레이북에 정의된 액션 목록을 순차 실행한다.
  • actions/block.py : SSH로 fwnids에 접속하여 iptables -I FORWARD -s <IP> -j DROP 명령을 실행한다.
  • actions/slack.py : Slack Incoming Webhook URL로 경보 내용을 POST 전송한다.
  • actions/notion.py : Notion API를 호출하여 인시던트 DB에 티켓(페이지)을 자동 생성한다.

환경변수(Slack Webhook URL, Notion API 키, SSH 키 경로 등)는 .env 파일로 관리하며, python-dotenv로 로딩한다.

 

 

4. 단계별 테스트: curl로 테스트용 가짜 alert 전송

SOAR 코드가 완성되면, Snort 없이도 curl로 가짜 Snort alert를 직접 전송하여 각 기능을 단독 테스트할 수 있다.

이 접근 방식이 중요한 이유는, 전체 파이프라인을 한 번에 테스트하면 어디서 문제가 생겼는지 파악하기 어렵기 때문이다.

 

1️⃣ 환경변수 설정 및 서버 실행

cd /home/soaradmin/mini-soar
cp .env.example .env
vi .env

# 먼저 SSH 관련만 채움 (Slack, Notion은 나중에)
SSH_KEY_PATH=/home/soaradmin/.ssh/id_rsa
FIREWALL_HOST=192.168.100.6

source venv/bin/activate
python app.py

 

서버가 뜨면 플레이북 3개(Port Scan, Shellshock, SQL Injection)가 로드되었다는 로그와 함께 0.0.0.0:5000에서 대기 중이라는 메시지가 출력된다.

▲ SOAR Flask API 기동 — 플레이북 3개 로드, 0.0.0.0:5000 대기

2️⃣ iptables 차단 테스트

새 터미널에서 curl로 가짜 Shellshock alert를 전송한다.

curl -X POST http://192.168.100.35:5000/api/alerts \
  -H "Content-Type: text/plain" \
  --data '[**] [1:2014:1] SHELLSHOCK HTTP request [**] [Classification: Web App Attack] [Priority: 1] {TCP} 192.168.100.3:54321 -> 10.10.11.10:80'

 

SOAR 서버 로그에 파싱 → 플레이북 매칭 → 차단 실행 과정이 찍힌다.

▲ SOAR 서버 로그 — 경보 수신 → Shellshock 플레이북 실행 → 192.168.100.3 차단

 

IPS에서 확인하면 FORWARD 체인에 차단 규칙이 자동 삽입되어 있다.

iptables -L FORWARD -n
Chain FORWARD (policy ACCEPT)
target     prot opt source               destination
DROP       all  --  192.168.100.3        0.0.0.0/0

▲ fwnids FORWARD 체인 — 192.168.100.3 DROP 규칙이 자동 등록됨

 

테스트 성공 후 규칙을 정리한다.

iptables -D FORWARD -s 192.168.100.3 -j DROP
 

5. Slack 연동

1️⃣ Slack Webhook URL 발급 절차

2️⃣ .env에 추가

vi /home/soaradmin/mini-soar/.env
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T.../B.../xxx

3️⃣ 테스트

서버를 재시작(Ctrl+C → python app.py)한 후, 다른 터미널에서 동일한 curl 명령을 실행하면 Slack #soar-alerts 채널에 공격자 IP, 대상 IP, 시그니처, 심각도가 포함된 알림이 도착한다.

▲ Slack #soar-alerts 채널에 공격 정보가 정리된 알림이 도착한다.

 

IPS 서버에서 테스트 차단 규칙을 정리한 뒤 Notion 연동으로 넘어간다.

iptables -D FORWARD -s 192.168.100.3 -j DROP

6. Notion 연동

1️⃣ Notion Integration 생성

2️⃣ 인시던트 DB 생성

Notion에서 새 데이터베이스를 만들고 다음 컬럼을 추가한다.

컬럼명 타입 비고
Name 제목 기본 포함
Status 선택 Open / Investigating / Closed
Priority 선택 High / Medium / Low
Attacker IP 텍스트  
Target IP 텍스트  
Signature 텍스트  
Date 날짜  

 

 

DB 페이지 우측 상단 ··· → "연결" → Mini-SOAR Integration을 연결한다.

3️⃣ DB ID 추출

DB 페이지 URL의 구조는 다음과 같다.

https://app.notion.com/p/382742***********************897?v=382742573a8680e980c3...

?v= 앞의 32자리 문자열이 DB ID다.

 

4️⃣ .env에 추가

NOTION_API_KEY=ntn_xxxxxxxxxxxxx
NOTION_DB_ID=382742573a86802fa09ae48444b98897

서버를 재시작 후 curl 테스트를 실행하면, iptables 차단 + Slack 알림 + Notion 티켓 3가지 액션이 모두 동작하는 것을 확인할 수 있다.

 

▲ Notion DB 에 티켓이 자동 생성된다.

7. Snort 연동 - alert 자동 전달

이제 curl 수동 테스트가 아닌, 실제 Snort alert를 자동으로 SOAR에 전달하는 파이프라인을 구축한다.

1️⃣ IPS 서버에 전달 스크립트 생성

cat > /usr/local/bin/snort_to_soar.sh << 'EOF'
#!/bin/bash
SOAR_URL="http://192.168.100.35:5000/api/alerts"
tail -F /var/log/snort/alert | while IFS= read -r line; do
    [[ "$line" == *\[\*\*\]* ]] || continue
    curl -s -X POST "$SOAR_URL" \
        -H "Content-Type: text/plain" --data "$line" &
done
EOF
chmod 755 /usr/local/bin/snort_to_soar.sh

이 스크립트의 동작 원리는 다음과 같다.

  • tail -F : Snort의 alert 파일을 실시간 감시한다. -F는 파일이 로테이션(삭제 후 재생성)되어도 계속 추적한다.
  • [[ "$line" == *\[\*\*\]* ]] : Snort alert의 시그니처 줄은 [**]로 시작한다. 이 패턴이 포함된 줄만 필터링한다.
  • curl ... & : 백그라운드로 POST 전송하여 다음 alert 처리를 블로킹하지 않는다.

 

2️⃣ systemd 서비스로 등록

스크립트를 수동 실행이 아닌 시스템 서비스로 등록하면 부팅 시 자동 시작, 크래시 시 자동 재시작이 보장된다.

cat > /etc/systemd/system/snort-to-soar.service << 'EOF'
[Unit]
Description=Snort Alert to SOAR Forwarder
After=network.target

[Service]
ExecStart=/usr/local/bin/snort_to_soar.sh
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now snort-to-soar
systemctl status snort-to-soar

8. Snort 룰 작성과 NFQUEUE 설정

End-to-End 테스트에 앞서, IPS 서버에 Shellshock 탐지 룰과 Snort 인라인 모드용 NFQUEUE 규칙을 설정한다.

1️⃣ Snort 룰 작성

vi /etc/snort/rules/local.rules
alert tcp any any -> $HOME_NET any (msg:"SHELLSHOCK"; content:"() { "; priority:1; sid:1000002; rev:2;)

룰의 각 요소를 분석하면 다음과 같다.

 

  • 룰 헤더: alert tcp any any -> $HOME_NET any : 모든 출발지에서 HOME_NET 대역으로 향하는 TCP 패킷을 대상으로 경보를 생성한다.
  • content:"() { " : 패킷 페이로드에서 이 문자열을 검색한다. Shellshock(CVE-2014-6271)의 핵심 패턴이다. Bash가 환경변수에서 함수 정의를 파싱할 때 () {로 시작하는 문자열 뒤의 명령을 무조건 실행하는 취약점으로, 공격자가 HTTP User-Agent 같은 헤더에 () { :; }; /bin/bash -c "악성명령"을 삽입하면 CGI를 통해 Bash가 이를 환경변수로 받아 실행한다.
  • priority:1 : 심각도 1(high). SOAR 플레이북의 severity 분류에 사용된다.
  • sid:1000002 : 룰 고유번호. 로컬 룰은 1000001부터 부여한다.

2️⃣ NFQUEUE 설정

Snort를 인라인(IPS) 모드로 동작시키려면 FORWARD 체인의 트래픽을 NFQUEUE로 보내야 한다.

iptables -I FORWARD -j NFQUEUE --queue-num 0
iptables-save > /etc/sysconfig/iptables

NFQUEUE는 netfilter가 패킷을 커널 큐에 넣고 유저스페이스 프로그램(Snort)에 판정을 위임하는 메커니즘이다.

Snort는 큐에서 패킷을 꺼내 룰과 매칭한 후, 통과(ACCEPT) 또는 차단(DROP) 결정을 내린다.

 

9. End-to-End 테스트 - Kali에서 공격

이제 준비는 끝났고, 실제 공격부터 자동 대응까지의 전체 파이프라인을 테스트한다.

아래 시퀀스 다이어그램이 전체 흐름을 보여준다.

 

1️⃣ Snort 시작 (IPS)

snort -Q --daq nfq --daq-var queue=0 -c /etc/snort/snort.conf -A fast

 

옵션 설명
-Q 인라인(IPS) 모드 — 패킷을 판정 후 통과/차단
--daq nfq NFQUEUE DAQ 모듈 사용
--daq-var queue=0 큐 번호 0 (iptables 설정과 일치)
-A fast alert를 fast 포맷으로 /var/log/snort/alert에 기록

 

2️⃣ Kali에서 Shellshock 공격

curl -g -A '() { :; }; /bin/bash -c "echo vulnerable"' \
  http://192.168.100.10:6271/cgi-bin/vulnerable

-A 옵션으로 User-Agent 헤더에 Shellshock 페이로드(() { :; }; ...)를 삽입한다.

-g는 중괄호를 URL 글로빙으로 해석하지 않도록 하는 옵션이다.

 

3️⃣ 결과 확인

공격 실행 후 다음 4가지를 확인한다.

  • SOAR 서버 로그 : alert 수신, 파싱, 플레이북 매칭, 각 액션 실행 로그가 순차적으로 출력된다.
  • IPS 서버의 FORWARD 체인 : iptables -L FORWARD -n으로 192.168.100.3에 대한 DROP 규칙이 자동 삽입되었는지 확인한다.
  • Slack #soar-alerts : 공격자 IP, 대상 IP, 시그니처, 심각도가 포함된 알림 메시지가 도착한다.
  • Notion SOAR Incidents DB : 인시던트 티켓이 자동 생성되어 Status: Open, Priority: High로 기록된다.

▲ SOAR 서버 로그
▲ IPS 서버에 DROP 규칙 생성됨
▲ Slack에 알림 도착
▲ Notion Incidents 데이터베이스에 티켓 생성

 

 

4가지 모두 확인되면 End-to-End 테스트가 완료된 것이다.

Kali에서 이후 동일한 공격을 시도하면 FORWARD 체인의 DROP 규칙에 의해 패킷이 차단된다.

 

10. 정리: SOAR 동작 원리

이 실습에서 구축한 Mini-SOAR의 핵심 요소는 다음과 같다.

1️⃣ SOAR(Security Orchestration, Automation and Response)

보안 경보를 수신하면 사전 정의된 플레이북에 따라 대응을 자동화하는 시스템이다. 상용 SOAR(Splunk SOAR, Palo Alto XSOAR 등)는 수백 개의 통합 커넥터를 제공하지만, 이 실습에서는 iptables 차단, Slack 알림, Notion 티켓이라는 3가지 액션으로 핵심 개념을 구현했다.

2️⃣ 플레이북(Playbook)

"이런 경보가 오면 이런 순서로 대응하라"는 규칙을 정의한 문서(YAML)다. 조건(시그니처 패턴, 심각도, 대상 IP 등)과 액션(차단, 알림, 티켓 등)의 조합으로 구성된다.

3️⃣ SSH command= 제한

authorized_keys의 command= 옵션은 해당 키로 접속했을 때 어떤 명령을 전달하든 지정된 명령만 실행되도록 강제한다. 원래 전달된 명령은 SSH_ORIGINAL_COMMAND 환경변수에 담겨 스크립트의 인자로 활용할 수 있다. 이 방식으로 SOAR의 SSH 접근 권한을 "IP 차단"이라는 단일 기능으로 제한한다.

4️⃣ NFQUEUE

netfilter가 패킷 판정을 유저스페이스 프로그램에 위임하는 메커니즘이다. Snort가 인라인(IPS) 모드로 동작하려면 FORWARD 체인의 패킷이 NFQUEUE를 통해 Snort에 전달되어야 하고, Snort가 검사 후 ACCEPT/DROP 판정을 내린다.

 

결론

이 실습에서 구현한 Mini-SOAR는 상용 제품 대비 기능이 단순하지만, SOAR의 핵심 동작 원리(경보 수신, 파싱, 플레이북 매칭, 자동 대응)를 직접 코드로 구현하고 실제 공격 시나리오에서 End-to-End로 검증했다는 데 의미가 있다.

SSH command= 제한으로 원격 실행 권한을 최소화하고, REST API(Flask, Slack Webhook, Notion API)로 이기종 시스템을 연동하는 패턴도 실습해볼 수 있었다.