RTX1200にアタックしてくる人々を可視化する

RTX1200にアタックしてくる人々を可視化する

たまには真面目な技術記事も書きたくなったのでタイトルの通りです。

やりたいこと

RTX1200の吐き出すsyslogの中からrejectした履歴だけを抽出し、アタックを仕掛けてきたヤツのIPアドレスを逆引きし、何回アクセスしてきているかを勘定して可視化するということがしたい。

前提条件

RTX1200のsyslogをLAN内のsyslogサーバーに飛ばす。通常の設定だとログが滅茶苦茶膨大になるので、syslog側でREJECTを含むログだけを抽出して保存する。/etc/rsyslog.confでポートを設定。514番ポートを開けてファイアウォールを開放してやる。

# Provides UDP syslog reception
# for parameters see http://www.rsyslog.com/doc/imudp.html
module(load="imudp") # needs to be done just once
#input(type="imudp" port="514")
# ポート開放
input(type="imudp" port="514" ruleset="RTX1200_FILTER_SET")

次に/etc/rsyslog.d/10-yamaha.confを作る。

ruleset(name="RTX1200_FILTER_SET") {
    # Rejected のみを対象にフィルタリング IPアドレスはルーターのLAN側に置き換える
    if ($fromhost-ip == '192.168.100.1') and ($msg contains 'Rejected') then {
        action(
            type="omfile"
            file="/var/log/yamaha/rtx1200.log"
            template="RSYSLOG_TraditionalFileFormat"
        )
        stop
    }
}

これで定義が終わったので、firewall-cmd –add-port=514/udp –zone=public –permanentでポートを開けたらsystemctl restart rsyslog。/var/log/yamaha/rtx1200.logができていればOK。

IP→リモホに変換するスクリプト

とりあえず今のモダン(?)らしいのでpythonでゴリゴリと書く。

#!/usr/bin/python

import re
import socket
import sys
from collections import Counter
from concurrent.futures import ThreadPoolExecutor, as_completed

# --- 設定 ---
LOG_FILE = '/var/log/yamaha/rtx1200.log'
OUTPUT_FILE = '/root/rejected_src_analysis.csv'
REVERSE_DNS_TIMEOUT_SEC = 1  # 逆引きのタイムアウト(秒)
MAX_WORKERS = 15             # 逆引きに使用する最大スレッド数
# --------------

def extract_source_ips(log_content):
    """
    ログの内容から送信元IPアドレスを抽出します。
    YAMAHA RTXのログフォーマット(Rejected at ... filter: [PROTOCOL] [SRC_IP]:[SRC_PORT] > ...)に対応。

    Args:
        log_content (str): ログファイルの内容全体。

    Returns:
        list: 抽出されたすべての送信元IPアドレスのリスト。
    """
    # 正規表現: "Rejected"の後ろにあるプロトコル名に続く "[IP]:[PORT] >" のIP部分をキャプチャ
    # (\d{1,3}(?:\.\d{1,3}){3}) がIPアドレスのパターン
    # \d+ がポート番号
    pattern = re.compile(r'Rejected.*?:\s+\S+\s+(\d{1,3}(?:\.\d{1,3}){3}):\d+\s+>')

    ips = pattern.findall(log_content)
    return ips

def reverse_lookup(ip):
    """
    単一のIPアドレスに対して逆引きを実行し、ホスト名を返します。
    タイムアウトやエラーが発生した場合は、元のIPアドレスを返します。

    Args:
        ip (str): 逆引きを行うIPアドレス。

    Returns:
        tuple: (IPアドレス, ホスト名または元のIPアドレス)
    """
    try:
        # socket.gethostbyaddr は (ホスト名, エイリアスのリスト, IPアドレスのリスト) を返す
        hostname, _, _ = socket.gethostbyaddr(ip)
        # 最初のホスト名を返す
        return ip, hostname
    except socket.herror:
        # DNSホストエラー(逆引きレコードなし)
        return ip, f"No PTR ({ip})"
    except socket.timeout:
        # タイムアウトエラー
        return ip, f"Timeout ({ip})"
    except Exception as e:
        # その他のネットワークエラー
        print(f"DEBUG: Error looking up {ip}: {e}", file=sys.stderr)
        return ip, f"Error ({ip})"

