什么是服务端模板注入漏洞(SSTI)

文章目录

  • [1. SSTI概述](#1. SSTI概述)
  • [2. 动态网页](#2. 动态网页)
  • [3. 模板引擎](#3. 模板引擎)
  • [4. 服务端模板注入漏洞](#4. 服务端模板注入漏洞)
    • [4.1 模板拼接](#4.1 模板拼接)
    • [4.2 表达式注入](#4.2 表达式注入)
  • [5. 典型漏洞代码模式](#5. 典型漏洞代码模式)
  • 参考

1. SSTI概述

SSTI(Server-Side Template Injection,服务器端模板注入) 是一种利用Web应用程序中模板引擎的安全漏洞,通过注入恶意代码来执行服务器端命令的攻击方式。

好,如果没有学过Web开发,不知道什么是MVC模型,上面这段话理解起来会很抽象。但有一点我们是可以明确的,只要是Web漏洞,都是前端/客户端提交请求数据包,通过HTTP协议发送,后端/服务端接收请求包,处理完后生成响应数据包返回。既然叫"服务端模板注入",顾名思义就是前端提交的数据注入到了后端的模板中,可后端不是一些PHP、Java、Python等语言程序文件吗?

要回答什么是模板,前端数据怎么注入到模板,注入模板怎么产生漏洞,那就需要看看后端是怎么动态生成HTML文件的,下面,我们就要尝试在有限的篇幅中描述清楚这一过程及其中的一些概念。

2. 动态网页

一个网页就是一个HTML文件,浏览器负责解析这个文件并展示。动态网页到底动态在哪?我们所有用户都可以登录CSDN,登录成功后返回给用户的HTML页面几乎是一致的,只是用户名、头像等略有差别。服务端不可能为每一个用户事先准备一个静态的HTML,分别为每一个用户返回,这样服务器存储会爆炸。

很明显,我们在CSDN登录成功后返回的HTML文件的骨架是固定的,只是在头像等区域换成各自用户的数据。后端就是根据这个固定的HTML骨架,并在相应位置插入用户的数据,形成最终返回给各自用户的HTML文件。

说起来很简单,要实现这个过程很繁琐。HTML文件本质就是一行行的字符串/文本,古早时候后端程序也确实是通过字符串拼接来动态生成HTML文件,这是一个怎样的过程呢?我们以Java为例子展示一下查询数据库获取数据后并生成HTML文件的过程,

java 复制代码
public class DeptListServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // 获取应用的根路径
        String contextPath = request.getContextPath();

        // 设置响应的内容类型以及字符集。防止中文乱码问题。
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();

        out.print("<!DOCTYPE html>");
        out.print("<html>");
        out.print("	<head>");
        out.print("		<meta charset='utf-8'>");
        out.print("		<title>部门列表页面</title>");

        out.print("<script type='text/javascript'>");
        out.print("    function del(dno){");
        out.print("        if(window.confirm('亲,删了不可恢复哦!')){");
        out.print("            document.location.href = '"+contextPath+"/dept/delete?deptno=' + dno");
        out.print("        }");
        out.print("    }");
        out.print("</script>");

        out.print("	</head>");
        out.print("	<body>");
        out.print("		<h1 align='center'>部门列表</h1>");
        out.print("		<hr >");
        out.print("		<table border='1px' align='center' width='50%'>");
        out.print("			<tr>");
        out.print("				<th>序号</th>");
        out.print("				<th>部门编号</th>");
        out.print("				<th>部门名称</th>");
        out.print("				<th>操作</th>");
        out.print("			</tr>");
        /*========================上面一部分是固定不变的========================*/

        // 连接数据库,查询所有的部门
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            // 获取连接
            conn = DBUtil.getConnection();
            // 获取预编译的数据库操作对象
            String sql = "select deptno as a,dname,loc from dept";
            ps = conn.prepareStatement(sql);
            // 执行SQL语句
            rs = ps.executeQuery();
            // 处理结果集
            int i = 0;
            while(rs.next()){
                String deptno = rs.getString("a");
                String dname = rs.getString("dname");
                String loc = rs.getString("loc");

                out.print("			<tr>");
                out.print("				<td>"+(++i)+"</td>");
                out.print("				<td>"+deptno+"</td>");
                out.print("				<td>"+dname+"</td>");
                out.print("				<td>");
                out.print("					<a href='javascript:void(0)' onclick='del("+deptno+")'>删除</a>");
                out.print("					<a href='"+contextPath+"/dept/edit?deptno="+deptno+"'>修改</a>");
                out.print("					<a href='"+contextPath+"/dept/detail?fdsafdsas="+deptno+"'>详情</a>");
                out.print("				</td>");
                out.print("			</tr>");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            DBUtil.close(conn, ps, rs);
        }

        /*=====================下面一部分是固定不变的=========================*/
        out.print("		</table>");
        out.print("		<hr >");
        out.print("		<a href='"+contextPath+"/add.html'>新增部门</a>");
        out.print("	</body>");
        out.print("</html>");
    }
}

在上面这个查看部门列表的代码里,部门的数量关乎列表的行数,部门数量是通过数据库动态查询出来的,也就是说HTML文件里的表格里地数据和表格行数需要动态地生成。那么我们代码里就需要取数据+拼接HTML格式地标签字符串,程序的逻辑和视图混合在一起,不利于前后端的分工,也使得程序的开发很繁琐。

自然而然,人们的解决办法就是:提供一个视图模板文件(HTML文件),里面有关于数据渲染的占位符,提供一个包含占位符信息的数据对象,有一个软件库,可以把数据直接渲染进模板,这个软件就是模板引擎。

3. 模板引擎

我们来实现一个功能极其单一分模板引擎,帮助我理解"什么是模板"、"什么是数据"、"什么是模板引擎"。

javascript 复制代码
<script>
var templateStr = '<h1>我在用{{lang}},实现一个简易的{{engine}}';
var data = {lang:'js', engine:'模板引擎'};
function render(templateStr, data){
	return templateStr.replace(/\{\{(\w+)\}\}/g, function(findStr, $1){
		return data[$1];
	});
}
var result = render(templateStr, data);
console.log(result)
</script>

在浏览器控制台中运行,发现成功"将数据渲染到了模板"。

4. 服务端模板注入漏洞

4.1 模板拼接

传统的MVC框架开发,生成HTML文件这一步是在后端完成的。现代前后端分离结构,可以简单理解为将MVC中的"C(控制)"和"V(视图)"放到了前端。

后端的模板引擎提供的功能是将数据填充进模板占位符,模板中的占位符并不单单是一个静态标记,而是一套有严格形式的指令。这些模板指令定义了该从哪里取数据,如何渲染数据等。模板引擎解析模板时,遇到对应的指令会执行对应的操作。服务器端模板注入(SSTI)的根源不在于模板引擎本身,而在于错误的代码编写模式。

模板语法 vs 模板内容

复制代码
安全的场景(数据层):
─────────────────────
模板:  "Hello, ${name}"
数据:  name = "<script>alert(1)</script>"
结果:  Hello, <script>alert(1)</script>
        ↑ name 的值被当作"数据"输出,不会被二次解析为模板语法


危险的场景(代码层):
─────────────────────
模板字符串:  "Hello, " + userInput   ← userInput 被拼进了模板本身
userInput = "${7*7}"
模板引擎解析的字符串:  "Hello, ${7*7}"
结果:  Hello, 49
        ↑ 用户输入变成了"模板代码",被执行了

漏洞模式:将用户输入直接拼接进模板

4.2 表达式注入

SSTI广义上包括任何模板指令注入,因为模板引擎提供的动态能力主要是通过表达式语言实现,即表达式注入是其最常见形式,攻击者通过注入表达式来突破数据边界执行代码。JSP的EL注入就是向JSP模板注入EL表达式,Spring的SpEL注入在模板场景下也是向模板注入SpEL表达式。因此,对于这类漏洞,核心都是表达式注入,表达式的执行权限决定了漏洞的危害程度。

5. 典型漏洞代码模式

CVE-2026-40478:Thymeleaf 模板注入

参考

1 Vue源码解析之mustache模板引擎

2 JavaWeb-JSP原理深度解析

3 《TOMCAT与JAVA WEB 开发技术详解 第3版》

4 deepseek

5 mimo

相关推荐
零零信安3 天前
零零信安荣登数世咨询《新质·数字安全专精百强(2026)》暗网情报领域,彰显专业实力与创新引领
安全·网络安全·数据泄露·暗网·零零信安
憧憬成为web高手3 天前
l33t-hoster
学习·web安全·网络安全
HackTwoHub3 天前
Sqli-Scanner SQL注入SKILL自动化挖掘SQL注入,零依赖自动化SQL注入挖掘,赏金猎人
数据库·人工智能·sql·web安全·网络安全·自动化·系统安全
爱网络爱Linux3 天前
网络安全与渗透测试实用工具大全
web安全·网络安全·信息安全·cisp-pte·cisp·cissp
xsc-xyc3 天前
用 Tailscale + Syncthing 实现手机、电脑与 NAS 的跨网络文件同步
linux·网络·网络安全·智能手机·电脑
持敬chijing3 天前
Web渗透之SQL注入-常用sql语句
sql·安全·web安全·网络安全
Chengbei113 天前
AISec真正拟人化全自动渗透工具!支持浏览器交互全自动化挖掘,SQL注入、XSS、越权等。
sql·安全·web安全·网络安全·自动化·系统安全·xss
X7x53 天前
深度拆解网络安全“闭环”之王——APPDRR模型
网络安全·网络攻击模型·安全威胁分析·安全架构·appdrr模型
Inhand陈工3 天前
污水泵站PLC数据上云实战:西门子PLC + 映翰通IG502 + DM平台全流程
人工智能·物联网·网络安全·阿里云·信息与通信·iot
X7x53 天前
一文讲透PADIMEE模型
网络安全·网络攻击模型·安全威胁分析·安全架构·padimee模型