『ライザのアトリエ3』調合ケーススタディ:『渡り鳥のお守り』の作り方。

2年ほど前に公開した「ボオス&クラウディア」の調合記事に対し、システムへの深い探求心に満ちたご質問コメントをいただきました。

まずは、お寄せいただいたコメントをご紹介します。

【読者様からのコメント】
3点ほど、助言いただけたら幸いです。
1.武器の特性・超特性に対して
2.ロールに対して
3.属性防御について
1.武器の調合に関してですが、
●A系統(攻防 / 攻速 / 防速 / 全能)
●B系統(スキル強化++ / スキル強化+ / スーパースキル)
の2つの系統に対し(他もある?)、今日の時点でA系統の強化しかしていないです。
よく攻略記事などで「最強武器」などは謳われていますが、それは「自分で操作する前提」「サポートモード」「アグレッシブモード」のどれにおける最強なのかというのがわからないなぁと思っています。
どうやって特性・超特性を決めていくのか、効果の計測方法はどんな感じでやっているのかなど知りたいです。
2.ロールについての理解が全くできてないです。。
たまにロール60みたいな画像が載っている記事を見ますが、私はロールが5とか7とかまでしか上がりません。そもそも役割もわかっていません。
ロールはどうやって60などに高めるのでしょうか?そうすると、何が変わるのでしょうか?
3.属性防御について
ボオスだけはなぜかステータス画面で属性の耐性に色がついていますが、なぜついているのかもわかりません。他の10人は色がついていないので、低耐性だと思います。もっとちゃんとした耐性の付け方があるのだと思います。(※その割にボオスは撃たれ弱すぎます。。)
11人全員に、渡り鳥のお守りをつけています。この渡り鳥は当然すべての効果を開放できています。14賢者、11クリスタルエレメント、その他、竜素材や動物素材にいたるまで可能なカテゴリの影響拡大中間生産物は作ってあるので。

基礎を完全に理解されているからこそ突き当たる、非常に鋭く本質的な疑問です。

そこで今回は、いただいた3つの疑問に対して小手先の回答で終わらせるのではなく、「筆者が実施している調合方針」を解き明かす形で、徹底的に解説してみたいと思います。

