Java代码审计基础(一)

标签: Web应用漏洞 java代码审计 java常见web漏洞 web安全 代码审计 | 发表时间:2015-03-04 00:35 | 作者:admin
出处:http://www.nxadmin.com

本文重点是让大家了解JAVA代码审计的基础,会以漏洞示例的方式介绍JAVA代码中常见Web漏洞的形成和针对的修复方案,文章是在国外网站上看到的,因为在接触JAVA代码审计,感觉挺高大上的文章,很适合对JAVA代码审计感兴趣的童鞋找感觉之用,就根据自己的理解翻译了过来,拿去某平台投稿,被鄙视说太基础,就只好发自己的Blog上了。

SQL注入

SQL注入攻击指的是通过构建特殊的输入作为参数传入Web应用程序,而这些输入大都是SQL语法里的一些组合,通过执行SQL语句进而执行攻击者所要的操作,其主要原因是程序没有细致地过滤用户输入的数据,致使非法数据侵入系统。

漏洞示例:

<%@page import="java.sql.*"%>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title> </title>
</head>
<body>
<% 
        String user = request.getParameter("user");
        String pass = request.getParameter("pass");
        Class.forName("com.mysql.jdbc.Driver");
        Connection con = (Connection)                                                              
        DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "root" ,"");
        Statement st= con.createStatement();
        ResultSet rs=st.executeQuery("select * from users where username='"+user+"' and password='"+pass+"' limit 0,1");
        if(rs.next())
        {
                out.println("Login success");
        }
        else
        {
                out.println("Login failed");
        }
%>

在上述代码中,开发者使用声明的类来创建一个SQL语句,并执行它来获取一个有效用户的用户名和密码。由于使用拼接SQL语句,并且没有做任何防注入的手段,导致存在SQL注入漏洞,可以绕过登录验证。

修复后的代码如下:

<%@page import="java.sql.*"%>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title></title>
</head>
<body>
<% 
    String user = request.getParameter("user");
    String pass = request.getParameter("pass");
    Class.forName("com.mysql.jdbc.Driver");
Connection con = (Connection) 
DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "root" , "");
    PreparedStatement ps=(PreparedStatement) con.prepareStatement("select * from users where username=? and password=? limit 0,1");
    ps.setString(1,user);
    ps.setString(2,pass);
    ResultSet rs=ps.executeQuery();
    if(rs.next())
    {
        out.println("Login success");
    }
    else
    {
        out.println("Login failed");
    }
%>
</body>

修复后的代码使用了PreparedStatement预编译的方式,使用这种方式无需对传入的参数进行过滤等处理,因为由于PreparedStatement内置了字符过滤,因此是能够防止SQL注入的。

数据明文存储

11年底CSDN的数据在网上泄漏,据说库中的密码都是明文存储的,因此导致非常大的影响。如果使用了复杂的加密存储,即时数据泄漏,密码也有可能不会破解成功,下面看看JAVA代码中数据明文存储的设计缺陷是如何形成的。

漏洞示例:

<%@page import="java.sql.*"%>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title></title>
</head>
<body>
<%
String user=request.getParameter("user");
String pass=request.getParameter("pass");
Class.forName("com.mysql.jdbc.Driver");
Connection con=(Connection)DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "root" ,"");
PreparedStatement ps = (PreparedStatement) con.prepareStatement("insert into users_crypt values(?,?)");
ps.setString(1,user);
ps.setString(1,pass);
int res = ps.executeUpdate();
if(res>0)
{
out.println("Register success");
}
else
{
out.println("Register failed");
}
%>
</body>

可以看到上面的代码对获取到的user和pass的没有做任何的加密处理,直接存储到数据库中,导致存在明文存储的缺陷。

修复后的代码如下:

