Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >审计Tomcat PUT方法任意文件写入(CVE-2017-12615)

审计Tomcat PUT方法任意文件写入(CVE-2017-12615)

原创
作者头像
Gh0st1nTheShel
修改于 2022-01-23 06:32:11
修改于 2022-01-23 06:32:11
1.2K0
举报
文章被收录于专栏:网络空间安全网络空间安全

欢迎关注我的微信公众号《壳中之魂》,查看更多网安文章

漏洞复现

产生原因

漏洞产生原因为web.xml里将readonly设置为了false(默认为true),导致了可以通过PUT写入任意文件

利用条件

经过实际测试,Tomcat 7.x 版本内 web.xml 配置文件内默认配置无 readonly 参数,需要手工添加,默认配置条件下不受此漏洞影响

影响范围

  • Apache Tomcat 7.0.0 - 7.0.79

复现

搭建环境:Vulhub - Docker-Compose file for vulnerability environment

搭建好环境后打开页面localhost:8080,然后使用burpsuite抓包

通过修改为PUT方法,可以直接写入文件

传入的URI必须的是/x.jsp/的格式,而不能是/x.jsp的格式

传入/x.jsp的会报404状态码

同时文件是没有被写入的

通用的绕过方法是使用/结尾,无论是linux或者是windows都可以绕过,如果是windows下还可以以::$DATA、%20空格等结尾

使用/结尾,响应码为201,说明成功写入,响应码如果为204也表示成功写入,但是说明原来存在相同文件名的文件,覆盖写入

此时访问/test.jsp

代码审计

通过审计web.xml可以发现,Tomcat在处理请求时有两个默认的Servlet,一个是DefaultServelt,另一个是JspServlet。两个Servlet被配置在 Tomcat的web.xml中

DefaultServelt

代码语言:txt
AI代码解释
复制
<servlet>
    <servlet-name>default</servlet-name>
    <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
    <init-param>
        <param-name>debug</param-name>
        <param-value>0</param-value>
    </init-param>
    <init-param>
        <param-name>listings</param-name>
        <param-value>false</param-value>
    </init-param>
    <init-param>
        <param-name>readonly</param-name>
        <param-value>false</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

JspServlet

代码语言:txt
AI代码解释
复制
<servlet>
    <servlet-name>jsp</servlet-name>
    <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
    <init-param>
        <param-name>fork</param-name>
        <param-value>false</param-value>
    </init-param>
    <init-param>
        <param-name>xpoweredBy</param-name>
        <param-value>false</param-value>
    </init-param>
    <load-on-startup>3</load-on-startup>
</servlet>

Mapping

代码语言:txt
AI代码解释
复制
<!-- The mapping for the default servlet -->
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

<!-- The mappings for the JSP servlet -->
<servlet-mapping>
    <servlet-name>jsp</servlet-name>
    <url-pattern>*.jsp</url-pattern>
    <url-pattern>*.jspx</url-pattern>
</servlet-mapping>

从中可以看出,除了.jsp和.jspx文件由JSPServlet处理,其他都由DefaultServelet处理(包括PUT和DELETE方法),根据刚才我们的漏洞复现发现,只有URI为/x.jsp/的格式才可以写入文件,如果为/x.jsp是不行的,这是因为为/x.jsp/时是由DefaultServelet处理,而传入/x.jsp是由JSPServlet处理,所以无法触发漏洞

可以发现,即使即使readonly设置为false,tomcat也是不允许直接通过PUT方法上传jsp和jspx文件的,这是由于jsp和jspx文件是由org.apache.jasper.servlet.JspServlet来处理,但是org.apache.jasper.servlet.JspServlet并不能处理PUT方法,所以要通过绕过来达到上传的目的

下面只对/绕过方法进行审计,针对%20、::$DATA绕过涉及到了windows的特性过于复杂,可以查看参考文章

每一个Servlet的实现都要继承一个HttpServlet,在HttpServlet中有一个doPut方法来处理PUT方法,DefaulatServlet重写了该方法

