【実務・中級編】 DNSクエリログの異常検知とドメイン生成アルゴリズム(DGA)の特定 – インシデントレスポンス & フォレンジック(DFIR)防御ガイド

ログの海に潜む「偽りの信号」を見抜け:DGAによるC2通信の検知と封じ込め

現場でインシデントレスポンスをしていると、決まって「異常なトラフィック」の正体に行き着く。特に、昨今の洗練されたマルウェアは、ハードコードされたIPアドレスなんて使わない。彼らが頼るのは、DGA(Domain Generation Algorithm:ドメイン生成アルゴリズム)だ。

今日は、DNSクエリログという「宝の山」をどう活用し、泥臭くも確実にC2(Command & Control)サーバーとの繋がりを断ち切るか、その実戦的な勘所を共有しよう。

1. なぜ「大量のNXDOMAIN」が危険信号なのか

攻撃者は、数百、数千ものランダムなドメインを一定の間隔で生成し、そのうちのどれか一つが生きていることを期待してDNSクエリを投げる。「とりあえず叩いてみて、繋がればラッキー」というこの性質上、ネットワーク上には膨大なNXDOMAIN(該当ドメインなし)応答が溢れかえる。

多くのエンジニアがこのログを「設定ミス」や「ネットワークの揺らぎ」として無視するが、それは致命的な判断ミスだ。攻撃者はこのノイズの中に、標的を操るための「命令」を隠している。

2. 実戦的アプローチ:Pythonによるエントロピー解析

DGAで生成されたドメイン名は、人間が読める意味を持たないことが多い。そこで我々が使うのが「エントロピー解析」だ。文字列のランダム性が高いものをスコアリングし、疑わしいクエリを炙り出す。

以下は、収集したDNSクエリログから、疑わしいドメインを抽出するための簡易的な分析スクリプトだ。

import math
from collections import Counter

def calculate_entropy(domain):
    """
    ドメイン文字列のエントロピーを算出。
    値が高いほどランダム性が高く、DGAの可能性が高い。
    """
    if not domain:
        return 0
    entropy = 0
    length = len(domain)
    # 文字の出現頻度をカウント
    counts = Counter(domain)
    for count in counts.values():
        p_x = count / length
        entropy -= p_x * math.log2(p_x)
    return entropy

# 解析対象のDNSクエリログリスト
query_logs = ["google.com", "xjhv128zsdklf.ru", "update-service-01.com", "qweasdzxc12345.net"]

# 閾値(この値を超えたら要調査)
THRESHOLD = 3.5

for domain in query_logs:
    score = calculate_entropy(domain)
    if score > THRESHOLD:
        print(f"[!] 異常検知: {domain} (Entropy: {score:.2f}) - 即時ブロック対象")

3. インフラレベルでの防御:DNSフィルタリングの鉄則

Pythonで検知するのも重要だが、もっと手前で止めるのがプロの流儀だ。Nginxやクラウドのセキュリティグループで防げないなら、DNSレベルで「ブラックホール化」する。

BIND/Unboundでの対応(設定の要点)

もし自前でDNSキャッシュサーバーを運用しているなら、response-policy(RPZ)を使うのが最も効率的だ。

# /etc/bind/db.rpz (RPZゾーンファイル例)
# DGAドメインをNXDOMAINに強制変換する
xjhv128zsdklf.ru CNAME .
qweasdzxc12345.net CNAME .

また、社内ネットワークの出口では、信頼できる脅威インテリジェンスフィード(OpenDNSやCloudflare Gatewayなど)と連携し、不審なTLD(.ru, .top, .xyzなどの特定の組み合わせ)へのクエリをデフォルトで拒否するポリシーを徹底してほしい。

4. Webアプリ開発者が知るべき「DNSの脆弱性」

Webアプリ開発中に、外部APIを叩く際の設定で「DNS解決を過信していないか?」を自問してほしい。

例えば、PHPでfile_get_contentsやcurlを使う際、ユーザー入力をそのままドメイン名として使うのは論外だ。攻撃者はここを突き、内部のDNSリゾルバを悪用したSSRF(Server-Side Request Forgery)を仕掛けてくる。

セキュアなDNS解決のサンプル(PHP)

外部ドメインへの接続は、必ずホワイトリストで管理すること。

<?php
// ホワイトリストによるドメイン検証
$allowed_domains = ['api.trusted-service.com', 'assets.mysite.com'];

function fetch_remote_data($url) {
    global $allowed_domains;
    $host = parse_url($url, PHP_URL_HOST);

    // ドメインが許可リストにあるか厳格にチェック
    if (!in_array($host, $allowed_domains)) {
        throw new Exception("許可されていないドメインへのアクセスです: " . $host);
    }

    // CURL設定でDNSキャッシュを強制し、IPを固定するなどの工夫も有効
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 5); // タイムアウトを短く設定
    return curl_exec($ch);
}
?>

最後に:ログを「見る」のではなく「語らせる」

DNSクエリログは、組織の通信の履歴書だ。毎日眺めていると、「いつもと違うリズム」が体に染み付いてくる。DGAの検知は、ツールに頼り切るのではなく、このような「違和感を言語化・数値化する」プロセスの積み重ねに過ぎない。

後輩諸君、ツールが吐き出したアラートを眺めるだけで満足してはいけない。その裏にある「攻撃者がなぜそのクエリを投げたのか」という意図を想像すること。それこそが、最強のセキュリティ対策になる。

インシデントは起きてから対処するものではない。ログの小さな揺らぎから、攻撃者の息遣いを感じ取る。それができるエンジニアこそが、今の現場には必要だ。

コメント

タイトルとURLをコピーしました