mirror of
http://git.pig4cloud.com/pig/pigx.git
synced 2026-11-08 20:49:00 +08:00
fix(codegen): 加固 Velocity 模板渲染
This commit is contained in:
1 parent
d3966d64f5
commit
a5d3dff3fc
1 file changed
+93
-27
+93
-27
@@ -1,15 +1,18 @@
|
||||
package com.pig4cloud.pigx.codegen.util;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.apache.velocity.Template;
|
||||
import org.apache.velocity.VelocityContext;
|
||||
import org.apache.velocity.app.Velocity;
|
||||
import org.apache.velocity.app.VelocityEngine;
|
||||
import org.apache.velocity.runtime.RuntimeConstants;
|
||||
import org.apache.velocity.tools.generic.DateTool;
|
||||
import org.apache.velocity.tools.generic.MathTool;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
@@ -21,6 +24,82 @@ import java.util.Properties;
|
||||
@Service
|
||||
public class VelocityKit {
|
||||
|
||||
/**
|
||||
* 模板高危关键字黑名单。
|
||||
* <p>
|
||||
* 聚焦反射 / 命令执行原语,正常代码生成模板不会出现这些 token,命中即拒绝渲染(早失败)。 真正的安全边界是引擎层的
|
||||
* {@link org.apache.velocity.util.introspection.SecureUberspector}, 本黑名单仅作纵深防御。
|
||||
*/
|
||||
private static final String[] DANGER_TOKENS = { "getClass", "forName", "getRuntime", "java.lang.Runtime",
|
||||
"java.lang.System", "ProcessBuilder", "ClassLoader", "java.lang.reflect", "getDeclaredMethod",
|
||||
"scriptEngine", "javax.script.ScriptEngine", "#evaluate" };
|
||||
|
||||
/**
|
||||
* Velocity 安全沙箱限制访问的包。
|
||||
*/
|
||||
private static final String[] RESTRICTED_PACKAGES = { "java.lang.reflect", "org.openjdk.nashorn", "bsh" };
|
||||
|
||||
/**
|
||||
* Velocity 安全沙箱限制访问的类。
|
||||
*/
|
||||
private static final String[] RESTRICTED_CLASSES = { "java.lang.Class", "java.lang.ClassLoader",
|
||||
"java.lang.Compiler", "java.lang.InheritableThreadLocal", "java.lang.Package", "java.lang.Process",
|
||||
"java.lang.ProcessBuilder", "java.lang.Reflect", "java.lang.Runtime", "java.lang.RuntimePermission",
|
||||
"java.lang.SecurityManager", "java.lang.System", "java.lang.Thread", "java.lang.ThreadGroup",
|
||||
"java.lang.ThreadLocal", "java.net.Socket", "javax.management.MBeanServer", "javax.script.ScriptEngine" };
|
||||
|
||||
/**
|
||||
* 安全的 Velocity 引擎,全局初始化一次。
|
||||
* <p>
|
||||
* 通过 {@link org.apache.velocity.util.introspection.SecureUberspector} 禁止 {@code getClass()}
|
||||
* 及对 {@code java.lang.Runtime/System/Thread/ProcessBuilder/Process/ClassLoader/Class} 和
|
||||
* {@code java.lang.reflect.*} 的方法调用,从根上阻断 Velocity SSTI → 反射 → RCE。
|
||||
*/
|
||||
private static final VelocityEngine ENGINE = buildSecureEngine();
|
||||
|
||||
private static VelocityEngine buildSecureEngine() {
|
||||
Properties prop = new Properties();
|
||||
// classpath 资源加载(保留 render() 按名加载模板的能力)
|
||||
prop.put(RuntimeConstants.RESOURCE_LOADERS, "class");
|
||||
prop.put("resource.loader.class.class",
|
||||
"org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
|
||||
// 关键:安全 Uberspector,禁用反射 / getClass 访问危险类
|
||||
prop.put(RuntimeConstants.UBERSPECT_CLASSNAME,
|
||||
"org.apache.velocity.util.introspection.SecureUberspector");
|
||||
// 显式保留 Velocity 默认限制,并扩展封禁脚本引擎等二次利用面
|
||||
prop.put("introspector.restrict.packages", String.join(",", RESTRICTED_PACKAGES));
|
||||
prop.put("introspector.restrict.classes", String.join(",", RESTRICTED_CLASSES));
|
||||
VelocityEngine engine = new VelocityEngine();
|
||||
engine.init(prop);
|
||||
return engine;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染前的安全校验:拦截包含反射 / 命令执行关键字的模板。
|
||||
* @param template 模板内容
|
||||
*/
|
||||
private static void checkTemplateSecurity(String template) {
|
||||
if (StrUtil.isBlank(template)) {
|
||||
return;
|
||||
}
|
||||
Assert.isFalse(StrUtil.containsAnyIgnoreCase(template, DANGER_TOKENS), "模板包含非法内容,已被安全策略拦截");
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建注入工具库的渲染上下文
|
||||
* @param map 数据模型
|
||||
* @return VelocityContext
|
||||
*/
|
||||
private static VelocityContext buildContext(Map<String, Object> map) {
|
||||
VelocityContext context = new VelocityContext(map);
|
||||
// 函数库
|
||||
context.put("math", new MathTool());
|
||||
context.put("dateTool", new DateTool());
|
||||
context.put("dict", new DictTool());
|
||||
context.put("str", new NamingCaseTool());
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Velocity 模板渲染方法
|
||||
* @param template 模板
|
||||
@@ -28,41 +107,28 @@ public class VelocityKit {
|
||||
* @return 渲染结果
|
||||
*/
|
||||
public static String render(String template, Map<String, Object> map) {
|
||||
// 设置velocity资源加载器
|
||||
Properties prop = new Properties();
|
||||
prop.put("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
|
||||
Velocity.init(prop);
|
||||
|
||||
VelocityContext context = new VelocityContext(map);
|
||||
// 函数库,使用 Lambda 表达式简化代码
|
||||
Optional.of(new MathTool()).ifPresent(mt -> context.put("math", mt));
|
||||
Optional.of(new DateTool()).ifPresent(dt -> context.put("dateTool", dt));
|
||||
Optional.of(new DictTool()).ifPresent(dt -> context.put("dict", dt));
|
||||
Optional.of(new NamingCaseTool()).ifPresent(nct -> context.put("str", nct));
|
||||
|
||||
// 渲染模板,使用 Lambda 表达式简化代码
|
||||
checkTemplateSecurity(template);
|
||||
VelocityContext context = buildContext(map);
|
||||
StringWriter sw = new StringWriter();
|
||||
Optional.ofNullable(Velocity.getTemplate(template, CharsetUtil.UTF_8)).ifPresent(tpl -> tpl.merge(context, sw));
|
||||
getTemplate(template).merge(context, sw);
|
||||
return sw.toString();
|
||||
}
|
||||
|
||||
private static Template getTemplate(String template) {
|
||||
return ENGINE.getTemplate(template, CharsetUtil.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染文本
|
||||
* @param str
|
||||
* @param str 模板字符串
|
||||
* @param dataModel 数据
|
||||
* @return
|
||||
* @return 渲染结果
|
||||
*/
|
||||
public static String renderStr(String str, Map<String, Object> dataModel) {
|
||||
// 设置velocity资源加载器
|
||||
Velocity.init();
|
||||
checkTemplateSecurity(str);
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
VelocityContext context = new VelocityContext(dataModel);
|
||||
// 函数库
|
||||
context.put("math", new MathTool());
|
||||
context.put("dateTool", new DateTool());
|
||||
context.put("dict", new DictTool());
|
||||
context.put("str", new NamingCaseTool());
|
||||
Velocity.evaluate(context, stringWriter, "renderStr", str);
|
||||
VelocityContext context = buildContext(dataModel);
|
||||
ENGINE.evaluate(context, stringWriter, "renderStr", str);
|
||||
return stringWriter.toString();
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user