[WUISP 25] Wazuh IP 차단 구현 (ModSecurity)

Entity08·2025년 12월 24일

WUISP 25

목록 보기
2/4

ModSecurity 탐지 기반 차단

Wazuh Manager측 수정 파일

/var/ossec/etc/rules/local_rules.xml 내용 추가

<group name="modsecurity,attack,auto_block">
  
  <rule id="100201" level="10">
    <match>ModSecurity: Access denied</match>
    <match>403</match>
    
    <description>ModSecurity 403 access denied - auto block</description>
  </rule>
  
</group>

/var/ossec/etc/ossec.conf 내용 추가

<command>
  <name>wazuh-nft-block</name>
  <executable>wazuh-nft-block.sh</executable>
  <timeout_allowed>yes</timeout_allowed>
</command>
<active-response>
  <command>wazuh-nft-block</command>
  <location>defined-agent</location>
  <agent_id>alerts.json에서 확인되는 agent id</agent_id>
  <rules_id>100201</rules_id>
  <timeout>600</timeout>
</active-response>

수정 후 Manager, Agent 모두 재시작

Wazuh Agent(피해자) 측 수정 파일

/var/ossec/active-response/bin/wazuh-nft-block.sh 파일 생성

#!/usr/bin/env bash

set -euo pipefail

# -------- Config --------
TABLE_FAMILY="inet"
TABLE_NAME="filter"
CHAIN_NAME="input"
SET_NAME="wazuh_block"

DEFAULT_TIMEOUT_SEC=600

# -------- Helpers --------

read_stdin_json() {
  local s=""
  # Read entire stdin (could be multi-line)
  while IFS= read -r line; do
    s+="$line"
  done
  printf "%s" "$s"
}

extract_json_value() {
  # Usage: extract_json_value "$JSON" "key"
  # Tries jq if available; otherwise uses a minimal sed parser for simple string fields.
  local json="$1"
  local key="$2"

  if command -v jq >/dev/null 2>&1; then
    # Many Wazuh AR JSON payloads are flat; support both top-level and nested "parameters" variants.
    jq -r --arg k "$key" '
      (.. | objects | select(has($k)) | .[$k]) // empty
    ' <<<"$json" | head -n1
  else
    # Minimal fallback: "key":"value"
    echo "$json" | sed -nE 's/.*"'"$key"'"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p' | head -n1
  fi
}

is_valid_ipv4() {
  local ip="$1"
  [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || return 1
  # ensure each octet <= 255
  IFS='.' read -r o1 o2 o3 o4 <<<"$ip"
  for o in "$o1" "$o2" "$o3" "$o4"; do
    [[ "$o" -ge 0 && "$o" -le 255 ]] || return 1
  done
  return 0
}

nft_ensure_base() {
  # Ensure table/chain/set and drop rule exist.
  nft list table "$TABLE_FAMILY" "$TABLE_NAME" >/dev/null 2>&1 || nft add table "$TABLE_FAMILY" "$TABLE_NAME"

  # Ensure input chain exists with hook (only if not already present)
  if ! nft list chain "$TABLE_FAMILY" "$TABLE_NAME" "$CHAIN_NAME" >/dev/null 2>&1; then
    nft add chain "$TABLE_FAMILY" "$TABLE_NAME" "$CHAIN_NAME" "{ type filter hook input priority 0; policy accept; }"
  fi

  # Ensure set exists (timeout flag is important)
  if ! nft list set "$TABLE_FAMILY" "$TABLE_NAME" "$SET_NAME" >/dev/null 2>&1; then
    nft add set "$TABLE_FAMILY" "$TABLE_NAME" "$SET_NAME" "{ type ipv4_addr; flags timeout; }"
  fi

  # Ensure drop rule referencing the set exists (insert near top)
  if ! nft list chain "$TABLE_FAMILY" "$TABLE_NAME" "$CHAIN_NAME" | grep -q "ip saddr @$SET_NAME drop"; then
    nft insert rule "$TABLE_FAMILY" "$TABLE_NAME" "$CHAIN_NAME" ip saddr "@$SET_NAME" drop
  fi
}

nft_block_ip() {
  local ip="$1"
  local timeout_sec="$2"
  nft_ensure_base
  # Add with timeout. If already exists, ignore error.
  nft add element "$TABLE_FAMILY" "$TABLE_NAME" "$SET_NAME" "{ $ip timeout ${timeout_sec}s }" 2>/dev/null || true
}

nft_unblock_ip() {
  local ip="$1"
  nft_ensure_base
  nft delete element "$TABLE_FAMILY" "$TABLE_NAME" "$SET_NAME" "{ $ip }" 2>/dev/null || true
}

iptables_block_ip() {
  local ip="$1"
  # Insert only if not already present
  iptables -C INPUT -s "$ip" -j DROP 2>/dev/null || iptables -I INPUT -s "$ip" -j DROP
}

iptables_unblock_ip() {
  local ip="$1"
  iptables -D INPUT -s "$ip" -j DROP 2>/dev/null || true
}

# -------- Main --------
JSON="$(read_stdin_json)"
[[ -n "${JSON:-}" ]] || exit 0

SRCIP="$(extract_json_value "$JSON" "srcip")"
ACTION="$(extract_json_value "$JSON" "command")"
# Some payloads use "action" instead of "command"
if [[ -z "${ACTION:-}" ]]; then
  ACTION="$(extract_json_value "$JSON" "action")"
fi

# Some Wazuh payloads include timeout; if present use it, else default
TIMEOUT="$(extract_json_value "$JSON" "timeout")"
if [[ -z "${TIMEOUT:-}" ]]; then
  TIMEOUT="$DEFAULT_TIMEOUT_SEC"
fi
# sanitize timeout
if ! [[ "$TIMEOUT" =~ ^[0-9]+$ ]]; then
  TIMEOUT="$DEFAULT_TIMEOUT_SEC"
fi

# Must have a srcip to act on
if [[ -z "${SRCIP:-}" ]]; then
  log "No srcip found in payload: $JSON"
  exit 0
fi

# Only handle IPv4 here (can extend to IPv6 later)
if ! is_valid_ipv4 "$SRCIP"; then
  log "Invalid IPv4 srcip: $SRCIP"
  exit 0
fi

# Decide add vs remove
# Depending on Wazuh version/config, ACTION might be: "add", "delete", "remove", "stop", etc.
# We treat anything containing delete/remove/stop as unblock, otherwise block.
if echo "$JSON" | grep -Eqi '"(delete|remove|del|stop)"'; then
  if command -v nft >/dev/null 2>&1; then
    nft_unblock_ip "$SRCIP"
  elif command -v iptables >/dev/null 2>&1; then
    iptables_unblock_ip "$SRCIP"
  fi
  log "Unblocked $SRCIP"
else
  if command -v nft >/dev/null 2>&1; then
    nft_block_ip "$SRCIP" "$TIMEOUT"
  elif command -v iptables >/dev/null 2>&1; then
    iptables_block_ip "$SRCIP"
  else
    log "Neither nft nor iptables found; cannot block $SRCIP"
    exit 0
  fi
  log "Blocked $SRCIP for ${TIMEOUT}s"
fi

exit 0

생성 후 권한 수정 및 재시작

sudo install -m 750 -o root -g wazuh wazuh-nft-block.sh /var/ossec/active-response/bin/wazuh-nft-block.sh
sudo systemctl restart wazuh-agent
profile
되면 한다

0개의 댓글