org.owasp.esapi.reference.DefaultHTTPUtilities Maven / Gradle / Ivy
Show all versions of esapi Show documentation
/**
* OWASP Enterprise Security API (ESAPI)
*
* This file is part of the Open Web Application Security Project (OWASP)
* Enterprise Security API (ESAPI) project. For details, please see
* http://www.owasp.org/index.php/ESAPI.
*
* Copyright (c) 2007 - The OWASP Foundation
*
* The ESAPI is published by OWASP under the BSD license. You should read and accept the
* LICENSE before you use, modify, and/or redistribute this software.
*
* @author Jeff Williams Aspect Security
* @created 2007
*/
package org.owasp.esapi.reference;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.owasp.esapi.ESAPI;
import org.owasp.esapi.HTTPUtilities;
import org.owasp.esapi.Logger;
import org.owasp.esapi.SecurityConfiguration;
import org.owasp.esapi.StringUtilities;
import org.owasp.esapi.User;
import org.owasp.esapi.ValidationErrorList;
import org.owasp.esapi.codecs.Hex;
import org.owasp.esapi.crypto.CipherText;
import org.owasp.esapi.crypto.PlainText;
import org.owasp.esapi.errors.AccessControlException;
import org.owasp.esapi.errors.AuthenticationException;
import org.owasp.esapi.errors.EncodingException;
import org.owasp.esapi.errors.EncryptionException;
import org.owasp.esapi.errors.IntegrityException;
import org.owasp.esapi.errors.IntrusionException;
import org.owasp.esapi.errors.ValidationException;
import org.owasp.esapi.errors.ValidationUploadException;
/**
* Reference implementation of the HTTPUtilities interface. This implementation
* uses the Apache Commons FileUploader library, which in turn uses the Apache
* Commons IO library.
*
* To simplify the interface, some methods use the current request and response that
* are tracked by ThreadLocal variables in the Authenticator. This means that you
* must have called ESAPI.authenticator().setCurrentHTTP(request, response) before
* calling these methods.
*
* Typically, this is done by calling the Authenticator.login() method, which
* calls setCurrentHTTP() automatically. However if you want to use these methods
* in another application, you should explicitly call setCurrentHTTP() in your
* own code. In either case, you *must* call ESAPI.clearCurrent() to clear threadlocal
* variables before the thread is reused. The advantages of having identity everywhere
* outweigh the disadvantages of this approach.
*
* @author Jeff Williams (jeff.williams .at. aspectsecurity.com) Aspect Security
* @since June 1, 2007
* @see org.owasp.esapi.HTTPUtilities
*/
public class DefaultHTTPUtilities implements org.owasp.esapi.HTTPUtilities {
private static volatile HTTPUtilities instance = null;
// Apache Commons FileUpload property for enabling / disabling Java deserialization via file uploads.
// ESAPI will save current value, set it to "false", and then restore value before returning. GitHub issue #417.
private static String DISKFILEITEM_SERIALIZABLE = "org.apache.commons.fileupload.disk.DiskFileItem.serializable";
public static HTTPUtilities getInstance() {
if ( instance == null ) {
synchronized ( DefaultHTTPUtilities.class ) {
if ( instance == null ) {
instance = new DefaultHTTPUtilities();
}
}
}
return instance;
}
/**
* Defines the ThreadLocalRequest to store the current request for this thread.
*/
private class ThreadLocalRequest extends InheritableThreadLocal {
public HttpServletRequest getRequest() {
return super.get();
}
public HttpServletRequest initialValue() {
return null;
}
public void setRequest(HttpServletRequest newRequest) {
super.set(newRequest);
}
}
/**
* Defines the ThreadLocalResponse to store the current response for this thread.
*/
private class ThreadLocalResponse extends InheritableThreadLocal {
public HttpServletResponse getResponse() {
return super.get();
}
public HttpServletResponse initialValue() {
return null;
}
public void setResponse(HttpServletResponse newResponse) {
super.set(newResponse);
}
}
/** The logger. */
private final Logger logger = ESAPI.getLogger("HTTPUtilities");
/** The max bytes. */
static final int maxBytes = ESAPI.securityConfiguration().getAllowedFileUploadSize();
/*
* The currentRequest ThreadLocal variable is used to make the currentRequest available to any call in any part of an
* application. This enables API's for actions that require the request to be much simpler. For example, the logout()
* method in the Authenticator class requires the currentRequest to get the session in order to invalidate it.
*/
private ThreadLocalRequest currentRequest = new ThreadLocalRequest();
/*
* The currentResponse ThreadLocal variable is used to make the currentResponse available to any call in any part of an
* application. This enables API's for actions that require the response to be much simpler. For example, the logout()
* method in the Authenticator class requires the currentResponse to kill the Session ID cookie.
*/
private ThreadLocalResponse currentResponse = new ThreadLocalResponse();
/**
* No arg constructor.
*/
public DefaultHTTPUtilities() {
}
/**
* {@inheritDoc}
* This implementation uses a custom "set-cookie" header rather than Java's
* cookie interface which doesn't allow the use of HttpOnly. Configure the
* HttpOnly and Secure settings in ESAPI.properties.
*/
public void addCookie( Cookie cookie ) {
addCookie( getCurrentResponse(), cookie );
}
/**
* {@inheritDoc}
* This implementation uses a custom "set-cookie" header rather than Java's
* cookie interface which doesn't allow the use of HttpOnly. Configure the
* HttpOnly and Secure settings in ESAPI.properties.
*/
public void addCookie(HttpServletResponse response, Cookie cookie) {
String name = cookie.getName();
String value = cookie.getValue();
int maxAge = cookie.getMaxAge();
String domain = cookie.getDomain();
String path = cookie.getPath();
boolean secure = cookie.getSecure();
// validate the name and value
ValidationErrorList errors = new ValidationErrorList();
String cookieName = ESAPI.validator().getValidInput("cookie name", name, "HTTPCookieName", 50, false, errors);
String cookieValue = ESAPI.validator().getValidInput("cookie value", value, "HTTPCookieValue", 5000, false, errors);
// if there are no errors, then set the cookie either with a header or normally
if (errors.size() == 0) {
if ( ESAPI.securityConfiguration().getForceHttpOnlyCookies() ) {
String header = createCookieHeader(cookieName, cookieValue, maxAge, domain, path, secure);
addHeader(response, "Set-Cookie", header);
} else {
// Issue 23 - If the ESAPI Configuration is set to force secure cookies, force the secure flag on the cookie before setting it
cookie.setSecure( secure || ESAPI.securityConfiguration().getForceSecureCookies() );
response.addCookie(cookie);
}
return;
}
logger.warning(Logger.SECURITY_FAILURE, "Attempt to add unsafe data to cookie (skip mode). Skipping cookie and continuing.");
}
/**
* {@inheritDoc}
*/
public String addCSRFToken(String href) {
User user = ESAPI.authenticator().getCurrentUser();
if (user.isAnonymous()) {
return href;
}
// if there are already parameters append with &, otherwise append with ?
String token = CSRF_TOKEN_NAME + "=" + user.getCSRFToken();
return href.indexOf( '?') != -1 ? href + "&" + token : href + "?" + token;
}
/**
* {@inheritDoc}
*/
public void addHeader(String name, String value) {
addHeader( getCurrentResponse(), name, value );
}
/**
* {@inheritDoc}
*/
public void addHeader(HttpServletResponse response, String name, String value) {
try {
String strippedName = StringUtilities.replaceLinearWhiteSpace(name);
String strippedValue = StringUtilities.replaceLinearWhiteSpace(value);
String safeName = ESAPI.validator().getValidInput("addHeader", strippedName, "HTTPHeaderName", 20, false);
String safeValue = ESAPI.validator().getValidInput("addHeader", strippedValue, "HTTPHeaderValue", 500, false);
response.addHeader(safeName, safeValue);
} catch (ValidationException e) {
logger.warning(Logger.SECURITY_FAILURE, "Attempt to add invalid header denied", e);
}
}
/**
* {@inheritDoc}
*/
public void assertSecureChannel() throws AccessControlException {
assertSecureChannel( getCurrentRequest() );
}
/**
* {@inheritDoc}
*
* This implementation ignores the built-in isSecure() method
* and uses the URL to determine if the request was transmitted over SSL.
* This is because SSL may have been terminated somewhere outside the
* container.
*/
public void assertSecureChannel(HttpServletRequest request) throws AccessControlException {
if ( request == null ) {
throw new AccessControlException( "Insecure request received", "HTTP request was null" );
}
StringBuffer sb = request.getRequestURL();
if ( sb == null ) {
throw new AccessControlException( "Insecure request received", "HTTP request URL was null" );
}
String url = sb.toString();
if ( !url.startsWith( "https" ) ) {
throw new AccessControlException( "Insecure request received", "HTTP request did not use SSL" );
}
}
/**
* {@inheritDoc}
*/
public void assertSecureRequest() throws AccessControlException {
assertSecureRequest( getCurrentRequest() );
}
/**
* {@inheritDoc}
*/
public void assertSecureRequest(HttpServletRequest request) throws AccessControlException {
assertSecureChannel( request );
String receivedMethod = request.getMethod();
String requiredMethod = "POST";
if ( !receivedMethod.equals( requiredMethod ) ) {
throw new AccessControlException( "Insecure request received", "Received request using " + receivedMethod + " when only " + requiredMethod + " is allowed" );
}
}
/**
* {@inheritDoc}
*/
public HttpSession changeSessionIdentifier() throws AuthenticationException {
return changeSessionIdentifier( getCurrentRequest() );
}
/**
* {@inheritDoc}
*/
public HttpSession changeSessionIdentifier(HttpServletRequest request) throws AuthenticationException {
// get the current session
HttpSession oldSession = request.getSession();
// make a copy of the session content
Map temp = new ConcurrentHashMap();
Enumeration e = oldSession.getAttributeNames();
while (e != null && e.hasMoreElements()) {
String name = (String) e.nextElement();
Object value = oldSession.getAttribute(name);
temp.put(name, value);
}
// kill the old session and create a new one
oldSession.invalidate();
HttpSession newSession = request.getSession();
User user = ESAPI.authenticator().getCurrentUser();
user.addSession( newSession );
user.removeSession( oldSession );
// copy back the session content
for (Map.Entry stringObjectEntry : temp.entrySet())
{
newSession.setAttribute(stringObjectEntry.getKey(), stringObjectEntry.getValue());
}
return newSession;
}
/**
* {@inheritDoc}
*/
public void clearCurrent() {
currentRequest.set(null);
currentResponse.set(null);
}
private String createCookieHeader(String name, String value, int maxAge, String domain, String path, boolean secure) {
// create the special cookie header instead of creating a Java cookie
// Set-Cookie:=[; =][; expires=][;
// domain=][; path=][; secure][;HttpOnly]
String header = name + "=" + value;
if (maxAge >= 0) {
header += "; Max-Age=" + maxAge;
}
if (domain != null) {
header += "; Domain=" + domain;
}
if (path != null) {
header += "; Path=" + path;
}
if ( secure || ESAPI.securityConfiguration().getForceSecureCookies() ) {
header += "; Secure";
}
if ( ESAPI.securityConfiguration().getForceHttpOnlyCookies() ) {
header += "; HttpOnly";
}
return header;
}
/**
* {@inheritDoc}
*/
public String decryptHiddenField(String encrypted) {
try {
return decryptString(encrypted);
} catch( EncryptionException e ) {
throw new IntrusionException("Invalid request","Tampering detected. Hidden field data did not decrypt properly.", e);
}
}
/**
* {@inheritDoc}
*/
public Map decryptQueryString(String encrypted) throws EncryptionException {
String plaintext = decryptString(encrypted);
return queryToMap(plaintext);
}
/**
* {@inheritDoc}
*/
public Map decryptStateFromCookie() throws EncryptionException {
return decryptStateFromCookie( getCurrentRequest() );
}
/**
* {@inheritDoc}
*
* @param request
*/
public Map decryptStateFromCookie(HttpServletRequest request) throws EncryptionException {
try {
String encrypted = getCookie( request, ESAPI_STATE );
if ( encrypted == null ) return new HashMap();
String plaintext = decryptString(encrypted);
return queryToMap( plaintext );
} catch( ValidationException e ) {
return null;
}
}
/**
* {@inheritDoc}
*/
public String encryptHiddenField(String value) throws EncryptionException {
return encryptString(value);
}
/**
* {@inheritDoc}
*/
public String encryptQueryString(String query) throws EncryptionException {
return encryptString(query);
}
/**
* {@inheritDoc}
*/
public void encryptStateInCookie(HttpServletResponse response, Map cleartext) throws EncryptionException {
StringBuilder sb = new StringBuilder();
Iterator i = cleartext.entrySet().iterator();
while ( i.hasNext() ) {
try {
Map.Entry entry = (Map.Entry)i.next();
// What do these need to be URL encoded? They are encrypted!
String name = ESAPI.encoder().encodeForURL( entry.getKey().toString() );
String value = ESAPI.encoder().encodeForURL( entry.getValue().toString() );
sb.append(name).append("=").append(value);
if ( i.hasNext() ) sb.append( "&" );
} catch( EncodingException e ) {
logger.error(Logger.SECURITY_FAILURE, "Problem encrypting state in cookie - skipping entry", e );
}
}
String encrypted = encryptString(sb.toString());
if ( encrypted.length() > (MAX_COOKIE_LEN ) ) {
logger.error(Logger.SECURITY_FAILURE, "Problem encrypting state in cookie - skipping entry");
throw new EncryptionException("Encryption failure", "Encrypted cookie state of " + encrypted.length() + " longer than allowed " + MAX_COOKIE_LEN );
}
Cookie cookie = new Cookie( ESAPI_STATE, encrypted );
addCookie( response, cookie );
}
/**
* {@inheritDoc}
*/
public void encryptStateInCookie( Map cleartext ) throws EncryptionException {
encryptStateInCookie( getCurrentResponse(), cleartext );
}
/**
* {@inheritDoc}
*/
public String getCookie( HttpServletRequest request, String name ) throws ValidationException {
Cookie c = getFirstCookie( request, name );
if ( c == null ) return null;
String value = c.getValue();
return ESAPI.validator().getValidInput("HTTP cookie value: " + value, value, "HTTPCookieValue", 1000, false);
}
/**
* {@inheritDoc}
*/
public String getCookie( String name ) throws ValidationException {
return getCookie( getCurrentRequest(), name );
}
/**
* {@inheritDoc}
*/
public String getCSRFToken() {
User user = ESAPI.authenticator().getCurrentUser();
if (user == null) return null;
return user.getCSRFToken();
}
/**
* {@inheritDoc}
*/
public HttpServletRequest getCurrentRequest() {
return currentRequest.getRequest();
}
/**
* {@inheritDoc}
*/
public HttpServletResponse getCurrentResponse() {
return currentResponse.getResponse();
}
/**
* {@inheritDoc}
*/
public List getFileUploads() throws ValidationException {
return getFileUploads( getCurrentRequest(), ESAPI.securityConfiguration().getUploadDirectory(), ESAPI.securityConfiguration().getAllowedFileExtensions() );
}
/**
* {@inheritDoc}
*/
public List getFileUploads(HttpServletRequest request) throws ValidationException {
return getFileUploads(request, ESAPI.securityConfiguration().getUploadDirectory(), ESAPI.securityConfiguration().getAllowedFileExtensions());
}
/**
* {@inheritDoc}
*/
public List getFileUploads(HttpServletRequest request, File finalDir ) throws ValidationException {
return getFileUploads(request, finalDir, ESAPI.securityConfiguration().getAllowedFileExtensions());
}
/**
* {@inheritDoc}
*/
public List getFileUploads(HttpServletRequest request, File finalDir, List allowedExtensions) throws ValidationException {
File tempDir = ESAPI.securityConfiguration().getUploadTempDirectory();
if ( !tempDir.exists() ) {
if ( !tempDir.mkdirs() ) throw new ValidationUploadException( "Upload failed", "Could not create temp directory: " + tempDir.getAbsolutePath() );
}
if( finalDir != null){
if ( !finalDir.exists() ) {
if ( !finalDir.mkdirs() ) throw new ValidationUploadException( "Upload failed", "Could not create final upload directory: " + finalDir.getAbsolutePath() );
}
}
else {
if ( !ESAPI.securityConfiguration().getUploadDirectory().exists()) {
if ( !ESAPI.securityConfiguration().getUploadDirectory().mkdirs() ) throw new ValidationUploadException( "Upload failed", "Could not create final upload directory: " + ESAPI.securityConfiguration().getUploadDirectory().getAbsolutePath() );
}
finalDir = ESAPI.securityConfiguration().getUploadDirectory();
}
List newFiles = new ArrayList();
String dfiPrevValue = "false"; // Fail safely in case of Java Security Manager & weird security policy
try {
dfiPrevValue = System.getProperty(DISKFILEITEM_SERIALIZABLE);
System.setProperty(DISKFILEITEM_SERIALIZABLE, "false");
final HttpSession session = request.getSession(false);
if (!ServletFileUpload.isMultipartContent(request)) {
throw new ValidationUploadException("Upload failed", "Not a multipart request");
}
// this factory will store ALL files in the temp directory,
// regardless of size
DiskFileItemFactory factory = new DiskFileItemFactory(0, tempDir);
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setSizeMax(maxBytes);
// Create a progress listener
ProgressListener progressListener = new ProgressListener() {
private long megaBytes = -1;
private long progress = 0;
public void update(long pBytesRead, long pContentLength, int pItems) {
if (pItems == 0)
return;
long mBytes = pBytesRead / 1000000;
if (megaBytes == mBytes)
return;
megaBytes = mBytes;
progress = (long) (((double) pBytesRead / (double) pContentLength) * 100);
if ( session != null ) {
session.setAttribute("progress", Long.toString(progress));
}
// logger.logSuccess(Logger.SECURITY, " Item " + pItems + " (" + progress + "% of " + pContentLength + " bytes]");
}
};
upload.setProgressListener(progressListener);
List items = upload.parseRequest(request);
for (FileItem item : items)
{
if (!item.isFormField() && item.getName() != null && !(item.getName().equals("")))
{
String[] fparts = item.getName().split("[\\/\\\\]");
String filename = fparts[fparts.length - 1];
if (!ESAPI.validator().isValidFileName("upload", filename, allowedExtensions, false))
{
throw new ValidationUploadException("Upload only simple filenames with the following extensions " + allowedExtensions, "Upload failed isValidFileName check");
}
logger.info(Logger.SECURITY_SUCCESS, "File upload requested: " + filename);
File f = new File(finalDir, filename);
if (f.exists())
{
String[] parts = filename.split("\\/.");
String extension = "";
if (parts.length > 1)
{
extension = parts[parts.length - 1];
}
String filenm = filename.substring(0, filename.length() - extension.length());
f = File.createTempFile(filenm, "." + extension, finalDir);
}
item.write(f);
newFiles.add(f);
// delete temporary file
item.delete();
logger.fatal(Logger.SECURITY_SUCCESS, "File successfully uploaded: " + f);
if (session != null)
{
session.setAttribute("progress", Long.toString(0));
}
}
}
} catch (Exception e) {
if (e instanceof ValidationUploadException) {
throw (ValidationException)e;
}
throw new ValidationUploadException("Upload failure", "Problem during upload:" + e.getMessage(), e);
}
finally {
if ( dfiPrevValue != null ) {
System.setProperty(DISKFILEITEM_SERIALIZABLE, dfiPrevValue);
}
}
return Collections.synchronizedList(newFiles);
}
/**
* Utility to return the first cookie matching the provided name.
* @param request
* @param name
*/
private Cookie getFirstCookie(HttpServletRequest request, String name) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies)
{
if (cookie.getName().equals(name))
{
return cookie;
}
}
}
return null;
}
/**
* {@inheritDoc}
*/
public String getHeader( HttpServletRequest request, String name ) throws ValidationException {
String value = request.getHeader(name);
return ESAPI.validator().getValidInput("HTTP header value: " + value, value, "HTTPHeaderValue", 150, false);
}
/**
* {@inheritDoc}
*/
public String getHeader( String name ) throws ValidationException {
return getHeader( getCurrentRequest(), name );
}
/**
* {@inheritDoc}
*/
public String getParameter( HttpServletRequest request, String name ) throws ValidationException {
String value = request.getParameter(name);
return ESAPI.validator().getValidInput("HTTP parameter value: " + value, value, "HTTPParameterValue", 2000, true);
}
/**
* {@inheritDoc}
*/
public String getParameter( String name ) throws ValidationException {
return getParameter( getCurrentRequest(), name );
}
/**
* {@inheritDoc}
*/
public void killAllCookies() {
killAllCookies( getCurrentRequest(), getCurrentResponse() );
}
/**
* {@inheritDoc}
*
* @param request
* @param response
*/
public void killAllCookies(HttpServletRequest request, HttpServletResponse response) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies)
{
killCookie(request, response, cookie.getName());
}
}
}
/**
* {@inheritDoc}
*
* @param request
* @param response
* @param name
*/
public void killCookie(HttpServletRequest request, HttpServletResponse response, String name) {
String path = "//";
String domain="";
Cookie cookie = getFirstCookie(request, name);
if ( cookie != null ) {
path = cookie.getPath();
domain = cookie.getDomain();
}
Cookie deleter = new Cookie( name, "deleted" );
deleter.setMaxAge( 0 );
if ( domain != null ) deleter.setDomain( domain );
if ( path != null ) deleter.setPath( path );
response.addCookie( deleter );
}
/**
* {@inheritDoc}
*/
public void killCookie( String name ) {
killCookie( getCurrentRequest(), getCurrentResponse(), name );
}
/**
* {@inheritDoc}
*/
public void logHTTPRequest() {
logHTTPRequest( getCurrentRequest(), logger, null );
}
/**
* {@inheritDoc}
*/
public void logHTTPRequest(HttpServletRequest request, Logger logger) {
logHTTPRequest( request, logger, null );
}
/**
* Formats an HTTP request into a log suitable string. This implementation logs the remote host IP address (or
* hostname if available), the request method (GET/POST), the URL, and all the querystring and form parameters. All
* the parameters are presented as though they were in the URL even if they were in a form. Any parameters that
* match items in the parameterNamesToObfuscate are shown as eight asterisks.
*
*
* @param request
*/
public void logHTTPRequest(HttpServletRequest request, Logger logger, List parameterNamesToObfuscate) {
StringBuilder params = new StringBuilder();
Iterator i = request.getParameterMap().keySet().iterator();
while (i.hasNext()) {
String key = (String) i.next();
String[] value = request.getParameterMap().get(key);
for (int j = 0; j < value.length; j++) {
params.append(key).append("=");
if (parameterNamesToObfuscate != null && parameterNamesToObfuscate.contains(key)) {
params.append("********");
} else {
params.append(value[j]);
}
if (j < value.length - 1) {
params.append("&");
}
}
if (i.hasNext())
params.append("&");
}
Cookie[] cookies = request.getCookies();
if ( cookies != null ) {
for (Cookie cooky : cookies)
{
if (!cooky.getName().equals(ESAPI.securityConfiguration().getHttpSessionIdName()))
{
params.append("+").append(cooky.getName()).append("=").append(cooky.getValue());
}
}
}
String msg = request.getMethod() + " " + request.getRequestURL() + (params.length() > 0 ? "?" + params : "");
logger.info(Logger.SECURITY_SUCCESS, msg);
}
private Map queryToMap(String query) {
TreeMap map = new TreeMap();
String[] parts = query.split("&");
for (String part : parts)
{
try
{
String[] nvpair = part.split("=");
String name = ESAPI.encoder().decodeFromURL(nvpair[0]);
String value = ESAPI.encoder().decodeFromURL(nvpair[1]);
map.put(name, value);
}
catch (EncodingException e)
{
// skip the nvpair with the encoding problem - note this is already logged.
}
}
return map;
}
/**
* {@inheritDoc}
*
* This implementation simply checks to make sure that the forward location starts with "WEB-INF" and
* is intended for use in frameworks that forward to JSP files inside the WEB-INF folder.
*/
public void sendForward(HttpServletRequest request, HttpServletResponse response, String location) throws AccessControlException,ServletException,IOException {
if (!location.startsWith("WEB-INF")) {
throw new AccessControlException("Forward failed", "Bad forward location: " + location);
}
RequestDispatcher dispatcher = request.getRequestDispatcher(location);
dispatcher.forward( request, response );
}
/**
* {@inheritDoc}
*/
public void sendForward( String location ) throws AccessControlException,ServletException,IOException {
sendForward( getCurrentRequest(), getCurrentResponse(), location);
}
/**
* {@inheritDoc}
*
* This implementation checks against the list of safe redirect locations defined in ESAPI.properties.
*/
public void sendRedirect(HttpServletResponse response, String location) throws AccessControlException, IOException {
if (!ESAPI.validator().isValidRedirectLocation("Redirect", location, false)) {
logger.fatal(Logger.SECURITY_FAILURE, "Bad redirect location: " + location);
throw new AccessControlException("Redirect failed", "Bad redirect location: " + location);
}
response.sendRedirect(location);
}
/**
* {@inheritDoc}
*/
public void sendRedirect( String location ) throws AccessControlException,IOException {
sendRedirect( getCurrentResponse(), location);
}
/**
* {@inheritDoc}
*/
public void setContentType() {
setContentType( getCurrentResponse() );
}
/**
* {@inheritDoc}
*/
public void setContentType(HttpServletResponse response) {
response.setContentType((ESAPI.securityConfiguration()).getResponseContentType());
}
/**
* {@inheritDoc}
*/
public void setCurrentHTTP(HttpServletRequest request, HttpServletResponse response) {
currentRequest.setRequest(request);
currentResponse.setResponse(response);
}
/**
* {@inheritDoc}
*/
public void setHeader(HttpServletResponse response, String name, String value) {
try {
SecurityConfiguration sc = ESAPI.securityConfiguration();
String strippedName = StringUtilities.replaceLinearWhiteSpace(name);
String strippedValue = StringUtilities.replaceLinearWhiteSpace(value);
String safeName = ESAPI.validator().getValidInput("setHeader", strippedName, "HTTPHeaderName", sc.getIntProp("HttpUtilities.MaxHeaderNameSize"), false);
String safeValue = ESAPI.validator().getValidInput("setHeader", strippedValue, "HTTPHeaderValue", sc.getIntProp("HttpUtilities.MaxHeaderValueSize"), false);
response.setHeader(safeName, safeValue);
} catch (ValidationException e) {
logger.warning(Logger.SECURITY_FAILURE, "Attempt to set invalid header denied", e);
}
}
/**
* {@inheritDoc}
*/
public void setHeader( String name, String value ) {
setHeader( getCurrentResponse(), name, value );
}
/**
* {@inheritDoc}
*/
public void setNoCacheHeaders() {
setNoCacheHeaders( getCurrentResponse() );
}
/**
* {@inheritDoc}
*
* @param response
*/
public void setNoCacheHeaders(HttpServletResponse response) {
// HTTP 1.1
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
// HTTP 1.0
response.setHeader("Pragma","no-cache");
response.setDateHeader("Expires", -1);
}
/**
* {@inheritDoc}
*
* Save the user's remember me data in an encrypted cookie and send it to the user.
* Any old remember me cookie is destroyed first. Setting this cookie will keep the user
* logged in until the maxAge passes, the password is changed, or the cookie is deleted.
* If the cookie exists for the current user, it will automatically be used by ESAPI to
* log the user in, if the data is valid and not expired.
*
* @param request
* @param response
*/
public String setRememberToken( HttpServletRequest request, HttpServletResponse response, String password, int maxAge, String domain, String path ) {
User user = ESAPI.authenticator().getCurrentUser();
try {
killCookie(request, response, REMEMBER_TOKEN_COOKIE_NAME );
// seal already contains random data
String clearToken = user.getAccountName() + "|" + password;
long expiry = ESAPI.encryptor().getRelativeTimeStamp(maxAge * 1000);
String cryptToken = ESAPI.encryptor().seal(clearToken, expiry);
SecurityConfiguration sg = ESAPI.securityConfiguration();
boolean forceSecureCookies = sg.getBooleanProp("HttpUtilities.ForceSecureCookies");
boolean forceHttpOnly = sg.getBooleanProp("HttpUtilities.ForceHttpOnlyCookies");
// Do NOT URLEncode cryptToken before creating cookie. See Google Issue # 144,
// which was marked as "WontFix".
Cookie cookie = new Cookie( REMEMBER_TOKEN_COOKIE_NAME, cryptToken );
cookie.setMaxAge( maxAge );
cookie.setDomain( domain );
cookie.setPath( path );
cookie.setHttpOnly(forceHttpOnly);
cookie.setSecure(forceSecureCookies);
response.addCookie( cookie );
logger.info(Logger.SECURITY_SUCCESS, "Enabled remember me token for " + user.getAccountName() );
return cryptToken;
} catch( IntegrityException e ) {
logger.warning(Logger.SECURITY_FAILURE, "Attempt to set remember me token failed for " + user.getAccountName(), e );
return null;
}
}
public String setRememberToken(HttpServletRequest request, HttpServletResponse response, int maxAge, String domain, String path){
String rval = "";
User user = ESAPI.authenticator().getCurrentUser();
try{
killCookie(request,response, REMEMBER_TOKEN_COOKIE_NAME);
// seal already contains random data
String clearToken = user.getAccountName();
long expiry = ESAPI.encryptor().getRelativeTimeStamp(maxAge * 1000);
String cryptToken = ESAPI.encryptor().seal(clearToken, expiry);
SecurityConfiguration sg = ESAPI.securityConfiguration();
boolean forceSecureCookies = sg.getBooleanProp("HttpUtilities.ForceSecureCookies");
boolean forceHttpOnly = sg.getBooleanProp("HttpUtilities.ForceHttpOnlyCookies");
// Do NOT URLEncode cryptToken before creating cookie. See Google Issue # 144,
// which was marked as "WontFix".
Cookie cookie = new Cookie( REMEMBER_TOKEN_COOKIE_NAME, cryptToken );
cookie.setMaxAge( maxAge );
cookie.setDomain( domain );
cookie.setPath( path );
cookie.setHttpOnly(forceHttpOnly);
cookie.setSecure(forceSecureCookies);
response.addCookie( cookie );
logger.info(Logger.SECURITY_SUCCESS, "Enabled remember me token for " + user.getAccountName() );
} catch( IntegrityException e){
logger.warning(Logger.SECURITY_FAILURE, "Attempt to set remember me token failed for " + user.getAccountName(), e );
return null;
}
return rval;
}
/**
* {@inheritDoc}
*/
public String setRememberToken( String password, int maxAge, String domain, String path ) {
return setRememberToken( getCurrentRequest(), getCurrentResponse(), password, maxAge, domain, path );
}
/**
* {@inheritDoc}
*/
public void verifyCSRFToken() throws IntrusionException {
verifyCSRFToken( getCurrentRequest() );
}
/**
* {@inheritDoc}
*
* This implementation uses the CSRF_TOKEN_NAME parameter for the token.
*
* @param request
*/
public void verifyCSRFToken(HttpServletRequest request) throws IntrusionException {
User user = ESAPI.authenticator().getCurrentUser();
// check if user authenticated with this request - no CSRF protection required
if( request.getAttribute(user.getCSRFToken()) != null ) {
return;
}
String token = request.getParameter(CSRF_TOKEN_NAME);
if ( !user.getCSRFToken().equals( token ) ) {
throw new IntrusionException("Authentication failed", "Possibly forged HTTP request without proper CSRF token detected");
}
}
/**
* {@inheritDoc}
*/
public T getSessionAttribute( String key ) {
final HttpSession session = ESAPI.currentRequest().getSession(false);
if ( session != null )
return (T) session.getAttribute(key);
return null;
}
/**
* {@inheritDoc}
*/
public T getSessionAttribute(HttpSession session, String key)
{
return (T) session.getAttribute(key);
}
/**
* {@inheritDoc}
*/
public T getRequestAttribute(String key)
{
return (T) ESAPI.currentRequest().getAttribute(key);
}
/**
* {@inheritDoc}
*/
public T getRequestAttribute(HttpServletRequest request, String key)
{
return (T) request.getAttribute( key );
}
/////////////////////
/* Helper method to encrypt using new Encryptor encryption methods and
* return the serialized ciphertext as a hex-encoded string.
*/
private String encryptString(String plaintext) throws EncryptionException {
PlainText pt = new PlainText(plaintext);
CipherText ct = ESAPI.encryptor().encrypt(pt);
byte[] serializedCiphertext = ct.asPortableSerializedByteArray();
return Hex.encode(serializedCiphertext, false);
}
/* Helper method to decrypt a hex-encode serialized ciphertext string and
* to decrypt it using the new Encryptor decryption methods.
*/
private String decryptString(String ciphertext) throws EncryptionException {
byte[] serializedCiphertext = Hex.decode(ciphertext);
CipherText restoredCipherText =
CipherText.fromPortableSerializedBytes(serializedCiphertext);
PlainText plaintext = ESAPI.encryptor().decrypt(restoredCipherText);
return plaintext.toString();
}
}