リモートコマンド実行のセキュリティ脆弱性が発見され一気に名前があがったDMI(Dynamic Method Invocation)という機能がStruts2にあります。
このDMI、昨今のバージョンではデフォルトで機能しなくなりましたが、下位互換性のため有効にする設定もあります。
DMIの機能
Struts2のActionクラスは通常、execute()メソッドにActionクラスで実行する処理を書きます。
例えばSampleActionクラスが/sample.action
のURLで呼び出されると設定してあるとすると、Actionクラスの実装は次のようになります。
import com.opensymphony.xwork2.ActionSupport;
public class SampleAction extends ActionSupport {
@Override
public String execute() throws Exception {
// 実装内容
//
return ActionSupport.SUCCESS;
}
}
DMIを有効にすると、このActionクラスのexecuteメソッド以外を呼び出すことが可能です。つまり1つのActionクラスに対して、複数の実装が可能になり、類似する処理や機能を1つのActionクラスにまとめることができる設計になります。
public class SampleAction extends ActionSupport {
@Override
public String execute() throws Exception {
return ActionSupport.SUCCESS;
}
public String search() throws Exception {
// 一覧検索結果の画面を出力
return "search";
}
public String detail() throws Exception {
// 検索結果の1件を出力
return "detail";
}
}
このようなActionクラスを作ります。
execute()
を実行するのは/sample.action
でした。DMIを有効にすると、search()
やdetail()
を次のURLで呼び出すことができます。呼び出し方にはいくつか用意されています。
Bang記法
URLの末尾に !(Bang)をつけ、その後にメソッド名を記載します。
リクエストURL | 起動するメソッド |
---|---|
/sample.action | execute() |
/sample!search.action | search() |
/sample!detail.action | detail() |
クエリ文字列記法
クエリ文字列に起動するメソッド名を記載します。
リクエストURL | 起動するメソッド |
---|---|
/sample.action?method:search=foo | search() |
method:起動するメソッド名=任意の値 で設定します。任意の値に設定した内容は破棄されます。
POSTパラメータ記法
<s:hidden name="method:search" value="foo" />
Struts2用のタグでPOSTパラメータに、接頭辞 method:
をつけることで接頭辞以降の文字列で呼び出すメソッドを決めます。これもvalueの値は破棄されます。
DMIの問題点
まずActionクラスに複数のURLからリクエスト受けるようになりますが、これにより次の弊害があります。
- Actionクラスに対する入力チェック(Validation)が同じものを使ってしまう
- 入力チェックエラー時の遷移先が同じになってしまう
- 任意のメソッドを実行できる
Actionクラスに対する入力チェック(Validation)も同じものが使われてしまうため、Actionクラスの実装やハンドリングが難しくなります。特に入力チェックエラー時の遷移先は、Struts2標準ではinputで定義した遷移先に自動遷移してしまいます。複数のActionや画面で入力チェックエラーのときは同じ画面へ遷移する画面…でしたら問題はないのですが、果たしてそういう画面がそんなにたくさんあるでしょうか…。
他にもActionクラスはリクエストパラメータの内容をActionクラスのフィールドに格納し、JSPなどに出力する際の値も格納します。全く同じ変数の組み合わせを複数の画面で使い回すとなると、とある画面では渡しても表示しない、ある画面では使う、といったことになり、不具合の温床にもなりそうです。
最後にセキュリティ的な問題です。POST時のパラメータだけでなく、クエリ文字列にmethod:とつけたパラメータの後に指定した内容はOGNLによって実行されます。これが今回の脆弱性の引き金で、既に攻撃用コードも公開されていますのでここには書きませんが、method:以降の記述でJavaのオブジェクトを生成できますので、ProcessBuilderも作れます。
それでもDMI使いたい!場合
Struts2.3からStrict DMIという新機能(?)がサポートされています。
URLで指定できるActionクラスのメソッドを struts.xmlのActionクラス設定に<allowed-methods></allowed-methods>
で指定しなければ動作しません。
なお、Struts2.5では、StrictDMIあらため、Strict Method Invocation(SMI)が採用されるそうです。
まとめ
DMIの採用はアプリケーションの設計的に見ても決して良くないので、使用はやめましょう