代码语言:txt
AI代码解释
复制
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    if (this.readOnly) {
        resp.sendError(403);
    } else {
        String path = this.getRelativePath(req);
        WebResource resource = this.resources.getResource(path);
        DefaultServlet.Range range = this.parseContentRange(req, resp);
        Object resourceInputStream = null;

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

            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) {
                }
            }

        }

    }
}

使用idea进行远程调试,在这次配置远程调试的时候遇到了一些困难,感谢热心的群友和p牛

特别感谢Litch1大佬的1对1帮助

p牛的肯定

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

代码语言:txt
AI代码解释
复制
version: '2'
services:
 tomcat:
   build: .
   ports:
    - "8080:8080"
    - "5005:5005"

然后开启环境,环境开启后进入修改tomcat/bin下的catalinna.sh文件,添加一句

代码语言:txt
AI代码解释
复制
JAVA_OPTS="$JAVA_OPTS -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005" 

5005为设置远程调试的端口

然后重启tomcat服务

然后配置idea,添加一个远程JVM调试环境,设置的端口为刚才5005端口

然后开始进行debug,如果出现

说明远程调试连接成功

通过上面的漏洞复现可以发现,成功写入任意文件的状态码为201和204,迅速定位到附近

代码语言:txt
AI代码解释
复制
if (this.resources.write(path, (InputStream)resourceInputStream, true)) {
    if (resource.exists()) {
        resp.setStatus(204);
    } else {
        resp.setStatus(201);
    }
} else {
resp.sendError(409);
}

观察第一个if判断,resources.write方法传入的path,联想到漏洞的/x.jsp状态码404和/x.jsp/状态码201或者204的区别,很有可能是此处的path出现了问题,通过调试查看传入的参数

代码语言:txt
AI代码解释
复制
PUT /test.jsp/ HTTP/1.1
Host: 192.168.3.35:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-Length: 32

<%out.println("Hello World!");%>

步入resources.write方法

代码语言:txt
AI代码解释
复制
public boolean write(String path, InputStream is, boolean overwrite) {
    path = this.validate(path);
    if (!overwrite && this.preResourceExists(path)) {
        return false;
    } else {
        boolean writeResult = this.main.write(path, is, overwrite);
        if (writeResult && this.isCachingAllowed()) {
            this.cache.removeCacheEntry(path);
        }
        return writeResult;
    }
}

继续步入main.write方法

完整代码:

代码语言:txt
AI代码解释
复制
public boolean write(String path, InputStream is, boolean overwrite) {
    this.checkPath(path);
    if (is == null) {
        throw new NullPointerException(sm.getString("dirResourceSet.writeNpe"));
    } else if (this.isReadOnly()) {
        return false;
    } else {
        File dest = null;
        String webAppMount = this.getWebAppMount();
        if (path.startsWith(webAppMount)) {
            dest = this.file(path.substring(webAppMount.length()), false);
            if (dest == null) {
                return false;
            } else if (dest.exists() && !overwrite) {
                return false;
            } else {
                try {
                    if (overwrite) {
                        Files.copy(is, dest.toPath(), new CopyOption[]{StandardCopyOption.REPLACE_EXISTING});
                    } else {
                        Files.copy(is, dest.toPath(), new CopyOption[0]);
                    }
                    return true;
                } catch (IOException var7) {
                    return false;
                }
            }
        } else {
            return false;
        }
    }
}

虽然我不能步入copy,但是通过传入的数据is可以发现里面包含了http头(转为字符串后),同时dest为传入的路径,猜测copy即为写入文件的方法,同时可以发现,路径传入的文件结尾的/已经被去除

审计file方法

完整代码

