All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.directwebremoting.dwrp.BaseDwrpHandler Maven / Gradle / Ivy

package org.directwebremoting.dwrp;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.directwebremoting.export.System;
import org.directwebremoting.extend.Handler;

/**
 * @author Joe Walker [joe at getahead dot ltd dot uk]
 */
public abstract class BaseDwrpHandler implements Handler
{
    /**
     * Complain if GET is disallowed
     * @param batch ...
     */
    protected void checkGetAllowed(Batch batch)
    {
        if (!allowGetButMakeForgeryEasier && batch.isGet())
        {
            log.error("GET is disallowed because it makes request forgery easier. See allowGetButMakeForgeryEasier setting for more details.");
            throw new SecurityException("GET Disallowed");
        }
    }

    /**
     * Check that this request is not subject to a CSRF attack
     * @param request The original browser's request
     * @param batch The data that we've parsed from the request body
     */
    protected void checkNotCsrfAttack(HttpServletRequest request, Batch batch)
    {
        if (!crossDomainSessionSecurity)
        {
            return;
        }

        // A check to see that this isn't a csrf attack
        // http://en.wikipedia.org/wiki/Cross-site_request_forgery
        // http://www.tux.org/~peterw/csrf.txt

        // It is only interesting to inspect requests that carry cookies, as CSRF
        // attacks are targeted at the state carried by other cookies
        if (request.getCookies() == null)
        {
            return;
        }

        // If there is a DWRSESSIONID cookie on the request then the
        // corresponding session value in the batch must match
        int dwrCookieCount = 0;
        int dwrCookieMatchCount = 0;
        for (Cookie cookie : request.getCookies())
        {
            if (cookie.getName().equals("DWRSESSIONID"))
            {
                dwrCookieCount++;
                if (cookie.getValue().equals(batch.getDwrSessionId()))
                {
                    dwrCookieMatchCount++;
                }
            }
        }

        // Issue error if no DWRSESSIONID cookie matches (probably an attack)
        if (dwrCookieCount > 0 && dwrCookieMatchCount == 0)
        {
            throwCsrfException();
        }

        // Issue warning in log if there were multiple and different cookies
        if (dwrCookieCount > 1 && dwrCookieMatchCount != dwrCookieCount)
        {
            log.warn("Multiple DWRSESSIONID cookies with different values in request.");
        }

        if (!extendedCrossDomainSessionSecurity)
        {
            return;
        }

        // Extended check: Issue error if there was no DWRSESSIONID cookie
        if (dwrCookieCount == 0)
        {
            throwCsrfException();
        }

        // Extended check: correlate and restrict DWRSESSIONID to owning session if any
        HttpSession sess = request.getSession(false);
        if (sess == null)
        {
            return;
        }
        String registeredDwrSessionId = updateRegisteredDwrSession(sess, batch);
        if (!registeredDwrSessionId.equals(batch.getDwrSessionId()))
        {
            throwCsrfException();
        }
    }

    private void throwCsrfException() throws SecurityException
    {
        log.error("A request has been denied as a potential CSRF attack. This security check is performed as DWR's crossDomainSessionSecurity setting is active. Read more in the DWR documentation.");
        throw new SecurityException("CSRF Security Error (see server log for details).");
    }

    /**
     * Update data we store for the CSRF protection
     * @param request The original browser's request
     * @param batch The data that we've parsed from the request body
     */
    protected void updateCsrfState(HttpServletRequest request, Batch batch)
    {
        if (!crossDomainSessionSecurity || !extendedCrossDomainSessionSecurity)
        {
            return;
        }

        if (request.getCookies() == null)
        {
            return;
        }

        HttpSession sess = request.getSession(false);
        if (sess == null)
        {
            return;
        }

        // Register DWRSESSIONID on the way out in case HttpSession was created/changed
        updateRegisteredDwrSession(sess, batch);
    }

    /**
     * Get registered DWRSESSIONID from session or register ourselves
     * @param sess
     * @param batch
     * @return DWRSESSIONID registered for this session
     */
    private String updateRegisteredDwrSession(HttpSession sess, Batch batch)
    {
        String registeredDwrSessionId;

        try
        {
            registeredDwrSessionId = (String) sess.getAttribute(System.ATTRIBUTE_DWRSESSIONID);
        }
        catch(IllegalStateException ex)
        {
            // We are probably on a misbehaving servlet container that has given
            // us an invalidated session instead of null in the getSession(false)
            // call (Wildfly/Undertow?)
            // We don't want to return null from this method so we use the empty
            // string, doesn't matter much as there is no session to protect
            registeredDwrSessionId = "";
        }

        if (registeredDwrSessionId == null)
        {
            // Check getAttribute again and update with setAttribute while locking out all other threads
            // (note that this is not a DCL anti-pattern as we are using an immutable
            // write-once design and getAttribute/setAttribute methods are also synchronized!)
            synchronized (this)
            {
                registeredDwrSessionId = (String) sess.getAttribute(System.ATTRIBUTE_DWRSESSIONID);
                if (registeredDwrSessionId == null)
                {
                    registeredDwrSessionId = batch.getDwrSessionId();
                    sess.setAttribute(System.ATTRIBUTE_DWRSESSIONID, registeredDwrSessionId);
                }
            }
        }
        return registeredDwrSessionId;
    }

    /**
     * Do we perform cross-domain session security checks?
     * @param crossDomainSessionSecurity the cross domain session security setting
     */
    public void setCrossDomainSessionSecurity(boolean crossDomainSessionSecurity)
    {
        this.crossDomainSessionSecurity = crossDomainSessionSecurity;
    }

    /**
     * Do we perform cross-domain session security checks?
     */
    private boolean crossDomainSessionSecurity = true;

    /**
     * Do we perform extended cross-domain session security checks?
     * @param value the cross domain session security setting
     */
    public void setExtendedCrossDomainSessionSecurity(boolean value)
    {
        this.extendedCrossDomainSessionSecurity = value;
    }

    /**
     * Do we perform extended cross-domain session security checks?
     */
    private boolean extendedCrossDomainSessionSecurity = true;

    /**
     * @param allowGetButMakeForgeryEasier Do we reduce security
     */
    public void setAllowGetButMakeForgeryEasier(boolean allowGetButMakeForgeryEasier)
    {
        this.allowGetButMakeForgeryEasier = allowGetButMakeForgeryEasier;
    }

    /**
     * By default we disable GET
     */
    private boolean allowGetButMakeForgeryEasier = false;

    /**
     * The log stream
     */
    private static final Log log = LogFactory.getLog(BaseDwrpHandler.class);
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy