detecting-arp-poisoning-in-network-traffic について
IPとMACの対応関係の変化やGratuitous ARPなどの異常からARPポイズニングを検出するネットワークセキュリティスキルです。ARPWatch、スイッチのDynamic ARP Inspection、パケットキャプチャ、独自のPythonツールによる監視と分析を扱います。
主な機能
- 01ARPWatchによるARPマッピング監視
- 02Dynamic ARP Inspectionの適用
- 03Wiresharkフィルターでのパケット分析
- 04独自のPython ARP監視ツールの作成
- 05代表的なポイズニング兆候の把握
活用シーン
- 01ARPポイズニングの疑いの調査
- 02ネットワーク脅威ハンティングクエリの作成
- 03ARP異常の監視範囲の検証
- 04レイヤー2防御の見直し
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mukul975/ anthropic-cybersecurity-skills --skill detecting-arp-poisoning-in-network-trafficインストール後、Claude Code で/detecting-arp-poisoning-in-network-trafficを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
LICENSEreferences/api-reference.mdscripts/agent.py詳細
ネットワークトラフィックにおけるARPポイズニングの検知
概要
ARPポイズニング(ARPスプーフィング)は、攻撃者が偽造したARPメッセージを送信し、自身のMACアドレスを正規ホストのIPアドレスに関連付けるレイヤー2攻撃です。これにより、中間者(MitM)による傍受、セッションハイジャック、またはサービス拒否が可能になります。ARPには組み込みの認証メカニズムがないため、ブロードキャストドメイン内のどのデバイスでもARP応答を偽造できます。検知には、Gratuitous ARPの大量送信、IPとMACの対応関係の変化、重複IPアドレスなどの異常を見つけるため、ARPトラフィックを監視する必要があります。このスキルでは、ARPWatch、Dynamic ARP Inspection(DAI)、Wiresharkベースの分析、カスタムPython監視ツールなど、複数の検知レイヤーの導入方法を説明します。
使用する場面
- ネットワークトラフィックにおけるARPポイズニングの検知が必要なセキュリティインシデントを調査する場合
- この領域の検知ルールや脅威ハンティングクエリを作成する場合
- SOCアナリストが、この種の分析のための体系的な手順を必要とする場合
- 関連する攻撃手法に対するセキュリティ監視のカバレッジを検証する場合
前提条件
- 対象ネットワークセグメント(ブロードキャストドメイン)へのアクセス
- ARPWatchおよびカスタム監視ツールを実行するLinuxホスト
- Dynamic ARP Inspectionに対応したマネージドスイッチ(Cisco Catalyst、Aruba、Juniper EX)
- パケットキャプチャ用のWiresharkまたはtcpdump
- DHCPスヌーピングが設定済みであること(DAIの前提条件)
- ネットワーク監視インフラストラクチャ(SIEM、syslogサーバー)
基本概念
ARPプロトコルの基礎
ARPは、ローカルネットワークセグメント上でIPアドレスをMACアドレスに対応付けます。このプロトコルはステートレスに動作し、認証機能はありません。
Normal ARP Process:
1. Host A broadcasts: "Who has 10.0.1.1? Tell 10.0.1.100"
2. Router replies: "10.0.1.1 is at AA:BB:CC:DD:EE:01"
3. Host A caches the mapping
ARP Poisoning Attack:
1. Attacker sends unsolicited ARP reply to Host A:
"10.0.1.1 is at EV:IL:MA:CA:DD:RR" (attacker's MAC)
2. Host A updates cache, sends traffic to attacker
3. Attacker forwards to real gateway (MitM position)
攻撃の兆候
| 兆候 | 説明 | 深刻度 |
|---|---|---|
| MACアドレスの頻繁な切り替わり | 同じIPアドレスが異なるMACアドレスに短時間で対応付けられる | 高 |
| Gratuitous ARPの大量送信 | 複数のホストを対象とする未要求のARP応答 | 高 |
| 重複IPアドレス | 2つの異なるMACアドレスが同じIPアドレスを使用している | 重大 |
| 通常と異なるARPトラフィック量 | 1秒あたりのARPパケット数の急増 | 中 |
| DHCP以外の送信元からのARP | 不明なデバイスによる静的IPアドレスの申告 | 中 |
| ゲートウェイのMACアドレスの変化 | デフォルトゲートウェイのMACアドレスが変更された | 重大 |
ワークフロー
ステップ1:継続的な監視のためにARPWatchを導入する
# Install ARPWatch
sudo apt-get install -y arpwatch
# Configure ARPWatch
sudo vi /etc/default/arpwatch
# INTERFACES="eth0"
# ARGS="-N -p -i eth0 -f /var/lib/arpwatch/arp.dat"
# Start monitoring
sudo systemctl enable arpwatch
sudo systemctl start arpwatch
# View current ARP database
cat /var/lib/arpwatch/arp.dat
# Monitor logs for changes
tail -f /var/log/syslog | grep arpwatch
ARPWatchのアラートタイプ:
- new station - これまで確認されていないMACアドレス
- changed ethernet address - IPアドレスが別のMACアドレスに対応付けられた(ポイズニングの可能性)
- flip flop - MACアドレスが2つのアドレス間で交互に切り替わる(進行中の攻撃)
- reused old ethernet address - 以前確認された対応関係が再び使用された
ステップ2:スイッチでDynamic ARP Inspection(DAI)を設定する
Cisco Catalystの設定:
! Enable DHCP snooping (prerequisite for DAI)
ip dhcp snooping
ip dhcp snooping vlan 10,20,30
! Configure trusted ports (uplinks, DHCP servers)
interface GigabitEthernet1/0/1
description Uplink to Distribution
ip dhcp snooping trust
interface GigabitEthernet1/0/48
description DHCP Server
ip dhcp snooping trust
! Enable Dynamic ARP Inspection
ip arp inspection vlan 10,20,30
! Configure trusted ports for DAI
interface GigabitEthernet1/0/1
ip arp inspection trust
! Set rate limits to prevent ARP flood DoS
interface range GigabitEthernet1/0/2-47
ip arp inspection limit rate 15
! Enable additional validation checks
ip arp inspection validate src-mac dst-mac ip
! Configure ARP ACL for static IP devices (servers, printers)
arp access-list STATIC-ARP-ENTRIES
permit ip host 10.0.10.100 mac host 0011.2233.4455
permit ip host 10.0.10.101 mac host 0011.2233.4456
ip arp inspection filter STATIC-ARP-ENTRIES vlan 10
! Verify DAI status
show ip arp inspection vlan 10
show ip arp inspection statistics
show ip dhcp snooping binding
ステップ3:Wiresharkの検知フィルター
# Detect gratuitous ARP (sender and target IP are the same)
arp.src.proto_ipv4 == arp.dst.proto_ipv4
# Detect ARP replies (focus on unsolicited)
arp.opcode == 2
# Detect duplicate IP address claims
arp.duplicate-address-detected
# Detect ARP packets from specific attacker MAC
eth.src == ev:il:ma:ca:dd:rr
# Detect ARP storms (high volume)
# Use Statistics > I/O Graphs > Display filter: arp
# Detect gateway impersonation
arp.src.proto_ipv4 == 10.0.1.1 && arp.src.hw_mac != aa:bb:cc:dd:ee:01
ステップ4:カスタムPython ARP監視ツール
#!/usr/bin/env python3
"""
Real-time ARP poisoning detection using packet capture.
Monitors ARP traffic for spoofing indicators and alerts on anomalies.
"""
import subprocess
import sys
import json
import time
from collections import defaultdict
from datetime import datetime
try:
from scapy.all import sniff, ARP, Ether, get_if_hwaddr, conf
SCAPY_AVAILABLE = True
except ImportError:
SCAPY_AVAILABLE = False
class ARPPoisonDetector:
def __init__(self, interface: str, gateway_ip: str, gateway_mac: str):
self.interface = interface
self.gateway_ip = gateway_ip
self.gateway_mac = gateway_mac.lower()
self.arp_table = {} # IP -> MAC mapping
self.arp_history = defaultdict(list) # IP -> list of (MAC, timestamp)
self.alerts = []
self.arp_count = defaultdict(int) # Source MAC -> count per interval
self.last_reset = time.time()
self.arp_rate_threshold = 50 # ARP packets per 10 seconds
def alert(self, severity: str, message: str, details: dict):
"""Generate alert for detected anomaly."""
alert_data = {
'timestamp': datetime.now().isoformat(),
'severity': severity,
'message': message,
'details': details,
}
self.alerts.append(alert_data)
print(f"\n[{severity}] {datetime.now().strftime('%H:%M:%S')} - {message}")
for key, value in details.items():
print(f" {key}: {value}")
def check_gateway_spoofing(self, src_ip: str, src_mac: str):
"""Check if someone is spoofing the gateway."""
if src_ip == self.gateway_ip and src_mac != self.gateway_mac:
self.alert('CRITICAL', 'Gateway ARP Spoofing Detected', {
'gateway_ip': self.gateway_ip,
'expected_mac': self.gateway_mac,
'spoofed_mac': src_mac,
'action': 'Potential MitM attack on default gateway',
})
return True
return False
def check_mac_change(self, src_ip: str, src_mac: str):
"""Check if IP-to-MAC mapping has changed."""
if src_ip in self.arp_table:
known_mac = self.arp_table[src_ip]
if known_mac != src_mac:
self.alert('HIGH', 'ARP Cache Poisoning Attempt', {
'ip_address': src_ip,
'previous_mac': known_mac,
'new_mac': src_mac,
'action': 'IP-to-MAC mapping changed unexpectedly',
})
return True
return False
def check_flip_flop(self, src_ip: str, src_mac: str):
"""Check for MAC address flip-flopping (active attack indicator)."""
self.arp_history[src_ip].append((src_mac, time.time()))
# Keep only last 60 seconds of history
cutoff = time.time() - 60
self.arp_history[src_ip] = [
(mac, ts) for mac, ts in self.arp_history[src_ip]
if ts > cutoff
]
unique_macs = set(mac for mac, ts in self.arp_history[src_ip])
if len(unique_macs) > 2:
self.alert('CRITICAL', 'ARP Flip-Flop Detected (Active Attack)', {
'ip_address': src_ip,
'mac_addresses': list(unique_macs),
'changes_in_60s': len(self.arp_history[src_ip]),
})
return True
return False
def check_arp_rate(self, src_mac: str):
"""Check for ARP flood (DoS or scanning)."""
self.arp_count[src_mac] += 1
# Reset counters every 10 seconds
if time.time() - self.last_reset > 10:
for mac, count in self.arp_count.items():
if count > self.arp_rate_threshold:
self.alert('MEDIUM', 'ARP Flood Detected', {
'source_mac': mac,
'arp_packets_10s': count,
'threshold': self.arp_rate_threshold,
})
self.arp_count.clear()
self.last_reset = time.time()
def process_packet(self, packet):
"""Process captured ARP packet."""
if not packet.haslayer(ARP):
return
arp = packet[ARP]
# Only process ARP replies (opcode 2) and requests (opcode 1)
if arp.op not in (1, 2):
return
src_ip = arp.psrc
src_mac = arp.hwsrc.lower()
# Run detection checks
self.check_gateway_spoofing(src_ip, src_mac)
self.check_mac_change(src_ip, src_mac)
self.check_flip_flop(src_ip, src_mac)
self.check_arp_rate(src_mac)
# Update ARP table
self.arp_table[src_ip] = src_mac
def start_monitoring(self):
"""Start real-time ARP monitoring."""
print(f"[*] Starting ARP Poison Detection on {self.interface}")
print(f"[*] Gateway: {self.gateway_ip} ({self.gateway_mac})")
print(f"[*] Monitoring... (Ctrl+C to stop)\n")
if SCAPY_AVAILABLE:
sniff(
iface=self.interface,
filter="arp",
prn=self.process_packet,
store=False,
)
else:
print("[-] Scapy not available. Install with: pip install scapy")
print("[*] Falling back to tcpdump-based monitoring...")
self._monitor_with_tcpdump()
def _monitor_with_tcpdump(self):
"""Fallback monitoring using tcpdump."""
cmd = ['tcpdump', '-i', self.interface, '-l', '-n', 'arp']
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL, text=True)
try:
for line in proc.stdout:
parts = line.strip().split()
if 'is-at' in parts:
try:
ip_idx = parts.index('is-at') - 1
mac_idx = parts.index('is-at') + 1
src_ip = parts[ip_idx]
src_mac = parts[mac_idx].lower()
self.check_gateway_spoofing(src_ip, src_mac)
self.check_mac_change(src_ip, src_mac)
self.arp_table[src_ip] = src_mac
except (IndexError, ValueError):
continue
except KeyboardInterrupt:
proc.terminate()
def generate_report(self) -> dict:
"""Generate summary report of detected anomalies."""
return {
'monitoring_interface': self.interface,
'gateway': {'ip': self.gateway_ip, 'mac': self.gateway_mac},
'total_alerts': len(self.alerts),
'arp_table_size': len(self.arp_table),
'alerts': self.alerts,
}
if __name__ == '__main__':
if len(sys.argv) < 4:
print("Usage: python process.py <interface> <gateway_ip> <gateway_mac>")
print("Example: python process.py eth0 10.0.1.1 aa:bb:cc:dd:ee:01")
sys.exit(1)
detector = ARPPoisonDetector(
interface=sys.argv[1],
gateway_ip=sys.argv[2],
gateway_mac=sys.argv[3],
)
try:
detector.start_monitoring()
except KeyboardInterrupt:
print("\n\n[*] Monitoring stopped.")
report = detector.generate_report()
print(f"[*] Total alerts generated: {report['total_alerts']}")
print(f"[*] ARP table entries: {report['arp_table_size']}")
防止策
レイヤー2の制御
- Dynamic ARP Inspection(DAI) - DHCPスヌーピングのバインディングテーブルと照合してARPパケットを検証します
- DHCPスヌーピング - 信頼できるIP・MAC・ポートの対応関係を記録したデータベースを構築します
- ポートセキュリティ - ポートごとのMACアドレス数を制限します
- プライベートVLAN - 同じVLAN内のホスト間の通信を制限します
ネットワーク制御
- 静的ARPエントリー - 重要なインフラストラクチャ(ゲートウェイ、DNS、DHCP)に設定します
- ネットワークのセグメント化 - VLANを使ってブロードキャストドメインの規模を縮小します
- 802.1X認証 - ネットワークへのアクセス前にデバイスを認証します
- 暗号化プロトコル - 傍受された場合でもデータを保護できるよう、SSH、HTTPS、TLSを使用します
ベストプラクティス
- 多層防御 - DAI、ARPWatch、カスタム監視を組み合わせて、包括的に保護します
- まず DHCP スヌーピングを有効化 - DAI はスヌーピングデータベースに依存するため、DAI を有効にする前に必ず DHCP スヌーピングを有効にします
- ゲートウェイの静的 ARP - デフォルトゲートウェイの静的 ARP エントリーを重要なサーバーに設定します
- Gratuitous ARP を監視 - 要求されていない ARP 応答に特に注意します
- ブロードキャストドメインを小さくする - VLAN を使用して、ARP ベースの攻撃が及ぶ範囲を制限します
- 定期的な監査 - デバイス間で ARP テーブルを定期的に比較し、異常を特定します