Java代码审计-模板注入漏洞
一、模板引擎
在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
相关文章:
Java代码审计-模板注入漏洞
一、模板引擎 在Java开发当中,为了将前端和后端进行分离,降低项目代码的耦合性,使代码更加易于维护和管理。除去以上的原因,模板引擎还能实现动态和静态数据的分离。 二、主流模板引擎 在Java中,主流的模板引擎有:Fre…...
如何在Linux中使用Cron定时执行SQL任务
文章目录 前言一、方案分析二、使用步骤1.准备脚本2.crontab脚本执行 踩坑 前言 演示数据需要每天更新监控数据,不想手动执行,想到以下解决方案 navicat 创建定时任务java服务定时执行linux crontab 定时执行sql脚本 一、方案分析 我选择了第三个方案…...
数据集划分
1、 sklearn玩具数据集介绍 数据量小,数据在sklearn库的本地,只要安装了sklearn,不用上网就可以获取 2 sklearn现实世界数据集介绍 数据量大,数据只能通过网络获取(科学上网) 3 sklearn加载玩具数据集 示…...
带你读懂什么是AI Agent智能体
一、智能体的定义与特性 定义:智能体是一个使用大语言模型(LLM)来决定应用程序控制流的系统。然而,智能体的定义并不唯一,不同人有不同的看法。Langchain的创始人Harrison Chase从技术角度给出了定义,但更…...
react动态路由
在React应用中,动态路由(Dynamic Routing)通常指的是根据应用的状态或用户的交互来动态地显示或隐藏路由(页面或组件)。这可以通过多种方法实现,包括使用React Router库,它提供了强大的路由管理…...
Linux基础(十四)——BASH
BASH 1.BASH定义2.shell的种类3.bash的功能3.1 命令记录功能3.2 命令补全功能3.3 命令别名设置3.4 工作控制、 前景背景控制3.5 程序化脚本: ( shell scripts)3.6 万用字符 4.bash的内置命令5.shell的变量功能5.1 变量的取用5.2 新建变量5.3 …...
架构师备考-概念背诵(系统架构)
软件架构概念 一个程序和计算系统软件体系结构是指系统的一个或者多个结构。结构中包括软件的构件,构件的外部可见属性以及它们之间的相互关系。体系结构并非可运行软件。确切地说,它是一种表达,使软件工程师能够: (1)分析设计在满足所规定的需求方面的有效性:(2)在设计变…...
如何让ffmpeg运行时从当前目录加载库,而不是从/lib64
程序在linux下运行时,一般从 /lib64 目录下加载依赖的库文件,如xxx.so. 有时候,系统里没有这些库,也不想从系统目录下加载,怎么办呢? 看下面的调整过程。 使用的源代码是 ffmpeg-6.1.tar.xz 解压后&…...
Kafka-Controller选举
一、上下文 《Kafka-broker粗粒度启动流程》博客中我们分析了broker的大致启动流程,这个时候每个broker都不是controller角色,下面我们就来看下它是如何选举出来的吧 二、设置ZooKeeper ZooKeeper是一个开源的分布式协调服务,主要用于分…...
必知的 Vue3 组件传值技巧:解锁组件交互新姿势
父传子defineProps 基本概念 在 Vue 3 中,父传子是一种组件间通信的方式,用于将父组件的数据传递给子组件。这种通信方式可以让组件之间更好地协作,实现功能的复用和模块的划分。 实现步骤 在父组件中传递数据 App.vue <template>…...
【论文阅读】医学SAM适配器:适应医学图像分割的任意分割模型
【论文阅读】医学SAM适配器:适应医学图像分割的任意分割模型 文章目录 【论文阅读】医学SAM适配器:适应医学图像分割的任意分割模型一、介绍二、联系工作三、方法四、实验 Medical SAM Adapter: Adapting Segment Anything Model for Medical Image Segm…...
创新体验触手可及 紫光展锐携手影目科技推出AI眼镜开放平台
近日,紫光展锐携手影目科技共同发布了搭载展锐W517芯片的影目X系列AI眼镜开放平台。这一产品的推出标志着双方在智能穿戴领域的深度协作,将紫光展锐的领先芯片技术与影目的产品创新相融合,合力打造全球智能眼镜市场的标杆产品。这一战略布局不…...
115页PDF | 埃森哲_XX集团信息化能力成熟度评估及能力提升方案(限免下载)
一、前言 这份报告是埃森哲_XX集团信息化能力成熟度评估及能力提升方案,报告首先分析了集团的战略规划,包括调整优化期、转型升级期和跨越发展期的目标,然后识别了集团面临的内部挑战和外部压力,如管控体系不完善、业务板块多样化…...
NumPy,科学计算领域中的Python明星库!
NumPy,科学计算领域中的Python明星库! 嘿,大家好呀,今天我们要来聊聊在科学计算领域里大放异彩的 NumPy 库。NumPy 是 Python 中的一个开源库,它提供了大量的数学函数,能够高效地处理大型数组与矩阵运算。…...
Hadoop生态圈框架部署(六)- HBase完全分布式部署
文章目录 前言一、Hbase完全分布式部署(手动部署)1. 下载Hbase2. 上传安装包3. 解压HBase安装包4. 配置HBase配置文件4.1 修改hbase-env.sh配置文件4.2 修改hbase-site.xml配置文件4.3 修改regionservers配置文件4.4 删除hbase中slf4j-reload4j-1.7.33.j…...
python怎么解决中文注释
最近开发学习Python,当加入中文注释时,运行程序报错: File "red.py", line 10 SyntaxError: Non-ASCII character \xe5 in file red.py on line 10, but no encoding declared; see http://www.python.org/peps/pep-0263.html fo…...
【Unity】Game Framework框架学习使用
前言 之前用过一段时间的Game Framework框架,后来有那么一段时间都做定制小软件,框架就没再怎么使用了。 现在要做大型项目了,感觉还是用框架好一些。于是又把Game Framework拾起来了。 这篇文章主要是讲Game Framework这个框架是怎么用的…...
Linux(CentOS 7) yum一键安装mysql8
1、通过yum安装 (1)下载mysql 在Linux找个地方输入以下命令 wget https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm (2)安装mysql yum 仓库配置文件 [rootVM-8-15-centos ~]# sudo rpm -Uvh mysql80-c…...
Kafka 快速入门(一)
1.1安装部署 1.1.1 集群规划 bigdata01bigdata02bigdata03zookeeperzookeeperzookeeperkafkakafkakafka 1.1.2 集群部署 官方下载地址:http://kafka.apache.org/downloads.html 检查三台虚拟机的zk是否启动:zkServer.sh start 默认启动方式 1)解压…...
丹摩征文活动 | SD3+ComfyUI的图像部署实践
一、前言 作为Stability AI 推出的一款革命性的文本转图像开源模型,Stable Diffusion 3(简称SD3)在图像质量、文本内容生成、理解复杂指令以及资源利用效率方面,都有着不俗的表现。 SD3的Medium版本,拥有20亿参数&am…...
ES6从入门到精通:前言
ES6简介 ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多新特性,包括语法糖、新数据类型、模块化支持等,显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...
HTML前端开发:JavaScript 常用事件详解
作为前端开发的核心,JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例: 1. onclick - 点击事件 当元素被单击时触发(左键点击) button.onclick function() {alert("按钮被点击了!&…...
初学 pytest 记录
安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...
R 语言科研绘图第 55 期 --- 网络图-聚类
在发表科研论文的过程中,科研绘图是必不可少的,一张好看的图形会是文章很大的加分项。 为了便于使用,本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中,获取方式: R 语言科研绘图模板 --- sciRplothttps://mp.…...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...
安卓基础(Java 和 Gradle 版本)
1. 设置项目的 JDK 版本 方法1:通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分,设置 Gradle JDK 方法2:通过 Settings File → Settings... (或 CtrlAltS)…...
使用SSE解决获取状态不一致问题
使用SSE解决获取状态不一致问题 1. 问题描述2. SSE介绍2.1 SSE 的工作原理2.2 SSE 的事件格式规范2.3 SSE与其他技术对比2.4 SSE 的优缺点 3. 实战代码 1. 问题描述 目前做的一个功能是上传多个文件,这个上传文件是整体功能的一部分,文件在上传的过程中…...
Pydantic + Function Calling的结合
1、Pydantic Pydantic 是一个 Python 库,用于数据验证和设置管理,通过 Python 类型注解强制执行数据类型。它广泛用于 API 开发(如 FastAPI)、配置管理和数据解析,核心功能包括: 数据验证:通过…...