def analyze_logs():
    """
    ログファイルを読み込み、IPアドレスのカウント、逆引きを実行し、結果を出力します。
    """
    print(f"--- ログファイル読込: {LOG_FILE} ---")
    try:
        with open(LOG_FILE, 'r', encoding='utf-8') as f:
            log_content = f.read()
    except FileNotFoundError:
        print(f"エラー: ファイル '{LOG_FILE}' が見つかりません。")
        exit()
    # 1. 送信元IPアドレスを抽出
    source_ips = extract_source_ips(log_content)
    if not source_ips:
        print("エラー: ログから有効な送信元IPアドレスを抽出できませんでした。")
        return

    # 2. 重複回数をカウントしてまとめる
    ip_counts = Counter(source_ips)
    unique_ips = list(ip_counts.keys())

    print(f"抽出されたユニークなIPアドレス数: {len(unique_ips)}")
    print(f"--- 逆引き処理開始(最大{REVERSE_DNS_TIMEOUT_SEC}秒のタイムアウト) ---")

    # 3. 逆引きを並行して実行(タイムアウト付き)
    # ThreadPoolExecutorを使用して並行処理とタイムアウトを制御
    resolved_results = {}
    # DNS解決は時間がかかる可能性があるため、ソケットのデフォルトタイムアウトも設定
    socket.setdefaulttimeout(REVERSE_DNS_TIMEOUT_SEC)

    # Executorの初期化
    with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
        # 逆引きタスクを投入
        future_to_ip = {
            executor.submit(reverse_lookup, ip): ip
            for ip in unique_ips
        }

        # タスク完了を待機
        for future in as_completed(future_to_ip):
            ip = future_to_ip[future]
            try:
                # ここでタイムアウト処理は不要(ThreadPoolExecutorで同期的に実行されるため)
                # reverse_lookup関数内でsocket.setdefaulttimeoutが適用される
                _, hostname = future.result()
                resolved_results[ip] = hostname
            except Exception as exc:
                # 逆引き関数内ですでにエラー処理済みのため、ここではIPアドレスをそのまま使用
                resolved_results[ip] = f"Unknown (Error: {exc.__class__.__name__})"

    print("--- 逆引き処理完了 ---")

    # 4. 結果を整理してファイルに保存
    output_lines = ["Count,Source IP,Remote Hostname"]

    # カウント順にソート(降順)
    sorted_counts = ip_counts.most_common()

    for ip, count in sorted_counts:
        hostname = resolved_results.get(ip, f"N/A (Missing Lookup)")
        output_lines.append(f"{count},{ip},{hostname}")

    # 結果をコンソールとファイルに出力
    output_content = "\n".join(output_lines)

    print("\n--- 結果(Console)---")
    print(output_content)
    print("-----------------------\n")

    # ファイルに書き込み
    try:
        with open(OUTPUT_FILE, 'w', encoding='utf-8') as f:
            f.write(output_content)
        print(f"分析結果をファイルに保存しました: {OUTPUT_FILE}")
    except IOError:
        print(f"エラー: ファイル '{OUTPUT_FILE}' への書き込みに失敗しました。")


if __name__ == "__main__":
    analyze_logs()

/root/.local/bin/ip2hosts.pyとして保存しchmod 700して実行するとこんな感じの結果が得られた。

最高アタック記録はなんと790回!常識では考えられない回数!まさにアンビリバボー!

思ったこと

業務用ルーターですらこれだけの回数の攻撃を常に受けています。最高記録は驚きの790回!ほぼ30秒単位で行っています。特に悪質で有名なのはこのIPアドレスでした。どうやらポートスキャンを延々と行った後、SSHなどにブルートフォースアタックするらしい。

業務用ルーターだからこそここまで詳細なログが取れるが、一般の民生品のルーターはどうなっているのか…考えたくもない。

コメントを投稿する

メールアドレスは公開されませんのでご安心ください。 * が付いている欄は必須項目となります。

内容に問題なければ、下記の「コメントを送信する」ボタンを押してください。

ページの先頭