本記事では、以下のステップに沿って順を追ってロジックを展開していきます。

  1. 装飾品「渡り鳥のお守り」:私はこう組みました
    (※あえてリンクコールを使わずに組んだ基礎例
  2. ステータス決定論:A系統(ステ盛り)vs B系統(スキル特化)の真実
    (※なぜ私はA系統に寄せるのか? アイテムダメージ200万超を叩き出すための相乗効果)
  3. 効果測定の実践:ラムロースト2号を用いた武器性能の計測手法
    (※自操作・サポート・アグレッシブの各モードにおける実戦値の比較検証)
  4. ロールの極意:「ロール60」への到達手順と劇的なダメージ変化
    (※ボオスを題材に、ロールレベル合算の仕組みと「リンクコールを利用した高レベルロール」の真価を解明)

読者様のヒントになれば幸いです。

それでは、まずは「渡り鳥のお守り」の設計から見ていきましょう。

前提条件

  • 中和剤ループによって各種特性をコントロールできていること。
  • 超純度による無限ジェムと中間素材の複製ができていること。
  • クリアデータ / クリアデータに近いレベルでSSRのシンセサイズキーを所有していること。
  • アドバンススキル「マテリアルプラス」(秘密の鍵を調合で使用したとき、投入回数が1回増える)を解放している。

筆者調合の渡り鳥のお守り

ステータス

項目備考
装飾品名渡り鳥のお守りランクS / Lv.39
装備可能者全員(11人)ボオス装備中
属性 / 属性値風・雷 / 4
品質999
HP145
攻撃力492
防御力237
素早さ499

効果・特性・超特性

  • 効果1:安全本願
    • 状態異常耐性が上昇する。戦闘開始時、防御力が上昇する。
  • 効果2:アイテム強化+20%
    • 使用するコアアイテムの威力が20%増加する
  • 効果3:ダメージ軽減+20%
    • あらゆるダメージを20%軽減する
  • 効果4:旅の休息
    • 行動時にHPとブレイク値を回復する
  • 特性1:攻速強化++ 99
    • 攻撃力と素早さが最大で100増加する
  • 特性2:防速強化++ 99
    • 防御力と素早さが最大で100増加する
  • 特性3:全能力強化++ 99
    • 全ての能力値が最大で50増加する
  • 超特性:英雄の心得
    • アタッカーレベルが上昇する

中間素材の調合

グランツオルゲン

筆者は上記の例で言う「A系統:ステータス振り切り」でやっています。理由は

「コアアイテム主体で戦うため、敵より素早さを上げて先制攻撃で屠る」

ためです。

そのため、グランツオルゲンは

  • 攻速強化++ 99
  • 防速強化++ 99
  • 全能力強化++ 99

を特性に選んでいます。ちょっとしたTIPSとして

  • 武器で使えるステータス上昇の特性は「攻撃」に関する特性が利用できます。
    • 速強化
    • 防強化
  • 防具で使えるステータス上昇の特性は「防御」に関する特性が利用できます。
    • 強化
    • 速強化
  • 装飾品で使えるステータス上昇の特性は「素早さ」に関する特性が利用できます。
    • 強化
    • 強化

そのため、武器や防具でグランツオルゲンを作る際は、筆者は上記を組み替えています。

ここでのポイントはシンセサイズキー「属性追加:雷」を使うこと。

これにより、グランツオルゲンには全属性が付与され、どの属性であっても「金属」のマテリアル環を発現できます。

クリムゾンバース

影響拡大+4を持つ気体。渡り鳥のお守りでの調合/リビルド時にマテリアル環を一気に発現させます。

ここでのポイントはシンセサイズキー「属性追加:風」を使うこと。渡り鳥のお守りのマテリアル環は風属性が多いため、調合レベルをあまり上げずに一気に効果を発現できるからです。

秘密の鍵

各地のランドマークを回り、○○上昇・超 をそろえておきます。(もちろん、DLCで白楼の鍵を手に入れているのであれば、○○上昇・神域が望ましいです)

調合

パールクリスタルを調合します。

秘密シリーズの定跡、下位素材から調合していき、最終的な投入回数を増やします。

  • 大貝の白玉(属性値3以上)を入れます。
  • コライユサーブル(属性値3以上)を入れます。
  • レシピ変化のマテリアル環があるところまで寄り道せずに素材を入れていきます。
  • レシピ変化の前にシンセサイズキーを入れます。「○○上昇・超 以上」のステータス上昇を投入します。

※このシンセサイズキーのパラメータ(ステータス)上昇は、レシピ変化で装飾品に変わったときにそのまま引き継がれます。

献身のロケットからレシピ変化させます。

  • 巨鳥の風切羽(属性値3以上)×2
  • 巨鳥の風切羽(属性値4以上)×1

を入れます。

  • レシピ変化の前にシンセサイズキーを入れます。「○○上昇・超 以上」のステータス上昇を投入します。

金属のマテリアル環で、先に述べた「グランツオルゲン(攻速強化、防速強化、全能力強化)を、「調合回数残り1」になるまで入れます。

というのも、渡り鳥のお守りで入れられるステータス上昇系のインゴットは、ステータス上昇がやや弱いクリミネアしかないからです。

導きの鳥の羽を入れてレシピ変化をします。

渡り鳥のお守りを調合します。

  • シンセサイズキーを入れます。

条件は

  • 属性:風
  • モチーフ:羽根
  • レアリティ:SSR以上
  • 効果:ステータス上昇系
  • クリムゾンバース(追加属性:風)を投入し、中間のマテリアル環を発現させます。
  • 後は投入回数いっぱいまでクリミネアを入れます。

※TIPS※「ソーサリーローズ」は「気体」を持つクロース系なので、投入に余裕があるなら、これを入れてのステータスアップも見込めます。

この段階での渡り鳥のお守りがこちらです。

ステータス

項目備考
装飾品名渡り鳥のお守りランクS / Lv.13
装備可能者全員(11人)初期調合完了時点
属性 / 属性値風・雷 / 4
品質999
HP142
攻撃力441
防御力237
素早さ436

効果・特性・超特性

  • 効果1:安全祈願・極
    • (※リビルド前段階の発現効果)
  • 効果2:アイテム強化+20%
    • 使用するコアアイテムの威力が20%増加する
  • 効果3:ダメージ軽減+10%
    • あらゆるダメージを10%軽減する(※リビルドで+20%へ成長余地あり)
  • 効果4:旅の休息
    • 行動時にHPとブレイク値を回復する
  • 特性1:攻速強化++ 99
    • 攻撃力と素早さが最大で100増加する
  • 特性2:全能力強化++ 99
    • 全ての能力値が最大で50増加する
  • 特性3:防速強化++ 99
    • 防御力と素早さが最大で100増加する
  • 超特性
    • (※この調合段階では未付与 / リビルド時に「英雄の心得」等を投入)

リビルドを行います。

この段階で超特性の気体(英雄の心得)を入れます。残りの効果を発現させていきます。

項目初期調合完了時 (Lv.13)アイテムリビルド完了時 (Lv.39)変化・進化ポイント
HP142142変動なし(初期投入素材で完成済み)
攻撃力441441変動なし
防御力237237変動なし
素早さ436436変動なし
アイテムLvLv.13Lv.39リビルド投入により上昇
効果1安全祈願・極安全本願ランクアップ発現!
効果3ダメージ軽減+10%ダメージ軽減+20%被ダメージ軽減が10%強化!
超特性なし(空き枠)英雄の心得アタッカーレベル上昇が付与

装備強化を行います。

既に効果テーブルは埋まっているので、適当にステータスが上がる中間素材(マスターレザー)を入れました。ドレッドレザーがあれば更に上がると思います。

まとめ

  1. 全属性を付与したグランツオルゲンを作っておく。
  2. その際に必要な特性を発現させておく。
  3. レシピ変化がある装飾品は下位素材から作っていく。
  4. この時、全ての段階でシンセサイズキーを入れてステータス上昇とマテリアルプラスによる投入回数上昇を見込んでおく。
  5. 調合の時点でステータス上昇の特性の素材をたっぷりと入れるため効果発現はリビルドで行う。
  6. リビルド時に超特性の付与をついでに行う。
  7. 最後に装備強化でステータスの追加上昇を行う。

が筆者の武器の調合法です。

次の展望

ステータス決定論:A系統(ステ盛り)vs B系統(スキル特化)の真実
(※なぜ私はA系統に寄せるのか? アイテムダメージ200万超を叩き出すための相乗効果)

を書いていこうと思います。

ワイヤレスイヤホン・新調。(Anker Liberty Pro4選定)

2024年以来のワイヤレスイヤホン、導入です。

Bluetoothイヤホンを買い換えました。筆者が最終的に選択したのはAnkerのSoundcore Liberty 4 Proです。

それまで使用していたモデルは、同社のSport X20。イヤーフックがあるため落としにくいという利点がありましたが、アクティブノイズキャンセリングの効き具合に関してはやや物足りなくなっていったというのも事実です。特に移動時や周囲の雑音が気になる環境では、より強力な遮音性能がほしいというのも購入のきっかけである。

選択肢Pro 4 かPro 5か

SportはX20が未だ現役ということから、イヤーフックなしでも使える機種を選定していきます。選択肢として検討したのは、

  • 昨年登場したLiberty Pro 4
  • 今年新たに発表された後継機のLibetty Pro 5です。

最新モデルであるPro 5は性能面を見れば非常に魅力的ではありましたが、ここに一つの問題が立ちはだかります。

落としても授業料で済むか?

筆者は過去に、当時のSONYのハイエンドモデルを側溝に落として紛失してしまった経験があります。いくら2年使っていたとはいえ、高価格なイヤホンを失った際の精神的・金銭的ダメージは小さくなく、だからこそこれまである程度割り切って使える価格帯(そして落ちにくい形状)の製品を選んできました。

なので、Pro 5の約2.5万円強という価格設定は、万が一の紛失を想像した際にやや心理的ハードルが高いというものがあります。

一方でPro 4は、実売1万円台半ばから後半という価格帯でありながら、十分強力なノイズキャンセリング性能を確認。万が一の紛失時のリスクを許容範囲に抑えつつ、日常の静寂を手に入れるという点も協力でした。

確定、Pro 4

また、Pro 4のスマート充電ケースにディスプレイが搭載されている点もポイントでした。ケース側でノイズキャンセリングの切り替えやモード設定を直接行えるため、歩行中などに耳元のイヤホン本体を直接触る機会が減る。これは耳元での操作ミスによるポロリと落とす事故をある程度防げます。

それ以上に、実機を装着して「自分を選んだ」感が強いのものPro 4でした。

使用感、気になったことなどは改めてご報告します。

プライズ:ライザのアトリエ3版フィギュア撮影。

今回は撮影に相当苦労しました。

BiCute Bunnies ライザのアトリエ3版。

  • 色飛びしやすい白
  • 強烈なバニースーツの質感
  • しっかり本物が使われているフィッシュネットパンスト。

これらが重なり、撮影は過去最大レベルでの難易度でしたが

S字カーブやパンストの質感

脇からうなじにかけての質感も非常に良くできていて、スケールフィギュア以上だと思った次第です。

Apache RewriteRuleを書き直して気付いたこと。「正規表現」は短く書くためではなく、保守しやすくするために使う

先日、Redmine の Googlebot 対策として Apache の mod_rewrite を使った RewriteRule を追加しました。

まずは CPU 使用率を下げることが最優先だったため、とにかく確実に動くことを重視して場当たり的に正規表現を追加しました。

なので、今回は、RewriteRule を書き直しながら改めて感じた、「正規表現は短く書くためではなく、保守しやすくするための道具」という話です。

動けばいい。だが、そんな状態では後でメンテしづらい

最初に書いた設定はこんな感じでした。

RewriteRule ^/projects/.*/knowledgebase - [G,L]
RewriteRule ^.*/knowledgebase - [G,L]

RewriteCond %{HTTP_USER_AGENT} (Googlebot|GoogleOther|bingbot|Baiduspider) [NC]
RewriteRule ^/(projects/[^/]+/)?issues\.pdf$ - [G,L]

RewriteCond %{HTTP_USER_AGENT} (Googlebot|GoogleOther|bingbot|Baiduspider) [NC]
RewriteRule ^/(projects/[^/]+/)?issues\.csv$ - [G,L]

RewriteCond %{HTTP_USER_AGENT} (Googlebot|GoogleOther|bingbot|Baiduspider) [NC]
RewriteRule ^/(projects/[^/]+/)?issues\.atom$ - [G,L]

もちろん、これでも動きます。むしろトラブル対応中であれば、このくらい分かりやすく書いた方が安全です。

ただ、冷静になって眺めてみると、

「同じことを何回も書いている」

ことに気付きました。

正規表現とは何か

そこで、さらに正規表現で直していきます。「正規表現」という言葉を聞くと、難しそうな印象を持たれる方も多いかもしれませんが、思ったよりも単純なルールでできます。

例えば、

issues.pdf
issues.csv
issues.atom

この3つは末尾だけが違います。

これを

issues\.(pdf|csv|atom)

と書けば、

.pdf でも .csv でも .atom でも一致する」

という意味になります。MtGで言うなれば、

  • 日没を遅らせる者
  • 時を解す者
  • ドミナリアの英雄

につく「テフェリー」は

(日没を遅らせる者|時を解す者|ドミナリアの英雄)、テフェリー

と書けます。このように、共通点があるものを一つのルールで表現する。それが正規表現です。

書き直した結果

最終的には次のような形に整理しました。

RewriteCond %{HTTP_USER_AGENT} (Googlebot|GoogleOther|bingbot|Baiduspider) [NC]
RewriteCond %{REQUEST_URI} ^/(projects/[^/]+/)?issues\.(pdf|csv|atom)$ [NC,OR]
RewriteCond %{QUERY_STRING} (^|&)(sort|set_filter|per_page)= [NC]
RewriteRule ^/(projects/[^/]+/)?issues - [G,L]

PDF、CSV、Atom を一つの正規表現へまとめ、さらに「動的クエリ」と「エクスポート要求」を一つの RewriteRule で処理できるようにしています。

行数だけを見ると少し減った程度です。

ですが、保守性はかなり向上しました。

保守性を求めた正規表現

今回の目的は「短く書くこと」ではありません。

例えば将来、

issues.json

も遮断対象にしたくなったとします。以前の書き方なら RewriteRule を一本追加します。この書き方であれば

(pdf|csv|atom|json)

と一か所を書き換えるだけで済みます。変更箇所が一つだけになる。これは保守する上でかなり大きな違いです。

Knowledgebaseも同じ考え方で整理。

Knowledgebase の RewriteRule も整理しました。

以前は

RewriteRule ^/projects/.*/knowledgebase - [G,L]
RewriteRule ^.*/knowledgebase - [G,L]

のように複数行で書いていました。これを

RewriteRule ^/(home/www-data/|(.*/)?knowledgebase) - [G,L]

という一つのルールへまとめています。さらに、このルールでは以前問題になった

/home/www-data/...

のような物理パスへの誤アクセスも同時に処理しています。

「Knowledgebaseだけ」ではなく、「Rails に渡したくないもの」という視点で整理し直した結果です。

「同じ意味のもの」をまとめる

今回 RewriteRule を整理していて感じたのは、正規表現を書くことが目的ではないということです。目的は、「同じ意味を持つものを一つのルールで表現する」ことでした。

  • Knowledgebase は「存在しない旧機能」です。
  • PDF、CSV、Atom は「重いエクスポート」です。
  • sort や set_filter は「重い動的クエリ」です。

そう考えると、設定ファイルも「何を止めたいのか」が分かる構成になっていきます。

未来の自分が読める設定にする

サーバー設定は、一度書いたら終わりではありません。半年後、一年後あるいは障害対応中の深夜に、また自分が読むことになります。

その時に

「この RewriteRule は何を止めているんだっけ?」

と悩むようでは、あまり良い設定とは言えません。もちろん、正規表現は凝ろうと思えばいくらでも複雑にできます。ですが、複雑さは必ずしも保守性につながりません。

今回の書き直しで目指したのは、「短い設定」ではなく、 「意図がまとまっていて、後から見ても理解しやすい設定」 でした。
正規表現は、そのための道具なのだと改めて感じた次第です。

Redmine 6.x移行記(後編)犯人はGooglebotだった。Apacheの410 GoneでPassengerを守るまで

前回の記事では、Redmine 6.1への移行後に Passenger の CPU 使用率が異常に高止まりし、production.log と netstat を追い始めたところまでを書きました。

調べていく中で、

  • 削除した Knowledgebase へのアクセス
  • sort=set_filter= を含むチケット一覧
  • PDF や CSV、Atom のエクスポート要求

といった、一見すると関連性のないリクエストが大量に飛んできていることが分かってきます。そして最後に、

「もしかしてクローラーなのでは?」

という疑問が残りました。今回は、その続きを書いていこうと思います。

アクセス元を確認してみる

まずは production.log とアクセスログを突き合わせながら、アクセス元を確認してみました。

すると、見えてきたのは見慣れた IP アドレスです。

66.249.xx.xx

Googlebot でした。しかも一度や二度ではありません。HTTPS のセッションを何十本も張りながら、次々とリクエストを投げています。

しかもアクセスされていたのは、削除した Knowledgebase だけではありません。チケット一覧についても、

sort=
page=
set_filter=

といったパラメータ違いを延々と巡回しています。さらに、

issues.pdf
issues.csv
issues.atom

まで取得しようとしていました。普段ブラウザで Redmine を使っていると、これらは「便利な機能」という認識です。

ですが、サーバー側から見ると話は変わります。例えば PDF の生成であれば、データベースからチケットを取得し、テンプレートを組み立て、PDF をレンダリングし、レスポンスを返す。

HTML を返すだけとは比較にならないくらい重い処理になります。

つまり Googlebot は、リンクがあるから辿っているだけなのですが、Passenger からすると重い仕事ばかり持ち込まれている状態でした。

404でもPassengerは起きてしまう

Knowledgebase のアクセスについても同じです。

「存在しないページなんだから404で終わりでは?」

最初はそう考えていました。しかし実際には、Rails が起動し、ルーティングを行い、存在しないことを確認し、404 を返します。404 を返すこと自体は正しい動作です。ですが、その404を返すためにも Passenger は仕事をしています。

これでは、存在しないページへのアクセスであっても CPU を使い続けることになります。

ならばRailsまで届かせなければいい

ここで考え方を変えました。Rails が重いのではありません。Rails に仕事を渡してしまっていることが問題です。

であれば、Rails が起きる前にApacheで止めればいい。Apache を使っている以上、一番軽いのは Apache の段階で処理してしまうことです。

そこで mod_rewrite を使うことにしました。バーチャルホストの.confを以下のように変えていきます。

RewriteRule ^/projects/.*/knowledgebase - [G,L]
RewriteRule ^.*/knowledgebase - [G,L]

まずは削除済みの Knowledgebase。続いて、

RewriteCond %{HTTP_USER_AGENT} (Googlebot|GoogleOther|bingbot|Baiduspider) [NC]
RewriteRule ^/(projects/[^/]+/)?issues\.(pdf|csv|atom)$ - [G,L]

PDF、CSV、Atom のエクスポート。さらに、

RewriteCond %{QUERY_STRING} (sort=|set_filter=|per_page=) [NC]
RewriteRule ^/(projects/[^/]+/)?issues - [G,L]

検索エンジンが総当たりしている動的クエリも Apache 側で受け止めることにしました。

404ではなく410を選んだ理由

404でも目的は達成できます。ですが、今回は410 Goneを選びました。

404エラーは、「今は見つからない」という意味です。検索エンジンから見ると、

「また後で来れば復活しているかもしれない」

という扱いになります。一方、410 Gone は、

「もう永久にありません。」

という宣言です。今回削除した Knowledgebase は、将来復活させる予定はありません。だったら、その意思を HTTP ステータスとして返した方が正確です。

robots.txtも追加した

もちろん、Apache の設定だけではありません。今後の巡回そのものを減らすため、robots.txt にも、

Disallow: /projects/*/knowledgebase*
Disallow: /*?*sort=
Disallow: /*?*set_filter=

などを追加しました。ただし、ここは誤解しやすいところです。robots.txt は即効性のある仕組みではありません。

既に巡回予定へ入っているリクエストは、そのまま飛んできます。まず Apache で止める。その上で robots.txt により今後の巡回を減らす。この二段構えにしました。

効果はすぐに現れた

設定を反映し、Passenger(mod_passengerなのでapacheそのもの)を再起動します。

その後、再び CPU 使用率を確認すると、先ほどまで70〜90%を推移していた Passenger が数%程度で落ち着いていました。

production.log からも

  • Knowledgebase の404、
  • IssuesController の大量アクセス、
  • 500.html

の RoutingError は姿を消しています。試しに curl から Googlebot の User-Agent を付けてアクセスしてもApache が Rails を起動することなく、即座に

HTTP/1.1 410 Gone

を返しました。狙い通りです。Passengerに到達すること無くは何も仕事をしていません。

今回学んだこと

今回改めて感じたのは、「プラグインを削除すること」と、「削除したことを検索エンジンへ伝えること」は全く別の話だということでした。

Redmine 側では綺麗にプラグインをアンインストールできていても、Google は昔の URL を覚えています。そして真面目に巡回を続けます。そのリクエストを Rails まで届けてしまえば、存在しないページであっても Passenger は起き、CPU を使います。

だからこそ、410エラーにより「もうありません。」という判断を Apache が先に行う。

今回のケースでは、この水際防衛が一番効果的でした。

Redmine に限らず、長く運用してきた Web アプリケーションでは、大きな整理やプラグインの削除を行った後、一度アクセスログを眺めてみることをおすすめします。

自分ではとっくに忘れていた URL を、検索エンジンは驚くほど律儀に覚えているものです。そして、それが思わぬサーバー負荷につながっていることも、決して珍しくありません。

余談「Apacheは実は柔軟」

これは私の持論なのですが、超大手プラットフォームや大手配信サイトではapacheは重い。nginxこそ主流だ的な記事を見かけますが筆者のように

多数のサーバを立てられない
それでもWebアプリを多数動かしたい
セキュリティも譲れない
場合はApacheの方が驚くほど柔軟なパターンがあります。その辺の話はまた改めて。

Redmine 6.x移行後の運用記(前編) Passengerが静かにならない。Redmine 6.1移行後に始まった高負荷の正体を追う

Redmine 6.1への移行は無事に終わりました。

Redmine 5.1からのメジャーアップグレードということもあり、今回は不要になったプラグインをかなり整理しています。長年使い続けてきた環境だっただけに、「使っていないものは思い切って切る」という判断をしました。

しかし、Webで公開していると思わぬトラブルが発生しました。

CPUが一向に下がらない

切り替えた Redmine 6.1 を監視していると、Passenger の CPU 使用率が妙な動きをしています。

%CPU   %MEM   PID      USER     COMMAND
--------------------------------------------------------------------------------------
78.1   4.7    560493   www-data Passenger RubyApp: /home/www-data/redmine_v6 (prod)
38.4   3.8    560422   www-data Passenger AppPreloader: /home/www-data/redmine_v6

何も触っていないのに 70~90% を行ったり来たりしています。移行直後なので、

  • 「まだ何か設定を忘れているのか?」
  • 「プラグインを外した影響が残っているのか?」

と考えながら調査を始めました。

まず Passenger を見ようとした

最初に確認したのは Passenger 自身です。普段であれば passenger-status を実行すれば状況はある程度分かります。

ところが今回は、それすら使えませんでした。UNIX ドメインソケットのパス長制限(108バイト)に引っ掛かり、ArgumentError が発生してしまいます。

管理コマンドが使えない以上、地道にログを追うしかありません。そこで production.log と netstat を確認してみることにしました。

最初に見えたもの

production.log を眺めていると、最初に目に入ったのは見覚えのある URL でした。

Knowledgebase。

v5.1→v6.1へ移行で完全に削除したプラグインです。ところが、その URL へ今でもアクセスが来ています。

「検索エンジンが昔の URL を覚えているのか。」

最初はそう考えました。しかし、ログを読み進めていくと、それだけでは説明が付きません。

他にも出てくる動き

アクセスされているのは Knowledgebase だけではありません。

チケット一覧。

しかも普通に開いているわけではなく、

  • sort=
  • page=
  • set_filter=

こういったパラメータ付きの URL が大量に飛んできます。

さらに、

  • PDF
  • CSV
  • Atom

といったエクスポートまで巡回しています。

ここまで来ると、「404 が増えている」という話ではなくなってきました。クローラーがかなり重い処理を次々と要求しています。その頃、netstat を見ると HTTPS セッションが大量に張り付いたままになっていました。

ここでようやく、一つの可能性が頭に浮かびます。

「もしかして、これ全部クローラーなのでは……?」
「というか、悪質クローラーは止めている(includeでシャットダウンしている)はずでは……?」

そして、もしそうだとしたら、Rails 側で受け止め続けていいのか。

次回はログをさらに追い掛けながら、最終的に Googlebot による旧 URL の巡回と、動的クエリの総当たりが Passenger を疲弊させていた こと、そして Apache の mod_rewrite410 Gone を使って水際で止めるまでの経緯を書いていこうと思います。

刺身とフライ。

いつも訪れる定食屋にて。

鰹の刺身定食、そして

あじフライを注文。

たっぷり脂の乗った戻り鰹。ここは丁寧に生姜とネギが別添えなので味変にリズムつけられます。

ここで揚げ物を食べないのはこの店への敬意が欠けるとしてサクサクのアジフライ。ドラムスティックのように尻尾を持って食べられるのが最高です。

ですが、真に驚くべきは、最後に口を支配するのがご飯の甘みと香り。刺身もフライも味噌汁•漬物すら上書きする主食の破壊力はまさに定食屋の面目躍如でした。

【WAF検知ログ解析】攻撃者が探しているのは wp-config.php だけではない。そのバックアップでもある。(not only A but also B)

はじめに

以前、.env ファイルを狙った大量スキャンについて記事を書きました。

そのとき紹介したのは、「設定ファイルが公開されていないか」を世界中で機械的に探し回るボットの話です。

今回観測したログは、その続編とも言えるものでした。

狙われていたのは wp-config.php ……ではありません。

/wp-config.php.bak
/wp-config.php.old

つまり、人間が「作業時に残しておいたバックアップ」です。

今回もホスト名・IPアドレス・ディレクトリ名などは無害化しています。例によって、テロリストに名前を与えていません。

観測されたログ(マスキング済み)

[Fri Sep 04 00:55:48 2026] ModSecurity:
Rule 920440
URL file extension is restricted by policy
URI: /wp-config.php.bak

[Fri Sep 04 00:55:48 2026] ModSecurity:
Rule 930130
Restricted File Access Attempt
URI: /wp-config.php.bak

[Fri Sep 04 00:55:48 2026] ModSecurity:
Rule 920440
URL file extension is restricted by policy
URI: /wp-config.php.old

[Fri Sep 04 00:55:48 2026] ModSecurity:
Rule 930130
Restricted File Access Attempt
URI: /wp-config.php.old

数百ミリ秒という短時間で、.bak.old の両方を試しています。ブラウザで人間が操作しているとは考えにくく、自動化されたスキャナーによる探索と見てよいでしょう。

なぜ .bak.old を探すのか?

攻撃者が欲しいのは、バックアップファイルそのものではありません。その中に書かれている情報と「運用者の癖」です。

wp-config.php には、

  • データベースの接続情報
  • WordPressの認証キー(AUTH_KEY や SECURE_AUTH_KEY)
  • Cookieの暗号化キー
  • テーブルプレフィックス
  • サイト固有の設定

などが保存されています。本来の wp-config.php はPHPとして実行されるため、その内容をブラウザから読むことはできません。

しかし、

  • wp-config.php.bak
  • wp-config.php.old

になると話は別です。WebサーバーはこれらをPHPとして扱わず、単なるテキストファイルとして返してしまう場合があります。

このテキストファイルが表示されてしまえば、攻撃者はWordPressの脆弱性を探す必要すらありません。データベースへ直接接続するための情報が、そのまま手に入ってしまうからです。

玄関の鍵を壊そうとしているのではありません。玄関脇の植木鉢に、合鍵が入っていないかを確認している状態です。

本当に見られているのは「運用」

ここで興味深いのは、攻撃者が .bak を探していることではありません。

「そのようなファイルを公開領域へ置く運用をしているか」

を見ています。例えば

cp wp-config.php wp-config.php.bak
  • 編集前の退避。
  • FTPソフトが自動生成したバックアップ。
  • エディタが保存した一時ファイル。

どれも作業中であれば珍しいものではありません。問題なのは、それを公開ディレクトリへ置いたままにしてしまうことです。

もし .bak が見つかったら、多くのスキャナーはそこで終わりません。

続けて

  • wp-config.php.save
  • wp-config.php.tmp
  • wp-config.php.orig
  • backup/
  • old/
  • test/

などを次々に探し始めます。理由は単純です。

「バックアップを公開ディレクトリへ置く運用をしているなら、他にも何か残っている可能性が高い。」

と判断するからです。つまり、.bak はゴールではありません。攻撃者にとっては

「このサーバーは、まだ掘る価値がある」

という判断材料です。

ModSecurityは何を見ていたのか

今回ヒットしたのは二つのルールでした。

Rule 920440

まず反応したのは

URL file extension is restricted by policy

というルールです。.bak.old といった、本来ブラウザから取得されるべきではない拡張子を検知しています。

つまり、「その拡張子をHTTPで取りに来る時点で怪しい」という考え方です。

Rule 930130

続いて

Restricted File Access Attempt

が反応しています。こちらはファイル名そのものを見ています。

  • 危険な拡張子
  • 危険なファイル名

という二方向から同じリクエストを評価していました。一つのルールだけではなく、多層で検査するOWASP CRSらしい構成です。

ボットは「運用者の癖」を学習している

以前の記事では、

「多くの攻撃は、意志を持たず、目標を自動的に追尾するボットが大半を占めています。」

と書きました。今回のログも、まさにその延長線上にあります。ボットは感情を持って

「この管理者は雑そうだ」

と考えているわけではありません。過去の膨大な成功例から、

  • .bak が見つかったサーバーは他にも置き忘れが多い
  • .env.production が見つかる環境は設定管理も甘い
  • .git が公開されているサーバーはソース管理も甘い

という"統計"を利用しているだけです。つまり攻撃者は、サーバーを見ているようで、その向こうにいる運用者の習慣を見ています。

ソフトウェアの脆弱性だけではなく、人間の癖まで含めてスキャン対象になっているということです。

おわりに

以前は、ソフトウェアの脆弱性を突く攻撃が主流でした。もちろん今もゼロデイ攻撃は存在します。

しかしログを眺めていると、それと同じくらい多いのが

「人間が置き忘れたものを探す攻撃」

です。

  • .env
  • .bak
  • .old

どれも高度な脆弱性ではありません。運用の隙です。だからこそ、防御も特別な技術だけではありません。

  • 不要なファイルを公開ディレクトリへ置かない。
  • 作業が終わったら必ず片付ける。
  • 公開前に一度確認する。

そうした当たり前の積み重ねが、結果として最も強い防御になります。

それを防ぐための筆者の手

/etc/conf_backup

のような、まず公開ディレクトリではないディレクトリを作り、

sudo cp -pi /home/www-data/wordpress/wp-config.php /etc/conf_backup/wp-config.php.conf.$(date +%Y%m%d)

のように別ディレクトリに作ります。(もちろん、/etcがWeb経由で閲覧できる状況になっているのであれば、それは「バックアップが見えるかどうか」を心配する段階ではありません。サーバーそのものの侵害を疑うべき状況です)

また、変数による日付自動付与により

  • 最新版2026-3(承認済み).xlsx
  • 2026確定版(社長の意見反映).xlsm
  • 最新確定版2026_コピー(2)セキュリティ委員修正済み.xlsx
  • 改訂版2026_new_コピー(1)レビュー済み.xlsm
  • 2026-10改訂最新版(消さないこと)重役確認済み.xlsx

のような「だから最新どれだよ」を防ぐことはある程度行えます。ちょっとした工夫で癖は防げるというお話しでした。

【WAF検知ログ解析】難読化された JavaScript 偵察ツールを ModSecurity はどう見抜いたのか

はじめに

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をじっくり読めたのは、攻撃がアプリケーションまで届かなかったからです。

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

WasabiをRedmineの添付ファイル保存先にしたら、画像の貼り付けだけ「早期削除」が気になり始めた話

Redmine の添付ファイル保存先として、Wasabi オブジェクトストレージを s3fs でマウントして利用しています。

運用自体は安定していましたが、ある日 Wasabi のダッシュボードを眺めていて違和感を覚えました。

Timed Object Storage(早期削除)の対象が、思ったより増えている。

以前、Nextcloud や Growi とオブジェクトストレージの組み合わせで、Timed Object Storage に長期間悩まされた経験(150日の亡霊)があります。

そのとき学んだのは、「動いている」ことと、「そのストレージに適した運用」であることは別だということでした。

その経験があったからこそ、今回も「また何か起きているのではないか」と考え、一つずつ確認してみることにしました。

添付方法によって違いがあるのでは?

まず比較したのは、画像の添付方法です。

確認した結果、少なくとも現時点では次のような傾向が見えました。

添付方法Timed Object Storage の発生
ファイル選択からアップロード確認できず
Ctrl+Vでクリップボード貼り付け確認
Enhanced UIなどの貼り付け機能確認

もちろん、これだけで原因が断定できるわけではありません。「画像を添付する」という同じ操作でも、添付方法によって内部処理が違う可能性は十分考えられます。

クリップボード貼り付けでは一時ファイルを生成・削除しているのかもしれませんし、Enhanced UI 側の実装が影響しているのかもしれません。

現時点では、そこまで踏み込んだ検証はできていません。

今回試してみる対策

以前の経験から、オブジェクトストレージへ直接細かなアクセスを繰り返す構成は、あまり相性が良くないと感じています。

そこで今回は、s3fs のキャッシュ機能を利用して、ローカル SSD をワンクッション挟む構成へ変更してみることにしました。

追加した主なオプションは次の3つです。

  • use_cache
  • stat_cache_expire=60
  • enable_content_md5

まずキャッシュディレクトリを作成します。

sudo mkdir -p /var/cache/s3fs_bucket_a /var/cache/s3fs_bucket_b
sudo chown -R www-data:www-data /var/cache/s3fs_bucket_a /var/cache/s3fs_bucket_b
sudo chmod 750 /var/cache/s3fs_bucket_a /var/cache/s3fs_bucket_b

続いて /etc/fstab を更新します。

cd /etc
sudo cp -pi fstab /etc/conf_backup/fstab.20260830
sudo nano fstab
--- /etc/conf_backup/fstab.20260830     2025-08-07 11:01:54.000000000 +0900
+++ fstab                               2026-08-30 19:58:37.000000000 +0900
@@ -2,8 +2,9 @@
 LABEL=BOOT      /boot   ext4    defaults        0 2
 LABEL=UEFI      /boot/efi       vfat    umask=0077      0 1
 /swapfile       none    swap    sw      0 0
-# Wasabi Bucket A (storage.example.com)
-s3fs#storage.example.com /mnt/wasabi fuse _netdev,allow_other,passwd_file=/home/sampleuser/.passwd-s3fs,url=https://s3.ap-northeast-1.wasabisys.com,use_path_request_style,uid=33,gid=33 0 0
 
-# Wasabi Bucket B (counter.example.org)
-s3fs#counter.example.org /mnt/wasabi2 fuse _netdev,allow_other,passwd_file=/home/sampleuser/.passwd-s3fs,url=https://s3.ap-northeast-1.wasabisys.com,use_path_request_style,uid=33,gid=33 0 0
+# Wasabi Bucket A (storage.example.com - ap-northeast-1)
+s3fs#storage.example.com /mnt/wasabi fuse _netdev,allow_other,passwd_file=/home/sampleuser/.passwd-s3fs,url=https://s3.ap-northeast-1.wasabisys.com,use_path_request_style,uid=33,gid=33,use_cache=/var/cache/s3fs_bucket_a,stat_cache_expire=60,enable_content_md5 0 0
+
+# Wasabi Bucket B (counter.example.org - ap-northeast-1)
+s3fs#counter.example.org /mnt/wasabi2 fuse _netdev,allow_other,passwd_file=/home/sampleuser/.passwd-s3fs,url=https://s3.ap-northeast-1.wasabisys.com,use_path_request_style,uid=33,gid=33,use_cache=/var/cache/s3fs_bucket_b,stat_cache_expire=60,enable_content_md5 0 0

変更後は再マウントします。(daemon-reloadしないと怒られました)

sudo umount /mnt/wasabi
sudo umount /mnt/wasabi2
sudo systemctl daemon-reload
sudo mount /mnt/wasabi
sudo mount /mnt/wasabi2

これで本当に改善するのか?

正直なところ、この記事を書いている時点ではまだ分かりません。今回の変更は、「クリップボード貼り付け時の一時ファイルが原因ではないか」という仮説に基づく対策です。

実際に Timed Object Storage の発生が止まるのか、それとも別の要因があるのかは、しばらく Wasabi のダッシュボードを見ながら経過観察する必要があります。

もし改善が確認できれば追記しますし、変化がなければ別の原因を探ることになります。

まとめ

今回の目的は、「原因を突き止めた」という報告ではありません。

Wasabi のダッシュボードで小さな違和感を見つけ、その原因として添付方法の違いに着目し、対策を試し始めたという記録です。

以前、オブジェクトストレージとの組み合わせで大きく痛い目を見た経験があるからこそ、「いつもと違う」を見逃さずに済みました。

サーバー運用では、エラーが出てから対応するよりも、違和感の段階で調べ始める方が結果として被害は小さく済みます。

今回の変更が正解かどうかは、これからの経過観察で判断したいと思います。

Page 1 of 303

Powered by WordPress & Theme by Anders Norén