<%@page import="java.util.Calendar"%>
<%@page import="java.text.SimpleDateFormat"%>
<%@page import="java.text.DateFormat"%>
<%@page import="java.math.BigInteger"%>
<%@page import="java.security.MessageDigest"%>
<%@page import="java.sql.*"%>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title></title>
</head>
<body>
<%
    String user=request.getParameter("user");
    String pass=request.getParameter("pass");
    DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
    Calendar cal = Calendar.getInstance();
    String reg_time = (dateFormat.format(cal.getTime())).toString();
    String original = pass+reg_time;
    String s = pass+reg_time;
    MessageDigest m = MessageDigest.getInstance("MD5");
    m.update(s.getBytes(),0,s.length());
    String calc_hash = new BigInteger(1,m.digest()).toString(16);
    if(calc_hash.length()<32)
    {
        calc_hash = "0"+calc_hash;
    }
    Class.forName("com.mysql.jdbc.Driver");
    Connection con=(Connection)DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "root" ,"");
    PreparedStatement ps = (PreparedStatement) con.prepareStatement("insert into users_crypt values(?,?,?)");
    ps.setString(1,user);
    ps.setString(2,calc_hash);
    ps.setString(3,reg_time);
    int res = ps.executeUpdate();
    if(res>0)
    {
        out.println("Register success");
    }
    else
    {
        out.println("Register failed");
    }
%>
</body>
</html>

修复后的代码将用户注册的时候输入的密码和注册时间组合加密之后保存在数据库中,这样如果数据泄漏,不阅读源代码很难将密码破解成明文形式,大大降低了泄密之后的影响。

失效的会话管理

该漏洞主要是因为Web应用程序没有正确的执行会话管理,例如用户登陆前的会话Cookie和登录后的是一样的,另外一个例子是当用户点击退出的时候,Session不会失效。

示例漏洞代码:

<%@page import="java.sql.*"%>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title> </title>
</head>
<body>
<% 
    String user = request.getParameter("user");
    String pass = request.getParameter("pass");
    Class.forName("com.mysql.jdbc.Driver");
    Connection con = (Connection) DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "root" , "");
    PreparedStatement ps=(PreparedStatement) con.prepareStatement("select * from users where username=? and password=? limit 0,1");
    ps.setString(1,user);
    ps.setString(2,pass);
    ResultSet rs=ps.executeQuery();
    if(rs.next())
    {
        session.setAttribute("useracc", rs.getString("user"));
        out.println("Login success");
    }
    else
    {
        out.println("Login failed");
    }
%>
</body>

修复后的代码:

<%@page import="java.sql.*"%>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title> </title>
</head>
<body>
<% 
    String user = request.getParameter("user");
    String pass = request.getParameter("pass");
    Class.forName("com.mysql.jdbc.Driver");
    Connection con = (Connection) DriverManager.getConnection("jdbc:mysql://localhost:3306/userdb", "root" , "");
    PreparedStatement ps=(PreparedStatement) con.prepareStatement("select * from users where username=? and password=? limit 0,1");
    ps.setString(1,user);
    ps.setString(2,pass);
    ResultSet rs=ps.executeQuery();
    if(rs.next())
    {
        session.invalidate();
        request.getSession(true);
        session.setAttribute("useracc", rs.getString("user"));
        out.println("Login success");
    }
    else
    {
        out.println("Login failed");
    }
%>
</body>

以上修复后的代码中,用户在登录的时候,首先会让之前的session失效,然后又获取新的seesion。

XSS漏洞

Xss漏洞小伙伴们应该都比较熟悉了,攻击者可以向网页中注入恶意的JS或者HTML代码,有反射XSS、存储XSS、DOM XSS三种。

漏洞示例:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>XSS Vulnerable</title>
</head>
<body>
    <form action="xss-vuln.jsp" method="post">
        Enter your name: <input type="text" name="name"><input type="submit">
    </form>
 
<%
    if(request.getMethod().equalsIgnoreCase("post"))
    {
        String name = request.getParameter("name");
        if(!name.isEmpty())
        {
            out.println("<br>Hi "+name+". How are you?");
        }
    }
%>
 
</body>
</html>

从上面漏洞代码中可以看到,对用户提交的name参数没有做任何的输入过滤和输出的编码,直接输出在HTML代码中,导致存在反射XSS漏洞。

修复后的代码:

<%@page import="org.apache.commons.lang.StringEscapeUtils"%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
Patch 
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>XSS Patched</title>
</head>
<body>
<form action="xss-patch.jsp" method="post">
Enter your name: <input type="text" name="name"><input type="submit">
</form>

