CVE-2022-24881: Improper Input Validation (CWE-20)
value: Ballcat Codegen provides the function of online editing code to generate templates. In versions prior to 1.0.0.beta.2, attackers can implement remote code execution through malicious code injection of the template engine. This happens because Velocity and freemarker templates are introduced but input verification is not done. The fault is rectified in version 1.0.0.beta.2.
CWE description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
CVSS v3 base score: 9.8
Published: 2022-04-26T16:15Z
Repository: https://github.com/ballcat-projects/ballcat-codegen
Language: Java
Files changed: ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/engine/FreemarkerTemplateEngine.java, ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/engine/TemplateEngine.java, ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/engine/TemplateEngineDelegator.java, ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/engine/VelocityTemplateEngine.java, ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/exception/TemplateRenderException.java, ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/service/impl/GeneratorServiceImpl.java
Commit date: 2022-04-25 15:56:06 +0800
Commit message: :lock: 修改模板引擎的默认安全策略，以防止RCE

Diff: diff --git a/ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/engine/FreemarkerTemplateEngine.java b/ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/engine/FreemarkerTemplateEngine.java
index 5c51aa3..62dfcc9 100644
--- a/ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/engine/FreemarkerTemplateEngine.java
+++ b/ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/engine/FreemarkerTemplateEngine.java
@@ -1,8 +1,9 @@
 package com.hccake.ballcat.codegen.engine;
 
 import com.hccake.ballcat.codegen.exception.TemplateRenderException;
+import freemarker.core.TemplateClassResolver;
 import freemarker.template.Configuration;
 import freemarker.template.Template;
 import lombok.SneakyThrows;
 import org.springframework.stereotype.Component;
 
