この記事のポイント
- 深刻度
- —
- 対象
- Apache Struts 2 RESTプラグイン 7.2.1~7.3.0
- 悪用状況
- 不明
- 推奨対応
- 7.4.0以降へ更新し、既存の注釈必須設定に加えてany-setter用設定を有効化する。許可するメンバーと動的値の深さを注釈で指定し、正規入力と拒否対象の回帰確認を行う。
目次
Apache Struts 2のRESTプラグインで、Jacksonの@JsonAnySetterを使う入力に@StrutsParameterの制限が適用されない問題が公表されました。が10月29日に示した対象は7.2.1から7.3.0です。RESTのリクエストボディをアクションやモデルへ取り込むアプリケーションは、any-setterの使用箇所と設定を確認してください。
対応は7.4.0以降への更新だけでは終わりません。公式資料ではstruts.rest.anySetter.requireAnnotations=trueを設定し、動的なキーを受け入れるメンバーに@StrutsParameter(allowDynamicKeys = true)を付ける手順を示しています。新設定は互換性のため既定値がfalseで、既存のstruts.parameters.requireAnnotationsも有効であることが前提です。
スポンサーリンク
対象はRESTボディとany-setterの組み合わせ
本稿はJVN#82782759と、ApacheのStrutsParameter Annotation公式資料に基づく対応の解説です。取得した資料に番号やの記載がないため、番号や深刻度を補って扱いません。また、本件の実環境での悪用を確認したという資料ではありません。
通常のプロパティでは、RESTプラグインのParameterAuthorizingModuleがデシリアライズ時に許可されていない値の設定を防ぎます。一方、@JsonAnySetterは、通常の名前付きプロパティに該当しないキーを受け取るJacksonの仕組みです。任意の追加属性を収容する用途がある反面、その経路が通常の処理で包まれていないことが今回の問題です。
JVNは、struts.parameters.requireAnnotations=trueで、RESTボディからバインドされるアクションまたはモデルクラスに@JsonAnySetterが宣言されている場合に発生すると説明しています。だけを想定せず、アプリがRESTプラグインで処理するJSONやXMLのボディから、どのクラスへ値が届くかを調べます。URLにRESTという文字があるかどうかでは判定できません。
影響として示されているのは、any-setterのメンバーへ入る値に注釈による認可チェックが行われず、データ改ざんにつながる可能性です。資料が示していない不要や管理者権限取得へ言い換えることはできません。実際の業務への影響は、その追加属性を保存・参照する処理と、要求元に許可された操作によって変わります。
最初に実行版と入力先のクラスを対応付ける
以下は本サイトの確認手順案です。まず本番へ配備したStruts本体とRESTプラグインの実行版を、定義だけでなく成果物や配備記録から確認します。ソース側で依存を更新しても、古い成果物が稼働していれば対応は反映されません。複数のWebアプリやノードを持つ場合は、アプリ名、担当、配備版を一行ずつ台帳に残します。
次に、RESTアクションが受け取る型、その下の入れ子の型、Jacksonのany-setterを追います。メソッド上の@JsonAnySetterだけでなくフィールドや@JsonCreatorパラメータも公式資料の対象です。共通ライブラリのDTOを複数ので共有している場合は、宣言元の一箇所を直したことで全配備先へ届いたかを確認する必要があります。
検索の入口には、で@JsonAnySetter、struts.parameters.requireAnnotations、struts.rest.anySetter.requireAnnotationsを探す方法があります。ただし文字列が見つからないことだけで対象外にはしません。設定の読み込み元、共有ライブラリ、生成コード、配備時の差し替えがあるなら、その経路も担当者に確認します。これは調査案であり、網羅性を保証する自動検出法ではありません。
スポンサーリンク
7.4.0以降で保護を明示的に有効化する
JVNが案内する修正先は7.4.0およびそれ以降の最新版です。採用版は現在のアプリの対応条件と照合し、RESTプラグインを含むをそろえます。そのうえで、any-setter用の追加設定を有効にします。既存のパラメータ注釈を必須にする設定を外して新設定だけを追加しても、公式資料が示す保護の前提を満たしません。
<constant name="struts.parameters.requireAnnotations" value="true"/>
<constant name="struts.rest.anySetter.requireAnnotations" value="true"/>
上記は公式資料の設定名を使った最小例です。実際にはアプリの設定ファイルの適切な場所へ組み込み、起動後に反映を確認します。同名設定が別のファイルや配備環境で上書きされる構成なら、ファイルに書いた事実と有効な値を分けて管理してください。設定を追加しただけで試験を省略しないことが大切です。
動的キーを受け取らせるメソッドまたはフィールドには、allowDynamicKeys = trueを明示します。公式の例は、@JsonAnySetterを付けたsetExtra(String key, Object value)に@StrutsParameter(allowDynamicKeys = true, depth = 1)を併記する形です。必要な入口だけに付与し、バインドさせたくないメンバーへ機械的に注釈を付けないでください。
この注釈は業務上許されるキーの一覧を自動で作る仕組みではありません。追加属性として何を受け付け、その値をどこへ保存するかはアプリ側で決めます。例えば表示設定を受け取るつもりの入口で、内部の承認状態まで更新できる設計なら、動的キーの受け入れを許可した後も業務側の検証が必要です。この例は説明用で、特定アプリの被害を示すものではありません。
depthとcreatorの違いを試験へ反映する
公式資料によると、depthは動的キーごとの値の入れ子の深さを制限します。depth = 0ではスカラー値だけ、depth = 1では一段のオブジェクトや配列も受け入れます。許可より深い値や、注釈のないany-setterへ来たキーは破棄されます。無制限に大きな値へ広げるのではなく、実際に必要な入力の形から決めます。
@JsonCreatorのパラメータにあるany-setterは、新設定による保護下ではすべてのキーを拒否すると公式は説明しています。メソッドの例をコンストラクタの引数へそのまま移して、同じように許可できると考えないでください。creator経由の受信を使うアプリは、正規の入力が更新後も到達するかを重点的に確認します。
親のプロパティ自体が許可されていない場合、その配下のany-setterへは到達せず、親の部分木ごと処理されないとされています。追加属性が届かなくなった際は、any-setterの注釈だけでなく親の認可も調べます。また@JsonUnwrappedは名前付きプロパティであり、今回の動的キーの扱いと混同しないようにします。
スポンサーリンク
許可と拒否を対にした回帰確認を行う
本サイトが提案する試験は、隔離した検証環境で通常のデータを使い、受け入れるべき入力と拒否するべき入力を対にして確認する方法です。本番へ攻撃実証を送る必要はありません。まず正規の名前付きプロパティと、業務が必要とする追加属性の値が、意図したクラスへ設定されることを確認します。
次に、注釈を付けていないany-setter、許可した深さを超える値、未許可の親の下の属性を試験項目にします。の成功・失敗コードだけで判断せず、ハンドラが受け取った値と保存後の状態を照合します。値が黙って破棄される経路では、応答が成功でも業務の必要な値が欠けている可能性があるためです。
拒否されたキーの記録は、公式資料ではany-setterと理由ごとに一つのWARNであり、キーごとに一件ではありません。ログ件数を入力キー数と同じだと仮定した監視は避けます。記録された要求とアプリの処理結果を対応付ける際も、機密値を丸ごとログへ追加せず、試験IDや必要な識別子で追跡する方法を提案します。
試験が通った範囲も明記します。一つのRESTアクションで動いたことを、別のモデルや異なるデシリアライズ経路まで保証する結果にしないでください。特に共通DTOを共有するでは、同じ型でも到達する親のプロパティと設定が異なることがあります。エンドポイントと入力型の組を分母にして、未確認の組を残します。
更新までの緩和と互換性の扱い
JVNのワークアラウンドは、RESTのパラメータを受けるActionやModelで@JsonAnySetterを使わないこと、必要なら値を保存する前にアプリ側で検証することです。単に画面から入力欄を消しても、ボディを受け取るサーバー側の経路は止まりません。不要な動的入力を外せるか、依存するクライアントと確認します。
検証する場合は、許可するキーと値の型、深さ、保存先を設計として定めます。未知のキーを一度すべて格納し、後段で表示だけ隠す実装を、保存前の検証と同じものには扱いません。暫定変更には適用先、担当、期限を記録し、更新と追加設定の試験が完了した時点で恒久的な実装へ整理することを勧めます。
正規のクライアントが追加属性に依存している場合、新設定により以前は入った値が届かなくなる可能性があります。その際に保護設定を一括でfalseへ戻す前に、どのメンバーが必要な入口なのか、注釈とdepthが適切かを切り分けます。新設定の既定値がfalseなのは互換性のためであり、更新後のfalseを安全性の確認結果とみなすことはできません。
入力認可と業務認可を別々に確認する
@StrutsParameterは、要求の値をどのメンバーへ入れるかを制御する注釈です。それを通過した値を使って、誰がどのレコードを更新できるかという業務認可まで代替するものではありません。今回の改修では、入力の受け入れ範囲を閉じる作業と、保存先の所有者・操作権限の確認を分けてレビューします。
ModelDrivenについて公式資料は、モデルを要求の受け口に指定するとモデルのメンバーが注釈要件の例外になることも説明しています。any-setterに関する一節だけを切り出して、すべてのモデルに同じ許可リストが掛かると仮定しないでください。アプリのバインド先がアクションかモデルかを確認し、その構成で許される入力全体を評価します。
今回の完了条件は、対象のREST経路を特定し、修正を含む版が稼働し、二つの設定と必要な注釈が有効になり、正規入力と拒否対象の結果を確認できたことです。過去の不正なデータ変更の調査は別の作業です。更新後の正常性だけで被害なしとは判断せず、利用者の申告や不審な変更があれば、保存履歴など利用可能な資料を保全して調べてください。
出典・一次情報
本記事は以下の公開情報をもとに作成しています。最新の状況は各リンク先で確認してください。













