0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【第11回】「マルチポスト」「コピペ爆撃」を瞬時に弾く!テキスト正規化とハッシュによる同一内容重複投稿防止機構

0
Posted at

「レートリミットをすり抜けて、5秒おきに全スレへ同じ怪文書を貼りまくる荒らしが現れた」
令和に自作した2ちゃんねる風掲示板「つゔぁいちゃんねる(2ch.biz)」開発秘話の第11弾。今回は、第10回で導入した秒単位の連投規制を巧妙に回避して襲来する「コピペ爆撃」や「マルチポスト」、そして一般利用者の「二重書き込み事故」を確実に防ぐ重複検知エンジンの仕組みを解説します。


💡 はじめに:レートリミットだけでは防げない「コピペ爆撃」の現実

第10回において、5秒〜60秒のインターバルを設ける「秒単位レートリミット」を導入し、1秒間に100回書き込むような単純なスクリプト攻撃は完全にシャットアウトしました。

しかし、攻撃者も馬鹿ではありません。
レートリミットの存在を察知した荒らしは、次のようなスクリプトに進化させてきます。

# 5秒待機しながら全板・全スレッドへ同じ文章を投下するマルチポスト
while true; do
  curl -s -X POST https://2ch.biz/ap_board.php?action=create_post \
       -d "board=news&thread=1&content=【超重要拡散希望】〇〇の真実..." 
  sleep 6
  curl -s -X POST https://2ch.biz/ap_board.php?action=create_post \
       -d "board=vip&thread=42&content=【超重要拡散希望】〇〇の真実..." 
  sleep 6
done

この攻撃は「投稿間隔(6秒)」を守っているため、レートリミットでは一切弾けません。
結果として、 掲示板中の全スレッドが同一の怪文書や宣伝コピペで埋め尽くされる「マルチポスト公害」 が発生します。

さらに、一般ユーザーにとっても、電波の悪い地下鉄などで送信ボタンを押した際、タイムアウトと誤認してもう一度送信した結果、同じレスが2つ並んでしまう 「二重カキコ事故」 は非常によくあるストレス要因でした。

この両方を同時に根絶するため、開発初期(2026年8月18日)に 「同一内容の重複投稿防止機構」 を実装しました。


🛠️ なぜ単純な文字列比較($text === $prevText)ではダメなのか?

「直前の投稿内容と、今送られてきた本文を比較すればいいのでは?」
最初はそう考えがちですが、素朴な文字列比較には大きな穴が3つあります。

  1. 改行コードの差異(\r\n vs \n)
    Windows環境のブラウザから送られる改行(CRLF)と、Linux環境(LF)で改行バイトが異なると、見た目は完全に一致していても別文字列として扱われてしまいます。
  2. 目に見えない空白・タブの揺らぎ
    文頭や文末に半角スペースや全角スペース、タブ文字(\t)が1文字混ざっているだけで一致判定をすり抜けられてしまいます。
  3. 長文テキストの文字列比較コスト
    数千文字に及ぶ長文コピペ同士を毎回フルテキストで比較すると、サーバーのCPUとメモリを無駄に浪費します。

したがって、判定を行う前に 「テキストの徹底的な正規化(Normalization)」 を行い、さらに 「ハッシュ値による比較」 を行う必要があります。


📐 テキスト正規化パイプラインの設計

つゔぁいちゃんねるでは、投稿本文を比較する前に以下のクレンジング処理を施します。

正規化処理のコード(api.php 抜粋)

function normalizeContentForHash($content) {
    // 1. 改行コードを LF (\n) に統一
    $text = str_replace(["\r\n", "\r"], "\n", $content);
    
    // 2. 全角スペースを半角スペースに変換
    $text = mb_convert_kana($text, 's', 'UTF-8');
    
    // 3. 各行の前後の空白をトリム
    $lines = explode("\n", $text);
    $trimmedLines = array_map('trim', $lines);
    $text = implode("\n", $trimmedLines);
    
    // 4. 全体の先頭と末尾の不要な空行を削除
    $text = trim($text);
    
    // 5. 高速照合用のSHA-256ハッシュを生成
    return hash('sha256', $text);
}

この正規化を通すことで、文末にスペースを1個足して誤魔化そうとする古典的な回避テクニックもすべて同一テキストとして無効化されます。


🔍 多層的な重複検知スコープ

重複判定は、以下の 「2つの異なるスコープ」 で行われます。

スコープ1:同一IPアドレスからの直前投稿(マルチポスト検知)