代码语言:txt
AI代码解释
复制
protected final File file(String name, boolean mustExist) {
    if (name.equals("/")) {
        name = "";
    }

    File file = new File(this.fileBase, name);
    if (mustExist && !file.canRead()) {
        return null;
    } else if (this.getRoot().getAllowLinking()) {
        return file;
    } else {
        String canPath = null;

        try {
            canPath = file.getCanonicalPath();
        } catch (IOException var7) {
        }

        if (canPath == null) {
            return null;
        } else if (!canPath.startsWith(this.canonicalBase)) {
            return null;
        } else {
            String fileAbsPath = file.getAbsolutePath();
            if (fileAbsPath.endsWith(".")) {
                fileAbsPath = fileAbsPath + '/';
            }

            String absPath = this.normalize(fileAbsPath);
            if (this.absoluteBase.length() < absPath.length() && this.canonicalBase.length() < canPath.length()) {
                absPath = absPath.substring(this.absoluteBase.length() + 1);
                if (absPath.equals("")) {
                    absPath = "/";
                }

                canPath = canPath.substring(this.canonicalBase.length() + 1);
                if (canPath.equals("")) {
                    canPath = "/";
                }

                if (!canPath.equals(absPath)) {
                    return null;
                }
            }

            return file;
        }
    }
}

重点代码

代码语言:txt
AI代码解释
复制
protected final File file(String name, boolean mustExist) {
    ...
    File file = new File(this.fileBase, name);
    ...
}

首先File file = new File(this.fileBase, name);实例化了一个file对象,其中name的值为/test.jsp/,fileBase的值为/usr/local/tomcat/webapps/ROOT,可以发现为Tomcat的绝对路径,最后得到的file值为/usr/local/tomcat/webapps/ROOT/test.jsp,由此可以发现,经过实例化后name结尾的/已经去除,由于无法直接步入IO库所以在jdk1.8文件下的src.zip找到IO库的源码,将其复制出来进行审计

根据传入的参数的类型(File, String)找到构造方法

完整代码

代码语言:txt
AI代码解释
复制
public File(File parent, String child) {
    if (child == null) {
        throw new NullPointerException();
    }
    if (parent != null) {
        if (parent.path.equals("")) {
            this.path = fs.resolve(fs.getDefaultParent(),
                                   fs.normalize(child));
        } else {
            this.path = fs.resolve(parent.path,
                                   fs.normalize(child));
        }
    } else {
        this.path = fs.normalize(child);
    }
    this.prefixLength = fs.prefixLength(this.path);
}

重点代码

代码语言:txt
AI代码解释
复制
public File(File parent, String child) {
    if (child == null) {
        ...
    }
    if (parent != null) {
        if (parent.path.equals("")) {
            this.path = fs.resolve(fs.getDefaultParent(),
                                   fs.normalize(child));
        } else {
            ...
        }
    } else {
        ...
    }
    ...
}

继续步入normalize方法

代码语言:txt
AI代码解释
复制
public String normalize(String path) {
    int n = path.length();
    char slash = this.slash;
    char altSlash = this.altSlash;
    char prev = 0;
    for (int i = 0; i < n; i++) {
        char c = path.charAt(i);
        if (c == altSlash)
            return normalize(path, n, (prev == slash) ? i - 1 : i);
        if ((c == slash) && (prev == slash) && (i > 1))
            return normalize(path, n, i - 1);
        if ((c == ':') && (i > 1))
            return normalize(path, n, 0);
        prev = c;
    }
    if (prev == slash) return normalize(path, n, n - 1);
    return path;
}

而在

代码语言:txt
AI代码解释
复制
if (c == altSlash)
    return normalize(path, n, (prev == slash) ? i - 1 : i); 

这句代码中会将结尾的/去掉,从而绕过过滤

参考文章:CVE-2017-12615/CVE-2017-12616:Tomcat信息泄漏和远程代码执行漏洞分析报告 (seebug.org)

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
代码审计 | Tomcat 任意文件写入 CVE-2017-12615
直接使用 PUT 发起请求就可以上传任意文件,比如向 /teamssix.jsp/ 发起请求
TeamsSix
2022/09/20
4150
代码审计 | Tomcat 任意文件写入 CVE-2017-12615
【Tomcat优化篇】如何让你的Tomcat性能更加优越
  我们可以打开Tomcat的管理页面,这块需要先配置下,在 tomcat-users.xml中添加相关的用户和角色信息
