package utils; import java.math.BigInteger; import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; import javax.servlet.http.Cookie; import javax.servlet.http.HttpSession; import org.apache.log4j.Logger; /** * Class is used to validate various inputs * <br/><br/> * This file is part of the Security Shepherd Project. * * The Security Shepherd project is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version.<br/> * * The Security Shepherd project is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details.<br/> * * You should have received a copy of the GNU General Public License * along with the Security Shepherd project. If not, see <http://www.gnu.org/licenses/>. * @author Mark Denihan * */ public class Validate { private static org.apache.log4j.Logger log = Logger.getLogger(Validate.class); /** * Finds JSession token from user's cookies[], validates and returns. * @param userCookies Cookies from users browser * @return JSession Id */ public static Cookie getSessionId (Cookie[] userCookies) { int i = 0; Cookie theSessionId = null; for(i = 0; i < userCookies.length; i++) { if(userCookies[i].getName().compareTo("JSESSIONID") == 0) { theSessionId = userCookies[i]; break; //End Loop, because we found the theSessionId } } return theSessionId; } /** * Finds CSRF token from user's cookies[], validates. * @param userCookies All of the user's cookies from their browser * @return csrfCookie */ public static Cookie getToken (Cookie[] userCookies) { int i = 0; Cookie theToken = null; for(i = 0; i < userCookies.length; i++) { if(userCookies[i].getName().compareTo("token") == 0) { theToken = userCookies[i]; break; //End Loop, because we found the token } } if(theToken != null) { //log.debug("Found Cookie " + theToken.getName() + " with value " + theToken.getValue()); //The Token is currently designed to be a random Big Integer. If the Big Integer Case does not work, the token has been modified. Potentially in a malicious manner try { BigInteger theTokenCasted = new BigInteger(theToken.getValue()); BigInteger tenGrand = new BigInteger("10000"); BigInteger tenGrandNeg = new BigInteger("-10000"); if(!(theTokenCasted.compareTo(tenGrand) > 0 || theTokenCasted.compareTo(tenGrandNeg) < 0)) { log.error("CSRF Cookie Token was modified in some manor!"); theToken = null; } } catch (Exception e) { log.error("CSRF Cookie Token was modified in some manor: " + e.toString()); theToken = null; } } return theToken; } /** * Validates class year when creating classes. Class year should be YY/YY, e.g. 11/12. So the first year must be less than the second. * @param classYear Class Year in YY/YY format, e.g. 11/12. * @return Boolean value stating weather or not these supplied attributes make a valid class year */ public static boolean isValidClassYear(String classYear) { boolean result = false; result = classYear.length() == 4; if(result) { try { result = Integer.parseInt(classYear) > 2010; } catch(Exception e) { log.error("Could not parse classYear"); result = false; } } return result; } /** * Email validation * @param email * @return Boolean reflect email validity */ public static boolean isValidEmailAddress(String email) { boolean result = true; try { InternetAddress emailAddr = new InternetAddress(email); emailAddr.validate(); } catch (AddressException ex) { result = false; } return result; } /** * Invalid password detecter * @param passWord * @return */ public static boolean isValidPassword(String passWord) { boolean result = false; result = passWord.length() > 7 && passWord.length() <= 512; if (!result) { log.debug("Invalid Password detected"); } return result; } /** * Used to validate user creation requests * @param userName User Name * @param passWord User Password * @return Boolean value stating weather or not these supplied attributes make a valid user */ public static boolean isValidUser(String userName, String passWord) { boolean result = false; result = userName.length() > 4 && passWord.length() > 7 && userName.length() <= 32 && passWord.length() <= 512; if (!result) { log.debug("Invalid Data detected in Validate.isValidUser()"); } return result; } /** * Used to validate user creation requests * @param userName User Name * @param passWord User Password * @param userAddress User address * @return Boolean value stating weather or not these supplied attributes make a valid user */ public static boolean isValidUser(String userName, String passWord, String userAddress) { boolean result = false; result = userName.length() > 4 && passWord.length() >= 8 && userName.length() <= 32 && passWord.length() <= 512 && userAddress.length() <= 128; if (!result) { log.debug("Invalid Data detected in Validate.isValidUser()"); } return result; } /** * Quick method to prevent data and javascript URLs * @param theUrl * @return */ public static String makeValidUrl(String theUrl) { theUrl = theUrl.toLowerCase(); if (!theUrl.startsWith("http")) { theUrl = "http" + theUrl; log.debug("Transformed to: " + theUrl); } return theUrl; } /** * Session is checked for credentials and ensures that they have not been modified and that they are valid for an administrator * @param ses HttpSession from users browser * @return Boolean value that reflects the validity of the admins session */ public static boolean validateAdminSession(HttpSession ses) { boolean result = false; String userName = new String(); if (ses == null) { log.debug("No Session Found"); } else { if (ses.getAttribute("logout") != null) { log.debug("Logout Attribute Found: Invalidating session..."); ses.invalidate(); // make servlet engine forget the session } else { //log.debug("Active Session Found"); if (ses.getAttribute("userRole") != null && ses.getAttribute("userName") != null) { try { userName = (String) ses.getAttribute("userName"); //log.debug("Session holder is " + userName); String role = (String) ses.getAttribute("userRole"); result = (role.compareTo("admin") == 0); if(!result) log.fatal("User " + userName + " Attempting Admin functions! (CSRF Tokens Not Checked)"); } catch (Exception e) { log.fatal("Tampered Parameter Detected!!! Could not parameters"); } } else { log.debug("Session has no credentials"); } } } return result; } /** * Session is checked for credentials and ensures that they have not been modified and that they are valid for an administrator. This function also validates CSRF tokens * @param ses HttpSession from users browser * @return Boolean value that reflects the validity of the admins session */ public static boolean validateAdminSession(HttpSession ses, Cookie cookieToken, Object requestToken) { boolean result = false; String userName = new String(); if (ses == null) { log.debug("No Session Found"); } else { if (ses.getAttribute("logout") != null) { log.debug("Logout Attribute Found: Invalidating session..."); ses.invalidate(); // make servlet engine forget the session } else { //log.debug("Active Session Found"); if (ses.getAttribute("userRole") != null && ses.getAttribute("userName") != null) { try { userName = (String) ses.getAttribute("userName"); //log.debug("Session holder is " + userName); String role = (String) ses.getAttribute("userRole"); result = (role.compareTo("admin") == 0); if(!result) { //Check CSRF Tokens of User to ensure they are not being CSRF'd into causing Unauthorised Access Alert boolean validCsrfTokens = validateTokens(cookieToken, requestToken); if(validCsrfTokens) log.fatal("User account " + userName + " Attempting Admin functions! (With Valid CSRF Tokens)"); else log.error("User account " + userName + " accessing admin function with bad CSRF Tokens"); } } catch (Exception e) { log.fatal("Tampered Parameter Detected!!! Could not parameters"); } } else { log.debug("Session has no credentials"); } } } return result; } /** * Takes a String submitted to be used to encrypt and makes it the correct length for an encryption key * @param userSalt String to be validated * @return A Valid Encryption Key based on the input */ public static String validateEncryptionKey(String userSalt) { String newKey = new String(); int keySize = userSalt.length(); if(keySize == 16) { //log.debug("Key Already Valid"); newKey = userSalt; } else { if(keySize > 16) { //log.debug("Key too Long..."); newKey = userSalt.substring(0, 16); } else // Shorter than 16 { //log.debug("Key too Short..."); newKey = userSalt; int howManyTimes = (16 / keySize) - 1; //log.debug("Repeating String " + howManyTimes + " times"); for(int i = 0; i < howManyTimes; i++) newKey += userSalt; keySize = newKey.length(); int toAdd = 16 - keySize; //log.debug("Adding " + toAdd + " more characters"); newKey = newKey.concat(userSalt.substring(0, toAdd)); } } log.debug("Encryption key is '" + newKey + "'"); return newKey; } /** * Function that will check if a valid language is set. if not, returns en (English) * @param lang Session Language Parameter * @return en by default, or the valid setting found in the submitted lang */ public static String validateLanguage(HttpSession ses) { String result = "en_GB"; String lang = new String(); try { lang = ses.getAttribute("lang").toString(); //log.debug("lang submitted: " + lang); if(lang != null) { if (!lang.isEmpty()) result = lang; } //log.debug("lang set to: " + result); } catch(Exception e) { log.error("Could not Retrieve User Lang Setting"); } return result; } /** * Validates objects received through a function request. Also ensures max length is not too high. * @param input Object to validate * @param maxLength Maximum length of object * @return Validated String value or empty string value */ public static String validateParameter (Object input, int maxLength) { String result = new String(); try { if(input == null) { result = new String(); } else { result = (String) input; if(result.length() > maxLength) { log.debug("Parameter Too Long: " + result.length() + " characters"); log.debug("Parmaeter Was: " + result); result = new String(); } } } catch(Exception e) { log.debug("Invalid String Parameter: " + e.toString()); result = new String(); } return result; } /** * Session is checked for credentials and ensures that they have not been modified and that they are valid * @param ses HttpSession from users browser * @return Boolean value that reflects the validity of the users session */ public static boolean validateSession(HttpSession ses) { boolean result = false; if (ses == null) { log.debug("No Session Found"); } else { if (ses.getAttribute("logout") != null) { log.debug("Logout Attribute Found: Invalidating session..."); ses.invalidate(); // make servlet engine forget the session } else { // log.debug("Active Session Found"); if (ses.getAttribute("userRole") != null) { try { //log.debug("Session holder is "+ses.getAttribute("userName").toString()); String role = (String) ses.getAttribute("userRole"); result = (role.compareTo("player") == 0 || role.compareTo("admin") == 0); if(!result) log.fatal("User Role Parameter Tampered. Role = " + role); else { String userName = ses.getAttribute("userName").toString(); //Has the user been suspended? Should they be kicked? if(UserKicker.shouldKickUser(userName)) { log.debug(userName + " has been Suspended. Invalidating Session and Reporting Invalid Session"); ses.invalidate(); //Killing Session result = false; //User will not access function they were attempting to call UserKicker.removeFromKicklist(userName); //Removing from kick list, as they are now authenticated, the DB Layer Suspension will prevent them from signing in } } } catch (Exception e) { log.fatal("Tampered Parameter Detected!!! Could not Decrypt stamp"); } } else { log.debug("Session has no credentials"); } } } return result; } /** * This method compares the two submitted tokens after ensuring they are not null and not empty. * @param cookieToken CSRF cookie Token * @param requestToken CSRF request Token * @return A boolean value stating weather or not the tokens are valid */ public static boolean validateTokens (Cookie cookieToken, Object requestToken) { boolean result = false; boolean cookieNull = (cookieToken == null); boolean requestNull = (requestToken == null); if(!cookieNull && !requestNull) { try { String theRequest = (String)requestToken; String theCookie = cookieToken.getValue(); boolean cookieEmpty = theCookie.isEmpty(); boolean requestEmpty = theRequest.isEmpty(); if(!cookieEmpty && !requestEmpty) result = theRequest.compareTo(theCookie) == 0; else if (cookieEmpty) log.error("Cookie Token Empty"); else if (requestEmpty) log.error("Request Token Empty"); if(!result) log.error("CSRF Tokens did not match"); } catch(Exception e) { log.error("CSRF in Request Error: " + e.toString()); } } else { if(cookieNull) log.error("Cookie Token was Null"); else if (requestNull) log.error("Request Token was Null"); } return result; } /** * Validates file name attributes to defend against path traversal * @param fileName File name to validate * @return Boolean value reflecting if valid or not */ /* public static String validateFileName(String fileName) { ShepherdLogManager.logEvent(request.getRemoteAddr(), request.getHeader("X-Forwarded-For"), "fileName: " + fileName); fileName = fileName.replaceAll(" ", "").replaceAll("\\.", "").replaceAll("/", "").replaceAll("\\\\", "").replaceAll("\n", ""); ShepherdLogManager.logEvent(request.getRemoteAddr(), request.getHeader("X-Forwarded-For"), "fileName: " + fileName); return fileName; } */ public static boolean validHostUrl(String hostUrl) { //TODO - Pull other validation steps into this boolean result; result = hostUrl.endsWith("/"); if (!result) log.error("URL Doesn't end with a forward slash. Very likely wrong"); return result; } }