@@ -21,10 +22,13 @@ public class FreemarkerTemplateEngine implements TemplateEngine {
 	private final Configuration configuration;
 
 	public FreemarkerTemplateEngine() {
 		this.configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
 		configuration.setDefaultEncoding(StandardCharsets.UTF_8.name());
+
+		// 安全处理 https://ackcent.com/blog/in-depth-freemarker-template-injection/
+		configuration.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
 	}
 
 	@Override
 	public TemplateEngineTypeEnum type() {
 		return TemplateEngineTypeEnum.FREEMARKER;
diff --git a/ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/engine/TemplateEngine.java b/ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/engine/TemplateEngine.java
index 3732859..0525522 100644
--- a/ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/engine/TemplateEngine.java
+++ b/ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/engine/TemplateEngine.java
@@ -1,7 +1,9 @@
 package com.hccake.ballcat.codegen.engine;
 
+import com.hccake.ballcat.codegen.exception.TemplateRenderException;
+
 import java.util.Map;
 
 /**
  * 模板引擎
  *
@@ -19,8 +21,8 @@ public interface TemplateEngine {
 	 * 渲染模板字符串
 	 * @param templateContent 模板字符串
 	 * @param context 渲染使用的上下文
 	 * @return 渲染完成后的字符串
 	 */
-	String render(String templateContent, Map<String, Object> context);
+	String render(String templateContent, Map<String, Object> context) throws TemplateRenderException;
 
 }
diff --git a/ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/engine/TemplateEngineDelegator.java b/ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/engine/TemplateEngineDelegator.java
index e7d885b..3404128 100644
--- a/ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/engine/TemplateEngineDelegator.java
+++ b/ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/engine/TemplateEngineDelegator.java
@@ -1,15 +1,16 @@
 package com.hccake.ballcat.codegen.engine;
 
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.StrUtil;
+import com.hccake.ballcat.codegen.exception.TemplateRenderException;
 
 import java.util.Map;
 
 /**
  * 模板引擎的委托者
- * 
+ *
  * @author hccake
  */
 public class TemplateEngineDelegator {
 
 	public final Map<TemplateEngineTypeEnum, TemplateEngine> templateEngineMap;
@@ -22,11 +23,12 @@ public class TemplateEngineDelegator {
 	 * 渲染模板字符串
 	 * @param templateContent 模板字符串
 	 * @param context 渲染使用的上下文
 	 * @return 渲染完成后的字符串
 	 */
-	public String render(TemplateEngineTypeEnum engineType, String templateContent, Map<String, Object> context) {
+	public String render(TemplateEngineTypeEnum engineType, String templateContent, Map<String, Object> context)
+			throws TemplateRenderException {
 		if (StrUtil.isEmpty(templateContent)) {
 			return StrUtil.EMPTY;
 		}
 		TemplateEngine templateEngine = templateEngineMap.get(engineType);
 		Assert.notNull(templateEngine, "未找到对应的模板引擎：{}", engineType);
diff --git a/ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/engine/VelocityTemplateEngine.java b/ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/engine/VelocityTemplateEngine.java
index e69e23a..ca0bc12 100644
--- a/ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/engine/VelocityTemplateEngine.java
+++ b/ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/engine/VelocityTemplateEngine.java
@@ -2,10 +2,11 @@ package com.hccake.ballcat.codegen.engine;
 
 import com.hccake.ballcat.codegen.exception.TemplateRenderException;
 import org.apache.velocity.VelocityContext;
 import org.apache.velocity.app.Velocity;
 import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
+import org.apache.velocity.util.introspection.SecureUberspector;
 import org.springframework.stereotype.Component;
 
 import java.io.StringWriter;
 import java.util.Map;
 import java.util.Properties;
@@ -20,20 +21,22 @@ public class VelocityTemplateEngine implements TemplateEngine {
 
 	static {
 		// 设置velocity资源加载器, 保留文件加载loader
 		Properties prop = new Properties();
 		prop.put("file.resource.loader.class", ClasspathResourceLoader.class.getName());
+		// 安全处理
+		prop.put("runtime.introspector.uberspect", SecureUberspector.class.getName());
 		Velocity.init(prop);
 	}
 
 	@Override
 	public TemplateEngineTypeEnum type() {
 		return TemplateEngineTypeEnum.VELOCITY;
 	}
 
 	@Override
-	public String render(String templateContent, Map<String, Object> context) {
+	public String render(String templateContent, Map<String, Object> context) throws TemplateRenderException {
 		VelocityContext velocityContext = new VelocityContext(context);
 		try (StringWriter sw = new StringWriter()) {
 			Velocity.evaluate(velocityContext, sw, "velocityTemplateEngine", templateContent);
 			return sw.toString();
 		}
diff --git a/ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/exception/TemplateRenderException.java b/ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/exception/TemplateRenderException.java
index b7ecee5..2e769e1 100644
--- a/ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/exception/TemplateRenderException.java
+++ b/ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/exception/TemplateRenderException.java
@@ -3,11 +3,11 @@ package com.hccake.ballcat.codegen.exception;
 /**
  * 模板渲染失败异常
  *
  * @author hccake
  */
-public class TemplateRenderException extends RuntimeException {
+public class TemplateRenderException extends Exception {
 
 	public TemplateRenderException(Exception e) {
 		super(e);
 	}
 
diff --git a/ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/service/impl/GeneratorServiceImpl.java b/ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/service/impl/GeneratorServiceImpl.java
index 28115e3..db71dbf 100644
--- a/ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/service/impl/GeneratorServiceImpl.java
+++ b/ballcat-codegen-backend/src/main/java/com/hccake/ballcat/codegen/service/impl/GeneratorServiceImpl.java
@@ -4,19 +4,22 @@ import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.io.IoUtil;
 import cn.hutool.core.util.StrUtil;
 import com.hccake.ballcat.codegen.constant.TemplateEntryTypeEnum;
 import com.hccake.ballcat.codegen.engine.TemplateEngineDelegator;
 import com.hccake.ballcat.codegen.engine.TemplateEngineTypeEnum;
+import com.hccake.ballcat.codegen.exception.TemplateRenderException;
 import com.hccake.ballcat.codegen.model.bo.FileEntry;
 import com.hccake.ballcat.codegen.model.bo.TableDetails;
 import com.hccake.ballcat.codegen.model.bo.TemplateFile;
 import com.hccake.ballcat.codegen.model.dto.GeneratorOptionDTO;
 import com.hccake.ballcat.codegen.model.entity.TemplateEntry;
 import com.hccake.ballcat.codegen.service.GeneratorService;
 import com.hccake.ballcat.codegen.service.TableInfoQuery;
 import com.hccake.ballcat.codegen.service.TemplateEntryService;
 import com.hccake.ballcat.codegen.util.GenUtils;
+import com.hccake.ballcat.common.core.exception.BusinessException;
+import com.hccake.ballcat.common.model.result.SystemResultCode;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
 import java.io.ByteArrayOutputStream;
@@ -131,26 +134,36 @@ public class GeneratorServiceImpl implements GeneratorService {
 		for (TemplateFile templateFile : templateFiles) {
 			FileEntry fileEntry = new FileEntry();
 			fileEntry.setType(templateFile.getType());
 
 			// 替换路径中的占位符
-			String filename = StrUtil.format(templateFile.getFilename(), context);
+			String templateFilename = templateFile.getFilename();
+			String filename = StrUtil.format(templateFilename, context);
 			fileEntry.setFilename(filename);
 
 			String parentFilePath = GenUtils.evaluateRealPath(templateFile.getParentFilePath(), context);
 			fileEntry.setParentFilePath(parentFilePath);
 
 			// 如果是文件
 			if (TemplateEntryTypeEnum.FILE.getType().equals(fileEntry.getType())) {
-				fileEntry.setFilePath(GenUtils.concatFilePath(parentFilePath, filename));
+				String filePath = GenUtils.concatFilePath(parentFilePath, filename);
+				fileEntry.setFilePath(filePath);
 				// 文件内容渲染
 				TemplateEngineTypeEnum engineTypeEnum = TemplateEngineTypeEnum.of(templateFile.getEngineType());
-				String content = templateEngineDelegator.render(engineTypeEnum, templateFile.getContent(), context);
-				fileEntry.setContent(content);
+
+				try {
+					String content = templateEngineDelegator.render(engineTypeEnum, templateFile.getContent(), context);
+					fileEntry.setContent(content);
+				}
+				catch (TemplateRenderException ex) {
+					String errorMessage = StrUtil.format("模板渲染异常，模板文件名：【{}】，错误详情：{}", templateFilename,
+							ex.getMessage());
+					throw new BusinessException(SystemResultCode.SERVER_ERROR.getCode(), errorMessage);
+				}
 			}
 			else {
-				String currentPath = GenUtils.evaluateRealPath(templateFile.getFilename(), context);
+				String currentPath = GenUtils.evaluateRealPath(templateFilename, context);
 				fileEntry.setFilePath(GenUtils.concatFilePath(parentFilePath, currentPath));
 			}
 
 			map.put(fileEntry.getFilePath(), fileEntry);
 		}

Vulnerable code: 	String render(String templateContent, Map<String, Object> context);
 * 
	public String render(TemplateEngineTypeEnum engineType, String templateContent, Map<String, Object> context) {
	public String render(String templateContent, Map<String, Object> context) {
public class TemplateRenderException extends RuntimeException {
			String filename = StrUtil.format(templateFile.getFilename(), context);
				fileEntry.setFilePath(GenUtils.concatFilePath(parentFilePath, filename));
				String content = templateEngineDelegator.render(engineTypeEnum, templateFile.getContent(), context);
				fileEntry.setContent(content);
				String currentPath = GenUtils.evaluateRealPath(templateFile.getFilename(), context);
Fixed code: import freemarker.core.TemplateClassResolver;

		// 安全处理 https://ackcent.com/blog/in-depth-freemarker-template-injection/
		configuration.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
import com.hccake.ballcat.codegen.exception.TemplateRenderException;

	String render(String templateContent, Map<String, Object> context) throws TemplateRenderException;
import com.hccake.ballcat.codegen.exception.TemplateRenderException;
 *
	public String render(TemplateEngineTypeEnum engineType, String templateContent, Map<String, Object> context)
			throws TemplateRenderException {
import org.apache.velocity.util.introspection.SecureUberspector;
		// 安全处理
		prop.put("runtime.introspector.uberspect", SecureUberspector.class.getName());
	public String render(String templateContent, Map<String, Object> context) throws TemplateRenderException {
public class TemplateRenderException extends Exception {
import com.hccake.ballcat.codegen.exception.TemplateRenderException;
import com.hccake.ballcat.common.core.exception.BusinessException;
import com.hccake.ballcat.common.model.result.SystemResultCode;
			String templateFilename = templateFile.getFilename();
			String filename = StrUtil.format(templateFilename, context);
				String filePath = GenUtils.concatFilePath(parentFilePath, filename);
				fileEntry.setFilePath(filePath);

				try {
					String content = templateEngineDelegator.render(engineTypeEnum, templateFile.getContent(), context);
					fileEntry.setContent(content);
				}
				catch (TemplateRenderException ex) {
					String errorMessage = StrUtil.format("模板渲染异常，模板文件名：【{}】，错误详情：{}", templateFilename,
							ex.getMessage());
					throw new BusinessException(SystemResultCode.SERVER_ERROR.getCode(), errorMessage);
				}
				String currentPath = GenUtils.evaluateRealPath(templateFilename, context);