用户4919348
2022/09/02
1.8K0
【Tomcat优化篇】如何让你的Tomcat性能更加优越
tomcat编译超过64k大小的jsp文件报错原因
今天遇到一个问题,首先是在tomcat中间件上跑的web项目,一个jsp文件,因为代码行数实在是太多了,更新了几个版本之后编译报错了,页面打开都是报500的错误,500的报错,知道http协议返回码的都知道,这是服务端的报错。
SmileNicky
2019/01/17
1.3K0
Tomcat常见的漏洞总结
该漏洞是由于Tomcat AJP协议存在缺陷而导致,攻击者利用该漏洞可通过构造特定参数,读取服务器webapp下的任意文件,如:webapp配置文件或源代码等。若目标服务器同时存在文件上传功能,攻击者可进一步实现远程代码执行。
黑白天安全
2021/04/07
10.2K0
Tomcat常见的漏洞总结
我成功攻击了Tomcat服务器之后
Tomcat是一个开源的轻量级Web应用服务器,在我们平常工作过程中接触得非常多。代码也非常经典,很多人为了提升自己的技术也会去阅读学习Tomcat的源码。但正如著名诗人李白所说的:世界上本没有漏洞,使用的人多了,也就发现了漏洞。比如今年的2月份就爆出了存在文件包含漏洞。今天我们选择两个比较直观的Tomcat漏洞去模拟整个漏洞被攻击的过程,以及漏洞为什么会产生,Tomcat大神们又是如何应对的。
牧码哥
2020/03/25
1.2K0
Tomcat 6 --- 使用Jasper引擎解析JSP
熟悉JAVA web开发的朋友都知道JSP会被转换成java文件(预编译),然后编译成class使用,即按照JSP-->java-->class的过程进行编译。 由于JVM只认识class文件,它不知道什么是JSP,因此在tomcat中 如何把JSP解析成java文件 就是本文所要描述的问题。 其他翻译内容参考:Tomcat官方文档翻译 如有错误,请予指正。 什么是Jasper   Jasper是tomcat中使用的JSP引擎,在Tomcat 6中使用的是Jasper 2,相对于原来的版
用户1154259
2018/01/17
2.1K0
Tomcat 6 --- 使用Jasper引擎解析JSP
【详解】TomcatSSI的配置
Server Side Includes (SSI) 是一种服务器端的脚本语言,主要用于在网页中动态插入内容。它允许开发者在HTML页面中嵌入简单的命令,以实现如包含文件、设置变量、执行CGI脚本等操作。Apache Tomcat 作为一个流行的Java应用服务器,虽然主要服务于JSP和Servlet,但也支持SSI功能。本文将详细介绍如何在Tomcat中启用和配置SSI。
大盘鸡拌面
2025/05/15
870
Tomcat与Servlet进行交互
3、Servlet容器创建一个HttpRequest对象,将客户请求的信息封装到这个对象中
Java架构师历程
2018/09/26
2.4K0
Tomcat与Servlet进行交互
英诗派精致版配置参数_自动舒适版是什么配置
运行ckfinder文件夹下–>_samples–>standalone.html文件,出现下面界面。
全栈程序员站长
2022/11/02
3240
英诗派精致版配置参数_自动舒适版是什么配置
Tomcat相关漏洞复现
管理后台链接:http://192.168.0.105:8080/manager/html
Gh0st1nTheShel
2022/03/15
7550
web.xml文件的作用及基本配置
Java的web工程中的web.xml文件有什么作用呢?它是每个web工程都必须的吗?
bear_fish
2018/09/19
1.6K0
第71节:Java中HTTP和Servlet
什么是协议,就是规则,规范,用于双方在交互,通讯的时候遵循的一种规范,规则.而http协议是对网络上的客户端和服务端在执行http请求的时候遵循的一种规范,其实就是规定了客户端在访问服务器端的时候,要带上一些东西,服务端返回数据的时候,也要带点东西,礼尚往来嘛!!!
达达前端
2019/07/03
5620
第71节:Java中HTTP和Servlet
Web.xml配置说明
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/149544.html原文链接:https://javaforall.cn
全栈程序员站长
2022/07/05
7260
Web---演示servlet技术(servlet生命周期),解决中文乱码问题
本节讲解决中文乱码问题的4种方法。 还有更好的方法,也就是用过滤器,这里就不演示了,博主目前也不会~呼♪(^∇^*)~过段时间才会学。
谙忆
2021/01/21
5070
Web---演示servlet技术(servlet生命周期),解决中文乱码问题
CVE-2024-50379:Apache Tomcat远程代码执行漏洞
Apache Tomcat是一个开源的Java Servlet容器,广泛用于运行Java Web应用程序。它实现了Java Servlet和JavaServer Pages (JSP) 技术,提供了一个运行环境来处理HTTP请求、生成动态网页,并支持WebSocket通信。
Timeline Sec
2025/02/18
9390
CVE-2024-50379:Apache Tomcat远程代码执行漏洞
web.xml配置  关于web.xml配置中的<url-pattern>
  在${CATALINA_HOME}\conf\web.xml中的内容,相当于写到了每个项目的web.xml中,它是所有web.xml的父文件。
