Securing Your Java Code 3
Learn Secure Coding
Sensitive Data Exposure
A web application is vulnerable if it does not store sensitive information like password, bank details, personal user information encrypted inside the data storage or database. A strong encryption algorithm and salted hashing techniques should be used to store sensitive user information. And the sensitive information between server and client should be encrypted using SSL.
[download]
Vulnerable Code:
[html]
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<h1>Login Page</h1>
<input type="submit" />
</form>
[/html]
The username-password pair is sent from client to sever over HTTP, due to which it can be sniffed on network and captured because it is plaintext. But if the same communication had taken place on HTTPS, then the data sent from client to server and from server to client would have been SSL encrypted and would not be captured.
Missing Function Level Access Control
An application checks the rights of a user before that resource is shown to the user, but the user might be able to bypass that check if it is a flawed implementation and requests can be forged or one level of access can be used to access resources of another level.
Vulnerable Code:
[html]
<%@page import="java.util.Enumeration"%>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<%
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)
{
out.println("
<h1>Welcome to Admin Console</h1>
");
}
else
{
out.println("You are not authorized to view this page");
}
[/html]
In vulnerable code, if the page admin-vuln.jsp is directly accessed, then the page will show an error message saying "You are not authorized to view this page". But as you can see in the highlighted text, the check is only if the user is logged-in and a session is registered for it. Then the flag is set to 1, which later on allows showing the Admin Console.
After successful login accessing admin page:
Patched Code:
[html]
<%@page import="java.util.Enumeration"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<%
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.equals("admin"))
if(name.equals("useracc") && !(value.isEmpty()))
{
flag = 1;
break;
}
}
if(flag == 1)
{
if(!(session.getAttribute("useracc").toString()).equals("admin"))
{
response.sendRedirect("login.jsp");
}
out.println("
<h1>Welcome to Admin Console</h1>
");
}
else
{
out.println("You are not authorized to view this page");
}
[/html]
In a patched version of the same code, we can see that another check has been included after checking the value of flag variable. If the session has not been registered for the admin user, then the user is redirected to the login page, else Admin Console is shown. The admin session is registered at the time of login if the user trying to login is using admin credentials.
Login with a non-admin user account:
Access the admin page after successful login:
Cross-Site Request Forgery
CSRF or Cross-Site Request Forgery is a type of a vulnerability targeting authenticated pages only. An authenticated page suffering from this web application vulnerability can be used to send unauthorized requests (both GET and POST) by hosting a malicious file to forge a request. When an unsuspecting user who has opened his authenticated page already in the same web browser navigates to a malicious page, it will send a forged HTTP request.
Vulnerable Code:
[html]
<%@page import="java.util.Enumeration"%>
<%@ page import="java.sql.*" %>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<%
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)
{ try
{
if(request.getMethod().equalsIgnoreCase("post"))
{
String src_accno = session.getAttribute("useracc").toString();
String dest_accno = request.getParameter("taccno");
int tamt = Integer.parseInt(request.getParameter("tamt"));
int old_src_bal = 0;
int old_dest_bal = 0;
int new_src_bal = 0;
int new_dest_bal = 0;
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,src_accno);
ResultSet rs = ps.executeQuery();
if(rs.next())
{
old_src_bal = Integer.parseInt(rs.getString("balance"));
ps = (PreparedStatement) con.prepareStatement("select * from account_balance where accno=? limit 0,1");
ps.setString(1,dest_accno);
ResultSet rs1 = ps.executeQuery();
if(rs1.next())
{
old_dest_bal = Integer.parseInt(rs.getString("balance"));
new_src_bal = old_src_bal-tamt;
ps = (PreparedStatement) con.prepareStatement("update account_balance set balance=? where accno=?");
ps.setString(1,Integer.toString(new_dest_bal));
ps.setString(2,dest_accno);
ps = (PreparedStatement) con.prepareStatement("update account_balance set balance=? where accno=?");
ps.setString(1,Integer.toString(new_src_bal));
ps.setString(2,src_accno);
if(r1>0 && r2>0)
{
out.println("Transfer successfull");
}
else
{
out.println("Transfer failed");
}
}
else
{
out.println("Error: Contact administrator.");
}
}
else
{
out.println("Error: Contact administrator.");
}
}
else
{
<form action="transfer-vuln.jsp" method="post">");
out.println("Account number to transfer to: <input type="text" name="taccno" />
");
out.println("Amount to transfer: <input type="text" name="tamt" />
");
out.println("<input type="submit" />");
out.println("</form>");
}
}
catch(Exception exp)
{
out.println("Error occurred. Contact administrator.");
}
}
else
{
response.sendRedirect("login.jsp");
}
[/html]
The above vulnerable code shows that when the page is accessed over HTTP GET method, then an HTML form for balance transfer is shown. When the same page is accessed over HTTP POST method, the balance transfer request is processed. But we can see that the request simply contains the account number where to transfer and the amount to transfer. This request can easily be forged by hosting a malicious page somewhere and sending its link to unsuspecting users. The forged request would transfer the amount to another bank account without user's permission.
Patched Code (process.jsp):
[html]
<%@page import="java.util.Calendar"%>
<%@page import="java.text.SimpleDateFormat"%>
<%@page import="java.text.DateFormat"%>
<%@page import="java.util.Enumeration"%>
<%@page import="java.math.BigInteger"%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="java.sql.*" %>
<%@ page import="java.security.MessageDigest" %>
<%@ page import="java.math.*" %>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<%
int flag = 0;
if(request.getMethod().equalsIgnoreCase("post"))
{
String accno = request.getParameter("accno");
String pass = request.getParameter("pass");
Class.forName("com.mysql.jdbc.Driver");
Connection con = (Connection) DriverManager.getConnection("jdbc:mysql://localhost/mydb", "root", "");
PreparedStatement ps = (PreparedStatement) con.prepareStatement("select * from accounts where accno=? limit 0,1");
ps.setString(1,accno);
ResultSet rs = ps.executeQuery();
if(rs.next())
{
String s = pass+rs.getString("regtime");
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;
}
if(calc_hash.equals(rs.getString("password")))
{
DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
Calendar cal = Calendar.getInstance();
//out.println(dateFormat.format(cal.getTime()));
String date_time = (dateFormat.format(cal.getTime())).toString();
String random_string = RandomStringUtils.randomAlphanumeric(32);
String csrf_input = date_time+random_string;
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(csrf_input.getBytes());
byte byteData[] = md.digest();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < byteData.length; i++)
{
sb.append(Integer.toString((byteData[i] & 0xff) + 0x100, 16).substring(1));
}
//out.println("Hex format : " + sb.toString());
String csrf_token = sb.toString();
session.setAttribute("useracc", rs.getString("accno"));
flag = 1;
}
else
{
out.println("Login failed. Please try again.");
}
}
else
{
out.println("Login failed. Please try again.");
}
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)
{
response.sendRedirect("home.jsp");
}
else
{
response.sendRedirect("login.jsp");
}
[/html]
The patching of this vulnerability starts at the time of login. When a successful login has been confirmed, then the current date-time and a random alphanumeric string 32 character long is used as input to generate a SHA-256 hash. This hash is registered in a session along with that of the logged-in user's session.
Patched Code (transfer-patch.jsp):
[html]
<%@page import="java.util.Enumeration"%>
<%@ page import="java.sql.*" %>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<%
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)
{ try
{
if(request.getMethod().equalsIgnoreCase("post"))
{
if(((session.getAttribute("csrf_token")).toString()).equals(request.getParameter("csrf_token")))
{
String src_accno = session.getAttribute("useracc").toString();
String dest_accno = request.getParameter("taccno");
int tamt = Integer.parseInt(request.getParameter("tamt"));
int old_src_bal = 0;
int old_dest_bal = 0;
int new_src_bal = 0;
int new_dest_bal = 0;
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,src_accno);
ResultSet rs = ps.executeQuery();
if(rs.next())
{
old_src_bal = Integer.parseInt(rs.getString("balance"));
ps = (PreparedStatement) con.prepareStatement("select * from account_balance where accno=? limit 0,1");
ps.setString(1,dest_accno);
ResultSet rs1 = ps.executeQuery();
if(rs1.next())
{
old_dest_bal = Integer.parseInt(rs.getString("balance"));
new_src_bal = old_src_bal-tamt;
ps = (PreparedStatement) con.prepareStatement("update account_balance set balance=? where accno=?");
ps.setString(1,Integer.toString(new_dest_bal));
ps.setString(2,dest_accno);
ps = (PreparedStatement) con.prepareStatement("update account_balance set balance=? where accno=?");
ps.setString(1,Integer.toString(new_src_bal));
ps.setString(2,src_accno);
if(r1>0 && r2>0)
{
out.println("Transfer successfull");
}
else
{
out.println("Transfer failed");
}
}
else
{
out.println("Error: Contact administrator.");
}
}
else
{
out.println("Error: Contact administrator.");
}
}
else
{
out.println("Invalid token");
}
}
else
{
<form action="transfer-patch.jsp" method="post">");
out.println("Account number to transfer to: <input type="text" name="taccno" />
");
out.println("Amount to transfer: <input type="text" name="tamt" />
");
out.println("<input type="hidden" name="csrf_token" value=""+session.getAttribute("csrf_token")+"" />
");
out.println("<input type="submit" />");
out.println("</form>");
}
}
catch(Exception exp)
{
out.println("Error occurred. Contact administrator.");
}
}
else
{
response.sendRedirect("login.jsp");
}
[/html]
In the second level of patching, the session variable csrf_token value is used as a hidden element in HTML form. When the balance transfer request is sent, then the value of the hidden form element is compared with that of the csrf_token session variable. If it matches, then the request is further processed, or else it is dropped with an error message. Since an attacker has no way to figure out what the random csrf_token will be, request forgery is not possible.
Using Components with Known Vulnerabilities
During application development it must be ensured that the components used in or with the web application are not vulnerable, i.e. a code or component which already has a vulnerability should not be used. If this practice is not followed, then it automatically opens a loop hole inside the web application, leading to data compromise or more.
For example, a default installation of earlier versions of JBoss had the JMX-Console publicly accessible, which can lead to arbitrary file upload.
To patch this vulnerability it should be ensured during application development that no vulnerable code or component or external library be used inside or along with the web application.
Unvalidated Redirects and Forwards
Unvalidated redirects/forwards take place when an application redirects a user from one page to another page. The page might be on the same website or different website. This vulnerability should not be ignored because a user being redirected from a trusted website to a malicious website might not suspect the security or privacy breach that might take place.
Vulnerable Code:
[html]
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<%
try
{
String url = request.getParameter("url");
response.sendRedirect(url);
}
catch(Exception e)
{
out.println("Error: Unexpected error has occurred");
}
[/html]
The above code shows that it accepts a parameter "url". Then it redirects the user to the page/website specified in this parameter. It does not check what the value of url is. It simply accepts a value and redirects to that page/website.
Patched Code:
[html]
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<%
try
{
String url = request.getParameter("url");
Class.forName("com.mysql.jdbc.Driver");
Connection con = (Connection) DriverManager.getConnection("jdbc:mysql://localhost/mydb", "root", "");
PreparedStatement ps = (PreparedStatement) con.prepareStatement("select * from urls where url=? and state=true limit 0,1");
ps.setString(1,url);
ResultSet rs = ps.executeQuery();
if(rs.next())
{
if(rs.getBoolean("state"))
{
response.sendRedirect(url);
}
else
{
out.println("Redirecting to this URL has been disabled now");
}
}
else
{
out.println("Redirection to this URL is not allowed");
}
}
catch(Exception e)
{
out.println("Error: Unexpected error has occurred");
}
[/html]
The patched code shown above accepts a parameter "url" containing a value of a page/website. Now the value of url variable is used to find if it is stored in database. The database stores a list of URLs on which redirection is allowed, along with a "state" column, which specifies if the redirection to the URL is enabled or disabled. If there is no result from the database for the value of url, then redirection is not allowed. And if the value is there in the database, but its state is false, then redirection is disabled. The final case for allowed redirection is when the URL is specified in the database along with a true state.
Learn Secure Coding