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 표면은 구성에 따라 달라집니다. FreeMarker 기본 wrapper는 임의 POJO의 JavaBean 속성과 메서드를 노출할 수 있으며 접근 가능한 멤버는 wrapper와 접근 정책에 달려 있습니다. Velocity 기본 Uberspect는 VTL 속성을 공개 Java 메서드에 연결합니다. 모든 모델이 무제한 reflection을 제공한다는 뜻은 아닙니다. 서비스 객체, class loader, 요청 객체 등 강력한 기능이 모델에 포함되면 위험이 커집니다. 문자열 map과 단순 값을 우선하고 신뢰된 템플릿에 필요한 필드만 제공하세요.

FreeMarker built-in ?new는 TemplateModel 구현 클래스를 인스턴스화할 수 있습니다. Apache는 업로드 템플릿에서 클래스 해석을 제한하라고 권고합니다. SAFER_RESOLVER는 일부 클래스를 허용하고 ALLOWS_NOTHING_RESOLVER는 모든 해석을 거부합니다. 이 설정만으로 제한된 wrapper와 최소 데이터 모델을 대체할 수는 없습니다.

위험을 낮추고 한계를 이해하기

엔진 제한은 defense-in-depth이지 프로세스 격리가 아닙니다. 지원 버전을 사용하고 공식 최신 지침에 따라 구성을 확인하며 renderer에 비밀 정보나 권한 객체를 주지 마세요.

자주 묻는 질문

Thymeleaf도 영향을 받나요?

관련은 있지만 별개의 위험입니다. 사용자가 제어하는 view 이름만으로 expression injection이 성립하지는 않습니다. Thymeleaf 또는 Spring Expression Language 평가로 이어지는 실제 데이터 흐름을 확인하고 신뢰할 수 없는 입력으로 표현식 조각을 만들지 마세요. 연동 라이브러리를 최신 상태로 유지하고 신뢰된 앱 로직으로 view를 선택하세요.

관리자만 템플릿을 수정할 수 있습니다. 괜찮은가요?

템플릿 작성자는 엔진과 데이터 모델이 노출한 기능을 사용할 수 있지만, 항상 OS 명령 실행이 가능하다는 뜻은 아닙니다. 템플릿 수정 권한을 민감한 코드 작성 권한으로 보고 신뢰된 유지보수자에게만 주며 변경을 감사하세요.

사용자 제공 템플릿에 가장 안전한 엔진은 무엇인가요?

Mustache 같은 logic-less 엔진은 표현식과 메서드 호출 표면을 줄일 수 있지만 출력 인코딩, 템플릿 출처 통제, helper 기능도 중요합니다. 해당 신뢰 모델에 맞는 엔진을 고르고 렌더링을 격리하세요. 범용 JVM 엔진을 sandbox로 간주하지 마세요.

공식 출처 및 참고 자료

함께 보기 SSTI: 템플릿 인젝션의 원인과 수정 방법 Jinja2 render_template_string()은 안전한가요? 서버 사이드 템플릿 인젝션이란?