投稿者のIPアドレスをキーにして、「そのユーザーが直前に書き込んだ投稿のハッシュ」と照合します。

  • 板やスレッドが違っていても関係ありません。
  • 「ニュース速報板」に書き込んだ文章と全く同じ文章を、6秒後に「VIP板」に投稿しようとすると即座に弾かれます。

スコープ2:同一スレッド内の直前投稿(コピペ爆撃検知)

もし攻撃者がプロキシやVPNを使ってIPアドレスをコロコロ切り替えながら攻撃してきた場合、IP単位の制限だけではすり抜けられてしまいます。
そのため、 「そのスレッドの最後のレス(直前レス)と同一内容ではないか?」 も同時にチェックします。

  • これにより、IPを変えながら同じAAやコピペでスレを埋めようとする行為も完全に遮断されます。

💻 コントローラーでの重複チェック実装

public function checkDuplicatePost($boardId, $threadId, $clientIp, $content) {
    $settings = $this->getSpamSettings();
    if (empty($settings['prevent_duplicate_posts'])) {
        return; // 機能OFFならスキップ
    }

    $currentHash = $this->normalizeContentForHash($content);

    // 1. 同一IPの直前投稿ハッシュを取得
    $stmt = $this->db->prepare("
        SELECT content_hash 
        FROM posts 
        WHERE ip_address = :ip 
        ORDER BY created_at DESC 
        LIMIT 1
    ");
    $stmt->execute([':ip' => $clientIp]);
    $lastIpHash = $stmt->fetchColumn();

    if ($lastIpHash && $lastIpHash === $currentHash) {
        $this->respondError("二重投稿(同一内容の重複書き込み)は禁止されています。");
    }

    // 2. 同一スレッド内の最新レスハッシュを取得
    $stmt = $this->db->prepare("
        SELECT content_hash 
        FROM posts 
        WHERE thread_id = :thread_id 
        ORDER BY post_number DESC 
        LIMIT 1
    ");
    $stmt->execute([':thread_id' => $threadId]);
    $lastThreadPostHash = $stmt->fetchColumn();

    if ($lastThreadPostHash && $lastThreadPostHash === $currentHash) {
        $this->respondError("同一スレッド内での連続した重複投稿は禁止されています。");
    }
}

投稿が正常に受理された場合は、posts テーブルの content_hash カラムにこのSHA-256ハッシュが保存され、次の投稿の判定に備えます。


⚙️ 管理コンソールでのトグル制御

「実況板」など、スポーツの得点時に「ああああああ」「きたああああ」といった短い叫びが連続する特殊な板や、テスト運用時を考慮し、管理画面(admin.html)の「🛡️ 荒らし・NG対策」タブからワンクリックでON/OFFを切り替えられるようにしました。

<!-- admin.html の重複防止チェックボックス -->
<div class="form-check mt-3">
    <input type="checkbox" id="preventDuplicateToggle" class="form-check-input">
    <label for="preventDuplicateToggle" class="form-check-label">
        📋 同一内容の重複投稿(コピペ爆撃・マルチポスト)を防止する
    </label>
</div>

📊 導入後の成果

この重複防止機構を導入した結果:

  • マルチポスト荒らしの壊滅 : スクリプトが同じ文章を貼ろうとしても、1件目以降はすべて 400 Bad Request で瞬時に弾かれ、DBに一切ゴミが残らない。
  • 誤送信・二重カキコ事故のゼロ化 : 一般利用者がうっかりボタンを2度押ししてしまっても、2発目が自動的にブロックされてスレッドが汚れない。
  • サーバー負荷の低減 : ハッシュ値の文字列比較(64文字の英数字一致判定)だけで済むため、DB負荷は実質ゼロ。

🎯 まとめと次回予告

第11回となる今回は、掲示板の美観とサーバーの平穏を保つ 「テキスト正規化とハッシュによる重複投稿防止機構」 について解説しました。

今回の学び・ポイント

  • レートリミット+重複検知の合わせ技 :間隔制限だけでなく「内容の同一性」を監視することで、マルチポストを完全に無力化。
  • 文字列比較の前に正規化せよ :改行コードや空白文字の差異を吸収するパイプラインが検知精度の鍵。
  • IP軸とスレッド軸の二重監視 :IP変更を伴うコピペ連投も、スレッド末尾のハッシュ照合で封じ込める。

しかし、攻撃者はさらに別の角度から攻めてきます。
「ブラウザを使わず、フォームのHTMLを直接解析してPOSTリクエストを機械的に送りつけてくる自動化ボット」 です。

次回、 【第12回】人間には見えずボットだけが引っかかる!不可視フィールドを用いたハニーポット罠の実装 へと続きます!


🔗 関連リンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?