flawopen.com/SSTI/FreeMarkerとVelocity
FreeMarkerとVelocityのテンプレートはユーザー入力に対して安全ですか?
信頼できるテンプレートに挿入されるユーザー値はデータです。ユーザーがテンプレートのソースを制御できる場合は異なり、テンプレート言語の式を書けます。式から到達できる範囲は、バージョン、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や最小限のデータモデルの代わりにはなりません。
リスクを下げ、制御の限界を理解する
- ✓FreeMarker: 信頼できないテンプレートでは
new_builtin_class_resolverをALLOWS_NOTHING_RESOLVERにし、任意のPOJOを拒否するSimpleObjectWrapperなどを使います。用途を限定した単純な値だけを渡します。 - ✓Velocity: 信頼できないテンプレートには
SecureUberspectorを使い、contextには必要な単純な値だけを入れます。文書化されたreflectionやオブジェクト生成経路を制限しますが、OSサンドボックスではありません。 - ✓両方: アプリのテンプレートは信頼でき、改変防止された場所から読み込みます。リクエスト値やユーザーが変更できるDB行をテンプレートソースにしないでください。
- ✓ユーザー作成テンプレートが製品要件なら: 最小権限の別プロセスでレンダリングし、ネットワーク、秘密情報、アプリの書き込み可能ファイルから隔離します。エンジン外でCPU、メモリ、実行時間を制限します。
エンジンの制限は多層防御であり、プロセス分離ではありません。サポート対象バージョンを使い、最新の公式指針に沿って設定を確認し、秘密情報や特権オブジェクトをレンダラーに渡さないでください。
よくある質問
関連しますが別のリスクです。ユーザーが制御するview名だけで式インジェクションが成立するわけではありません。ThymeleafまたはSpring Expression Languageの評価に至る実際のデータフローを確認し、信頼できない入力から式の断片を作らないでください。連携ライブラリを更新し、信頼されたアプリのロジックでviewを選びます。
テンプレート作者はエンジンとデータモデルが公開する機能を使えますが、必ずOSコマンド実行ができるとは限りません。編集権限を機微なコード作成権限として扱い、信頼された保守担当者に限定して変更を記録してください。
Mustacheのようなロジックレスエンジンは式やメソッド呼び出しの範囲を減らせますが、出力エスケープ、テンプレートソース管理、helperの機能も重要です。その信頼モデル向けのエンジンを選び、レンダリングを分離します。汎用JVMエンジンをサンドボックスとして扱わないでください。