例外処理のチューニング

偽陽性排除のために用いている

/usr/share/modsecurity-crs/coreruleset/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf

は、「ModSecurityがブロックに至らないルールセットの閾値を引き上げる」処理もできます。

上記ファイルに以下を加えます。

# スローロリス対策
SecRule REQUEST_HEADERS:Connection "@rx (?i)(?:keep-alive(?:,\s*close|,\s*keep-alive)|close(?:,\s*keep-alive|,\s*close))" \
    "id:10001,phase:1,t:none,block,msg:'[CUSTOM RULE] Contradictory Connection header, possible Slowloris probe. Incrementing anomaly score.',tag:'application-attack',tag:'PROTOCOL_VIOLATION/INVALID_HREQ',setvar:'tx.inbound_anomaly_score_pl1=+%{tx.critical_anomaly_score}'"

これにより、「CRSはブロックしないためじわじわとリソースを奪う」戦略の裏をかきます。設定後、

sudo systemctl restart apache2.service

で、状況を反映します。

ログの沈静化

引き合いに出した「いやがらせ」は、継続的なエラーログを吐かせることで、別のエラーログを見えにくくします。

第1回目の記事で

        # IPアドレスのブラックリスト(テキストファイル)を読み込み、
        # 一致した場合はログも残さず接続を拒否する
        SecRule REMOTE_ADDR "@pmFromFile /etc/modsecurity/ip-blacklist.txt" \
            "phase:1,id:1001,status:404,msg:'IP Blacklisted',nolog,noauditlog"

