説明
Java/Jakarta Expression Language(EL)は、文字列で書かれた式からオブジェクトのプロパティを読み書きし、メソッドを呼び出せます。信頼できない入力を式のソースとして使うと、データと実行構造の境界が失われ、ELインジェクションが発生します。
Jakarta Expression Language 6.0は、既定で java.lang.* をインポートし、公開された静的フィールド、メソッド、コンストラクターへの参照をサポートします。実際の影響は、評価コンテキストに登録したBean、関数、リゾルバー、インポートによって異なります。攻撃者はアクセス可能なデータを読み書きしたり、メソッドを呼び出したりできます。公開された機能によっては、OSコマンドの実行につながるおそれもあります。
主なAPIの動作は次のとおりです。
| API | 式を渡す引数 | 動作 |
|---|---|---|
ELProcessor.eval(expression) |
第1引数 | 式を直ちに評価します。 |
ELProcessor.getValue(expression, type) |
第1引数 | 式を評価し、結果を指定した型へ変換します。 |
ELProcessor.setValue(expression, value) |
第1引数 | 対象の式を評価し、末尾のプロパティや変数を変更します。第2引数はデータです。 |
ELProcessor.setVariable(name, expression) |
第2引数 | 後で参照する式を解析して変数に関連付けます。第1引数は変数名です。 |
ExpressionFactory.createValueExpression(context, expression, type) |
第2引数 | 後で評価する値の式を解析します。 |
ExpressionFactory.createMethodExpression(context, expression, ...) |
第2引数 | 後で呼び出すメソッドの式を解析します。 |
javax.el は従来のJava EEの名前空間、jakarta.el はJakarta EEの名前空間です。
想定される影響
- 評価コンテキストに公開されたアプリケーションデータの不正な閲覧や変更
- アクセス可能なオブジェクト、静的メソッド、コンストラクターの呼び出しによる機能の悪用
- ファイル、ネットワーク、OSコマンドのAPIに到達できる場合のサーバー権限での処理
- 計算量の大きい式やストリーム操作によるリソース消費
対処方法
- EL式のテキストは開発者が管理する固定文字列にしてください。リクエスト値や外部設定値を式に連結したり、そのまま解析・評価したりしないでください。
- 信頼できない値は、式のソースではなくデータとしてバインドしてください。
ELProcessor.defineBeanやVariableMapperを使い、その値を参照する固定式だけを評価してください。 - ユーザーが操作を選ぶ場合は、サーバー管理の有限のキーを、確認済みの定数式に対応付けてください。任意の文字列を受け入れる正規表現や一般的なエスケープ関数では、EL文法全体の安全性を保証できません。
- 固定式に必要なBean、関数、リゾルバー、インポートだけを公開し、最小権限で実行してください。これは多層防御であり、攻撃者が制御する式を安全にするサニタイザーではありません。
- ELが不要な機能では評価を取り除くか無効にしてください。Jakarta EL 6.0はJavaの
SecurityManagerへの参照を削除しているため、標準的なサンドボックス対策として依存しないでください。
例
変更前
import jakarta.el.ELProcessor;
import jakarta.servlet.http.HttpServletRequest;
final class UnsafeElEvaluation {
Object evaluate(HttpServletRequest request) {
String expression = request.getParameter("expression");
ELProcessor processor = new ELProcessor();
return processor.eval(expression); // リクエスト値をELソースとして評価
}
}
リクエスト値を ELProcessor.eval の式の引数に直接渡しています。攻撃者は、その環境でアクセスできるプロパティやメソッドを選ぶEL構文を挿入できます。
変更後
import jakarta.el.ELProcessor;
import jakarta.servlet.http.HttpServletRequest;
final class SafeElEvaluation {
Object display(HttpServletRequest request) {
String displayName = request.getParameter("displayName");
ELProcessor processor = new ELProcessor();
processor.defineBean("displayName", displayName); // リクエスト値はデータとしてバインド
return processor.eval("displayName"); // 式は開発者が固定
}
}
リクエスト値はBeanのデータとしてだけ登録し、評価する式は固定しています。複数の機能からユーザーが選ぶ場合も、選択キーをサーバー管理の定数式へ明示的に対応付けてください。
適用時の注意点
ExpressionFactory.create*Expression と ELProcessor.setVariable は、後で使う式を解析します。返された式や変数が実際に評価される箇所まで確認してください。入力は固定式のデータとして渡し、バインドした値を式のソースとして再評価しないでください。
参考資料
- Jakarta Expression Language 6.0仕様
- Jakarta EL 6.0
ELProcessorAPI - Jakarta EL 6.0
ExpressionFactoryAPI - CWE-917: Expression Language Injection
- OWASP Expression Language Injection
- OWASP Top 10:2025 A05 - Injection
- OWASP Top 10:2021 A03 - Injection
- OWASP ASVS 5.0.0 V1 - Encoding and Sanitization
- KISAソフトウェアセキュリティ弱点診断ガイド(2021)