FlawOpen

flawopen.com/SSTI/FreeMarkerとVelocity

FreeMarkerとVelocityのテンプレートはユーザー入力に対して安全ですか?

CWE-1336: Server-Side Template Injectionリファレンスページ
要点

信頼できるテンプレートに挿入されるユーザー値はデータです。ユーザーがテンプレートのソースを制御できる場合は異なり、テンプレート言語の式を書けます。式から到達できる範囲は、バージョン、wrapperやintrospector、モデルのオブジェクト、アプリの権限によって変わります。すべてのテンプレートインジェクションがOSコマンドを実行すると決めつけず、信頼できないテンプレートソースは実行ロジックとして扱い、アプリのプロセス外でレンダリングしてください。

脆弱な例
// 危険:リクエスト値をテンプレートソースとして解析
String src = "Hello " + userSuppliedName;
Template t = new Template("t", src, cfg);
t.process(model, writer);

// 危険:ユーザー制御のVTL文字列を評価
Velocity.evaluate(context, writer,
                  "log", userTemplate);
修正版
// 信頼されたclasspathテンプレート;モデルはスカラー値のみ
Configuration cfg = new Configuration(
    Configuration.VERSION_2_3_34);
cfg.setTemplateLoader(new ClassTemplateLoader(
    App.class, "/templates"));
cfg.setNewBuiltinClassResolver(
    TemplateClassResolver.ALLOWS_NOTHING_RESOLVER);
cfg.setObjectWrapper(new SimpleObjectWrapper(
    Configuration.VERSION_2_3_34));
Template t = cfg.getTemplate("hello.ftl");
Map<String,Object> model = Map.of(
    "name", userSuppliedName);
t.process(model, writer);

// Velocity:信頼されたテンプレートと制限付きintrospection(多層防御)
Properties p = new Properties();
p.setProperty("resource.loaders", "class");
p.setProperty("resource.loader.class.class",
    ClasspathResourceLoader.class.getName());
p.setProperty("runtime.introspector.uberspect",
    SecureUberspector.class.getName());
VelocityEngine engine = new VelocityEngine();
engine.init(p);
Template vt = engine.getTemplate("hello.vm", "UTF-8");
VelocityContext vc = new VelocityContext();
vc.put("name", userSuppliedName);
vt.merge(vc, writer);

Javaエンジンが危険な機能を公開し得る理由

テンプレートはデータモデル上で式を評価しますが、利用できるJava APIは設定によって異なります。FreeMarkerの既定wrapperは任意のPOJOのJavaBeanプロパティやメソッドを公開することがあり、実際に使えるメンバーはwrapperとアクセス方針に依存します。Velocityの既定UberspectはVTLプロパティを公開Javaメソッドに対応づけます。すべてのモデルが無制限のreflectionを許すという意味ではありません。サービス、ClassLoader、リクエストオブジェクトなど強い機能をモデルに含めるとリスクが高まります。文字列マップや単純な値を優先し、信頼されたテンプレートに必要な項目だけを公開します。

FreeMarkerの組み込み ?new は TemplateModel を実装するクラスを生成できます。Apacheはアップロードされたテンプレートでクラス解決を制限するよう勧めています。SAFER_RESOLVER は一部のクラスを許可し、ALLOWS_NOTHING_RESOLVER はすべてのクラス解決を拒否します。この設定は、制限付きwrapperや最小限のデータモデルの代わりにはなりません。

リスクを下げ、制御の限界を理解する

エンジンの制限は多層防御であり、プロセス分離ではありません。サポート対象バージョンを使い、最新の公式指針に沿って設定を確認し、秘密情報や特権オブジェクトをレンダラーに渡さないでください。

よくある質問

Thymeleafも影響を受けますか?

関連しますが別のリスクです。ユーザーが制御するview名だけで式インジェクションが成立するわけではありません。ThymeleafまたはSpring Expression Languageの評価に至る実際のデータフローを確認し、信頼できない入力から式の断片を作らないでください。連携ライブラリを更新し、信頼されたアプリのロジックでviewを選びます。

管理者だけがテンプレートを編集できます。それなら許容できますか?

テンプレート作者はエンジンとデータモデルが公開する機能を使えますが、必ずOSコマンド実行ができるとは限りません。編集権限を機微なコード作成権限として扱い、信頼された保守担当者に限定して変更を記録してください。

ユーザー提供テンプレートに最も安全なエンジンはどれですか?

Mustacheのようなロジックレスエンジンは式やメソッド呼び出しの範囲を減らせますが、出力エスケープ、テンプレートソース管理、helperの機能も重要です。その信頼モデル向けのエンジンを選び、レンダリングを分離します。汎用JVMエンジンをサンドボックスとして扱わないでください。

公式資料と参考情報

関連項目 SSTI:テンプレートインジェクションの原因と対策 Jinja2 の render_template_string() は安全か サーバーサイドテンプレートインジェクションとは?