<%
    if(request.getMethod().equalsIgnoreCase("post"))
    {
        String name =  
        StringEscapeUtils.escapeHtml(request.getParameter("name"));
        if(!name.isEmpty())
        {
            out.println("<br>Hi "+name+". How are you?");
        }
    }
%>
</body>

上面修复后的代码对用户提交的name参数进行了HTML的编码处理,使用了StringEscapeUtils类的escapeHtml方法,该方法会自动对特殊符号进行HTML编码处理。该类是包含在 commons-lang-2.4.jar包中的。

越权漏洞

如果一个Web应用程序不正确检查用户是否被授权访问的特定的资源,就有可能导致产生越权漏洞。例如帐号A在登录的状态下,遍历访问请求中的ID就可以查看其它人的相关信息。

漏洞示例代码:

<%@page import="java.util.Enumeration"%>
<%@ page import="java.sql.*" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Account Balance</title>
</head>
<body>
<%
    int flag = 0;
    Enumeration e = session.getAttributeNames();
    while (e.hasMoreElements())
    {
        String name = (String) e.nextElement();
        String value = session.getAttribute(name).toString();
        if(name.equals("useracc") && !(value.isEmpty()))
        {
            flag = 1;
            break;
        }
    }
    if(flag == 1)
    {
        String accno = request.getParameter("accno");
        Class.forName("com.mysql.jdbc.Driver");
        Connection con = (Connection) DriverManager.getConnection("jdbc:mysql://localhost/mydb", "root", "");
        PreparedStatement ps = (PreparedStatement) con.prepareStatement("select * from account_balance where accno=? limit 0,1");
        ps.setString(1,accno);
        ResultSet rs = ps.executeQuery();
        if(rs.next())
        {
            String s = rs.getString("balance");
            out.println("<h1>Welcome to your account</h1>");
            out.println("<br>Account Number: "+session.getAttribute("useracc"));
            out.println("<br>Your current balance is: "+s);
        }
        else
        {
            out.println("Error: Contact administrator.");
        }
    }
    else
    {
        response.sendRedirect("login.jsp");
    }
%>
</body>
</html>

在上面的代码中,没有判断用户Session,导致通过修改accno的值就可以遍历返回的结果信息。

修复后的代码:

<%@page import="java.util.Enumeration"%>
<%@ page import="java.sql.*" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Account Balance</title>
</head>
<body>
<%
    int flag = 0;
    Enumeration e = session.getAttributeNames();
    while (e.hasMoreElements())
    {
        String name = (String) e.nextElement();
        String value = session.getAttribute(name).toString();
        if(name.equals("useracc") && !(value.isEmpty()))
        {
            flag = 1;
            break;
        }
    }
     
    if(flag == 1)
    {
        String sess_accno = session.getAttribute("useracc").toString();
        String accno = request.getParameter("accno");
        if(sess_accno.equals(accno))
        {
            Class.forName("com.mysql.jdbc.Driver");
            Connection con = (Connection) DriverManager.getConnection("jdbc:mysql://localhost/mydb", "root", "");
            PreparedStatement ps = (PreparedStatement) con.prepareStatement("select * from account_balance where accno=? limit 0,1");
            ps.setString(1,accno);
            /*
            This line will be better
            ps.setString(1,sess_accno);
            */
            ResultSet rs = ps.executeQuery();
            if(rs.next())
            {
                String s = rs.getString("balance");
                out.println("<h1>Welcome to your account</h1>");
                out.println("<br>Account Number: "+session.getAttribute("useracc"));
                out.println("<br>Your current balance is: "+s);
            }
            else
            {
                out.println("Error: Contact administrator.");
            }
        }
        else
        {
            out.println("Unauthorized Access Detected");
        }
    }
    else
    {
        response.sendRedirect("login.jsp");
    }
%>
</body>
</html>

上面修复后的代码,判断了用户的Session,在为True的情况下才能够查看返回的信息,因此当用户遍历accno的值来尝试获取返回结果时,会提示无权访问。

相关 [java 代码审计 基础] 推荐:

Java代码审计基础(一)

