接上个章节讲
2.1、会话固定漏洞(失效的会话管理)
2.1.1、漏洞产生原因
该漏洞主要是因为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>
2.1.2、安全测试方法
漏洞测试比较简单,可借助Firefox、Burpsuite等工具在观察登录前、登录后、退出状态下cookie、jsessionid是否有异同,如果不变,则存在问题。
2.1.3、修复建议
一般来说,解决固定会话是相当容易的。最基本的建议就是:一旦用户登录成功以后,马上invalidate用户的会话。具体的步骤如下:
➊ 用户输入用户名和密码。
➋ 系统对用户进行验证通过。
➌ 已有的会话信息如果仍然需要,则转移到一个临时变量中去(请参考➌)。
➍ invalidate 当前的会话(请参考➍)。
➎ 创建一个新的会话(请参考➎)。
➏ 把临时变量中保存的会话信息恢复到新创建的会话中去(请参考➏)。
➐ 用户使用这个新的会话登录到系统中并进行操作。
修复代码参考:
<%
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");
}
%>
以上修复后的代码中,用户在登录的时候,首先会让之前的session失效,然后又获取新的seesion。
2.2、越权漏洞
2.2.1、漏洞产生原因
如果一个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>
2.2.2、安全测试方法
抓包修改用于区分用户标识的account_id、memberid字段,查看是否可以越权查看其他人信息。
2.2.3、修复建议
判断了用户的Session,在为True的情况下才能够查看返回的信息,因此当用户遍历accno的值来尝试获取返回结果时,会提示无权访问。
<%
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");
}
%>