とログすら残さないのは「一度ブラックリストに乗った者を気にする必要は無い」という意思表示と同時に、古代ローマにおける「ダムナティオ・メモリアエ(Damnatio Memoriae/記録抹消刑」の精神です。

Wikipedia引用
ダムナティオ・メモリアエを受けた人物は、その一切の存在がなかったとして、自らが遺したあらゆる痕跡を抹消された。社会的な体面や名誉を重んじた古代ローマ人にとって、ダムナティオ・メモリアエは最も厳しい措置と見なされた。

  • 交渉しない
  • 聞く耳を持たない
  • 名前を与えない

の精神で粛々とログの沈静化を図ります。

ネペンテスの罠(wp-login.phpへの対処)

筆者のvpsはRedmineやBookStackが主に乗っていますが、挨拶のように

  • wp-login.php
  • wp-admin

へとアクセスしてきます。そのような攻撃ツールを誘導するための罠です。

virtualhostへの記載

    <IfModule mod_alias.c>
    # 1. URLパスと実際のファイルパスを結びつける
    Alias /wp-login.php /home/www-data/nepenthes/login.html
    Alias /wp-admin /home/www-data/nepenthes/login.html

    # 2. Apacheがそのディレクトリにアクセスすることを許可する
    <Directory /home/www-data/nepenthes>
        Require all granted
    </Directory>
    </IFModule>

この、aliasにより、rewriteよりも早い段階で、wp-login.phpにアクセスしてきたホストをnepenthes(ウツボカズラ)ディレクトリに移動させます。

表示させるダミーhtml

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Unauthorized &mdash; Proceed with Caution</title>
    <style>
        body { font-family: monospace; background-color: #1a1a1a; color: #00ff00; margin: 20px; line-height: 1.5; }
        .container { max-width: 800px; margin: auto; padding: 20px; border: 1px solid #006600; box-shadow: 0 0 10px rgba(0,255,0,0.5); }
        h1 { text-align: center; color: #ff0000; }
        pre { white-space: pre-wrap; word-wrap: break-word; }
        .message { color: #ffff00; text-align: center; margin-top: 20px; }
    </style>
</head>
<body>
    <div class="container">
        <h1>ACCESS DENIED.</h1>
        <div class="message">Proceed with caution. Unauthorized access is recorded.</div>
        <pre id="encrypted-poem"></pre>
    </div>

    <script>
        const poem = `
            'Twas brillig, and the slithy toves
            Did gyre and gimble in the wabe:
            All mimsy were the borogoves,
            And the mome raths outgrabe.

            "Beware the Jabberwock, my son!
            The jaws that bite, the claws that catch!
            Beware the Jubjub bird, and shun
            The frumious Bandersnatch!"

            He took his vorpal sword in hand;
            Long time the manxome foe he sought—
            So rested he by the Tumtum tree,
            And stood awhile in thought.

            And, as in uffish thought he stood,
            The Jabberwock, with eyes of flame,
            Came whiffling through the tulgey wood,
            And burbled as it came!

            One, two! One, two! And through and through
            The vorpal blade went snicker-snack!
            He left it dead, and with its head
            He went galumphing back.

            "And hast thou slain the Jabberwock?
            Come to my arms, my beamish boy!
            O frabjous day! Callooh! Callay!"
            He chortled in his joy.

            'Twas brillig, and the slithy toves
            Did gyre and gimble in the wabe:
            All mimsy were the borogoves,
            And the mome raths outgrabe.
        `;

        const cipherMethods = [
            // シーザー暗号
            (text) => {
                const shift = Math.floor(Math.random() * 25) + 1; // 1-25のランダムなシフト
                return text.split('').map(char => {
                    if (char.match(/[a-z]/i)) {
                        const base = char === char.toLowerCase() ? 'a'.charCodeAt(0) : 'A'.charCodeAt(0);
                        return String.fromCharCode((char.charCodeAt(0) - base + shift) % 26 + base);
                    }
                    return char;
                }).join('');
            },
            // 逆順(リバース)
            (text) => text.split('').reverse().join(''),
            // Base64エンコード (実際はもっと複雑だが、解析を意識)
            (text) => btoa(encodeURIComponent(text).replace(/%([0-9A-F]{2})/g,
                function toSolidBytes(match, p1) {
                    return String.fromCharCode('0x' + p1);
                })),
            // 文字列のランダムシャッフル(単語単位でシャッフル)
            (text) => {
                const words = text.split(/\s+/);
                for (let i = words.length - 1; i > 0; i--) {
                    const j = Math.floor(Math.random() * (i + 1));
                    [words[i], words[j]] = [words[j], words[i]];
                }
                return words.join(' ');
            },
            // ASCIIコード置換 (16進数)
            (text) => text.split('').map(char => char.charCodeAt(0).toString(16)).join(' '),
            // 文字列中にランダムなノイズ(ランダム文字)を挿入
            (text) => {
                let noisyText = '';
                for (let i = 0; i < text.length; i++) {
                    noisyText += text[i];
                    if (Math.random() < 0.2) { // 20%の確率でノイズ挿入
                        noisyText += String.fromCharCode(Math.floor(Math.random() * (126 - 33 + 1)) + 33); // ランダムなASCII文字
                    }
                }
                return noisyText;
            }
        ];

        // ランダムな暗号化方法を選択
        const randomCipher = cipherMethods[Math.floor(Math.random() * cipherMethods.length)];
        document.getElementById('encrypted-poem').textContent = randomCipher(poem.trim());

        // 開発者ツール対策: 意図的にコンソールに警告を出して混乱させる
        console.warn("WARNING: Unauthorized access detected. All activities are being monitored.");
        console.info("Clues may be hidden in plain sight. Or not. The Jabberwock awaits.");
        // コンソールをクリアして重要なメッセージを見にくくする
        console.clear(); 

    </script>
</body>
</html>

これは『鏡の国のアリス』の『ジャバウォックの詩』を

  • シーザー暗号
  • 逆順
  • 文字列のランダムシャッフル

などの簡単な暗号化を施して表示させるというhtml。このように、意味不明だがきちんと人間の手がかかる文章を読ませることで、目視確認をしてくる攻撃者の行動に釘を刺します。なお、これは

  • 完全に静的なファイルのためサーバのリソースを喰わない
  • ログインフォームを用いていないためログイン情報が送信される余地はない

の2点で安心です。

まとめにかえて:本システムの対象者

以上、全3回にわたり

  • Apache及び設定
  • Mod_Security
  • シェルスクリプト

で、低コストで柔軟な運用ができる『ONE OUTS』システムを紹介しましたが、そもそも、この、大胆な設定を行えたのは以下の理由に尽きます。

「筆者のサイトは収益化やアクセス増を全く目指していないから行えた」
「サーバの運営費は自分のポケットマネーで出してる。ノートやインク代のようなもの」
「広告が大嫌いだから自分のサイトでは見たくない」

という、Webサイトの多くの目的から全く相反する考え。なので、アクセス減につながる帯域のブロックは平気で行います。
また、このシステムは広告が設置するようなスクリプトとも相性が悪いでしょう。

  • 自分のメモ帳としてWebに公開している(いわゆるチラシの裏)
  • 趣味と実用を兼ねる

方向けの、身も蓋もない結論でもって、3アウトチェンジとさせていただきます。