はじめに

Redmine 6.1への移行作業がようやく一段落したので、久しぶりにサーバーログを眺めていました。

すると、トップページ (hoge.example.com) に対して、見慣れない長大なペイロードが飛んできていました。

最近の攻撃は「脆弱性を突く」ことだけが目的ではありません。まずは対象がどんな環境なのかを調べ、その結果によって次の手を変えてきます。

今回は、その偵察段階で送られてきたJavaScriptペイロードがなかなか興味深い内容だったので、ModSecurityのログを追いながら見ていきます。

検出された ModSecurity ログ

まずは実際のログです。

例によってIPアドレスはダミーに置き換えています。テロリストに名前を与えていません。

[Wed Sep 02 02:04:07 2026] [security2:error] [client 198.51.100.24:56250] ModSecurity: Warning. Pattern match "(?i)(?:b[\\"'\\\\)\\\\[\\\\x5c]..." at ARGS:0. [file "REQUEST-932-APPLICATION-ATTACK-RCE.conf"] [line "205"] [id "932235"] [msg "Remote Command Execution: Unix Command Injection (command without evasion)"] [data "Matched Data: eval)(global[String['from'+'CharCode'](66,117,102,102,101,114)].from('KGFzeW5jIGZ1bmN0aW9uKCl7Ci8vIGZhc3RfcmVjb25fdjY..."] [hostname "hoge.example.com"] [uri "/"]
[Wed Sep 02 02:04:07 2026] [security2:error] [client 198.51.100.24:56250] ModSecurity: Rule [id "932250"] - Execution error - PCRE limits exceeded (-47): (null). [hostname "hoge.example.com"] [uri "/"]
[Wed Sep 02 02:04:07 2026] [security2:error] [client 198.51.100.24:56250] ModSecurity: Warning. Pattern match "(?i)\\\\b\\\\(?[\\"']*(?:assert|eval|exec|passthru)..." at ARGS:0. [file "REQUEST-933-APPLICATION-ATTACK-PHP.conf"] [line "406"] [id "933160"] [msg "PHP Injection Attack: High-Risk PHP Function Call Found"] [hostname "hoge.example.com"] [uri "/"]

最初に目を引くのは Rule 932235 が反応していることです。

Remote Command Execution:
Unix Command Injection

さらに途中では

PCRE limits exceeded (-47)

というログも出ています。

Base64の中身を見てみる

ログ中に埋め込まれていたBase64文字列をデコードすると、次のようなヘッダーが現れました。

(async function(){
// fast_recon_v6 — signature-rotated recon payload
// Changes from v5:
//   - Randomized top-level JSON keys (no fixed schema to fingerprint)
//   - Variable output structure per invocation
//   - IMDS calls use randomized User-Agent + jittered timeouts
//   - File reads are shuffled order (no deterministic sequence)
...

名前からも分かるように、これは偵察を目的としたJavaScriptです。面白いのは「何を調べるか」よりも、「どうやってWAFの検知を避けようとしているか」の方でした。

難読化の目的はコードを隠すことではない

例えば、

global[String['from'+'CharCode'](...)]

という書き方。普通なら

Buffer

と書けば済むものを、わざわざ文字コードから組み立てています。さらに

(0,eval)(...)

という間接呼び出しも使われています。どちらもJavaScriptとしては動作は同じですが、単純な文字列検索やシグネチャ検知を避けるための定番手法です。

コードを読めなくすることより、「機械に見つかりにくくすること」の方が目的と言った方が近いでしょう。

偵察対象も今どきらしい

コードを見ると、次のような情報収集も行おうとしていました。

  • クラウドメタデータ(IMDS)へのアクセス
  • IAMロールなど認証情報の取得
  • ファイル探索順序のランダム化
  • User-Agentやタイムアウト値のランダム化

固定パターンをできるだけ作らないよう工夫されており、「同じ攻撃を毎回少しずつ変える」という最近の攻撃らしい作りになっています。

PCRE が限界を迎えても、防御は止まらない

途中で

PCRE limits exceeded (-47)

が記録されています。これは巨大な入力に対して、ある正規表現がバックトラック上限へ達したことを示しています。

しかし、OWASP CRSは一つの巨大なルールだけで防御しているわけではありません。このリクエストでは、

  • 932235
  • 932260
  • 933160
  • 933210

と、別方向から検査するルールが続いてヒットしました。

つまり、一つの正規表現が処理を諦めても、他のルールが引き継ぐ設計になっています。

多層防御という言葉はよく聞きますが、今回のログはその動きを非常に分かりやすく見せてくれました。

おわりに

最近は攻撃そのものより、「検知をどう回避するか」の工夫を見る機会が増えてきました。

とはいえ、こうして落ち着いて中身を眺められるのは、防御基盤が先に仕事をしてくれているからです。

「サーバー運用はサファリパークに似ている」ようなものです。猛獣を間近で観察できるのは面白いですが、それは檻や装甲車があるからこそです。

今回も同じでした。難読化されたJavaScriptをじっくり読めたのは、攻撃がアプリケーションまで届かなかったからです。

運用者としては、その「観察できる余裕」こそが、一番ありがたい成果だったのかもしれません。