eadela
2019/09/29
1.5K0
python项目练习六:使用CGI进行远程编辑
记得一开始接触web开发的时候,看视频,视频里面的老师一般都会语重心长的说:想当年我们一开始学习编程那会儿,都是用cgi编程,复杂的很,现在你们学习web编程,直接有现成的框架来用,十分简单。记得当然听完这句话之后就会觉得这个老师好有经验,技术很高。
the5fire
2019/02/28
7320
Spring MVC 上下文(ApplicationContext)初始化入口
应该来说是很少使用这种方法用于生产开发,常常在学习Spring做demo的时候会使用到。更有可能出现在Spring项目的代码测试,不过呢,单元测试的框架(比如 JUnit)已经提供了简单的方式,也就不建议直接实例化上下文。因为实例化一个上下文还得要做维护,再者现在常用的是基于Web的开发,也就是常用 Spring MVC。如果没有基于 Web 应用的开发,那么很可能就是一个小的程序,类似于提供给第三方使用的 SDK 代码,那么使用 Spring 感觉会太重,最重要是自己要维护一个 ApplicationContext,感觉不是那么方便
用户3148308
2018/09/13
2K0
Tomcat目录文件列表功能和定制化
先说一下背景。 某天,产品小伙伴过来提了一个需求:能不能把公司的需求文档以列表的方式展示出来,当开发者需要哪个的时候,自己在目录中寻找并点击进入(需要哪个点哪个,so easy),也就不用记录那么多文档url了。 另外说明一下,公司的需求文档是以文件夹和html组织形式部署在tomcat的,版本8,这是前提。 听完需求,格子的脑袋就开始运转起来,这不是分分钟能搞定的事吗,袖子撸起来,说做咱做。
格子Lin
2018/08/27
1.8K0
Tomcat目录文件列表功能和定制化
WebXml文件与SpringMVC的联系
无论采用何种框架来进行Java Web的开发,只要是Web项目必须在WEB-INF下有web.xml,这是java规范。 当然,我们最早接触到Java Web容器通常是tomcat,但这并不意味着web.xml是属于Tomcat的,同样,Servlet本身也不属于Tomcat,它与JSP等是Java Web的基础规范。而Servlet的运行需要有Servlet容器的支持,常见的容器有Tomcat、Jetty、JBoss等。
w4ngzhen
2023/10/16
3610
WebXml文件与SpringMVC的联系
相关推荐
代码审计 | Tomcat 任意文件写入 CVE-2017-12615
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档