- - 阿德马Web安全
本文重点是让大家了解JAVA代码审计的基础,会以漏洞示例的方式介绍JAVA代码中常见Web漏洞的形成和针对的修复方案,文章是在国外网站上看到的,因为在接触JAVA代码审计,感觉挺高大上的文章,很适合对JAVA代码审计感兴趣的童鞋找感觉之用,就根据自己的理解翻译了过来,拿去某平台投稿,被鄙视说太基础,就只好发自己的Blog上了.

java基础知识

- - CSDN博客互联网推荐文章
JAVA相关基础知识. 1、面向对象的特征有哪些方面. 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面. 抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节. 抽象包括两个方面,一是过程抽象,二是数据抽象. 继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法.

JAVA面试精选【Java基础】

- - CSDN博客编程语言推荐文章
  这个系列面试题主要目的是帮助你拿轻松到offer,同时还能开个好价钱. 只要能够搞明白这个系列的绝大多数题目,在面试过程中,你就能轻轻松松的把面试官给忽悠了. 对于那些正打算找工作JAVA软件开发工作的童鞋们来说,当你看到这份题目的时候,你应该感动很幸运,因为,只要你把题目中的内容都搞懂了,在笔试的时候就可以游刃有余,通过面试只有半步之遥了,笔试只能反映你的JAVA技能.

Java并发编程基础

- - 并发编程网 - ifeve.com
并发是一种能并行运行多个程序或并行运行一个程序中多个部分的能力. 如果程序中一个耗时的任务能以异步或并行的方式运行,那么整个程序的吞吐量和可交互性将大大改善. 现代的PC都有多个CPU或一个CPU中有多个核. 是否能合理运用多核的能力将成为一个大规模应用程序的关键. 进程是以独立于其他进程的方式运行的,进程间是互相隔离的.

Java基础—ClassLoader的理解

- - ZJD'S NOTES
} ``` 其余两个ClassLoader都是继承自`ClassLoader`这个类. Java的类加载采用了一种叫做“双亲委托”的方式(稍后解释),所以除了`Bootstarp ClassLoader`其余的ClassLoader都有一个“父”类加载器, 不是通过集成,而是一种包含的关系. ``` ##“双亲委托” 所谓“双亲委托”就是当加载一个类的时候会先委托给父类加载器去加载,当父类加载器无法加载的时候再尝试自己去加载,所以整个类的加载是“自上而下”的,如果都没有加载到则抛出`ClassNotFoundException`异常.

JAVA基础之理解JNI原理

- shuangxi - 博客园-首页原创精华区
JNI是JAVA标准平台中的一个重要功能,它弥补了JAVA的与平台无关这一重大优点的不足,在JAVA实现跨平台的同时,也能与其它语言(如C、C++)的动态库进行交互,给其它语言发挥优势的机会. 有了JAVA标准平台的支持,使JNI模式更加易于实现和使用. 环境说明:ubuntu 10.4.2 LTS系统.

JAVA操作Hbase基础例子

- - CSDN博客云计算推荐文章
        创建一张表.         写入一条数据.         写入一组数据.         //查询出一条数据.         删除一张表.         清空一张表.      * 写入一组数据.      * 获得一组Put.      * 组装一个Put.      * 查询出一条数据.

JAVA并发总结-基础篇

- - CSDN博客编程语言推荐文章
java中有几种方法可以实现一个线程. 继承Thread类,实现Runnable接口创建一个线程的唯一方法是实例化java.lang.Thread类(或其子类),并调用其start()方法. 调用ThreadInstanceA.inerrupt()方法,这样当A线程在Thread的sleep,join方法,或者Object的wait方法的时候会直接抛出InerruptedException,捕捉后便可退出.

JAVA 应用性能监控基础

- - Linux - 操作系统 - ITeye博客
       这里简单介绍了JAVA 应用程序部署linux 服务器上的一些常用监控信息,虽然现在很多自动化监控的东西,但是一些基本的东西,我们还是需要了解.        1.我习惯性先看看 CPU 和内存的使用情况,做一个简单的关注.           命令:top 可以关注运行状态.           命令:大写P:按CPU 使用排序,大写M:按内存使用排序,小写c:详细显示应用       .