Package org.apache.wicket.protocol.http.request

Source Code of org.apache.wicket.protocol.http.request.CryptedUrlWebRequestCodingStrategy$HackAttackException

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.wicket.protocol.http.request;

import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.wicket.Application;
import org.apache.wicket.IRequestTarget;
import org.apache.wicket.Request;
import org.apache.wicket.RequestCycle;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.protocol.http.PageExpiredException;
import org.apache.wicket.protocol.http.RequestUtils;
import org.apache.wicket.protocol.http.WicketURLDecoder;
import org.apache.wicket.protocol.http.WicketURLEncoder;
import org.apache.wicket.request.IRequestCodingStrategy;
import org.apache.wicket.request.RequestParameters;
import org.apache.wicket.request.target.coding.IRequestTargetUrlCodingStrategy;
import org.apache.wicket.util.crypt.ICrypt;
import org.apache.wicket.util.string.AppendingStringBuffer;
import org.apache.wicket.util.string.Strings;
import org.apache.wicket.util.string.UrlUtils;
import org.apache.wicket.util.value.ValueMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
* This is a request coding strategy which encrypts the URL and hence makes it impossible for users
* to guess what is in the url and rebuild it manually. It uses the CryptFactory registered with the
* application to encode and decode the URL. Hence, the coding algorithm must be a two-way one
* (reversible). Because the algorithm is reversible, URLs which were bookmarkable before will
* remain bookmarkable.
* <p>
* To register the request coding strategy to need to do the following:
*
* <pre>
* protected IRequestCycleProcessor newRequestCycleProcessor()
* {
*   return new WebRequestCycleProcessor()
*   {
*     protected IRequestCodingStrategy newRequestCodingStrategy()
*     {
*       return new CryptedUrlWebRequestCodingStrategy(new WebRequestCodingStrategy());
*     }
*   };
* }
* </pre>
*
* <b>Note:</b> When trying to hack urls in the browser an exception might be caught while decoding
* the URL. By default, for safety reasons a very simple WicketRuntimeException is thrown. The
* original stack trace is only logged.
* <p/>
* <b>Note:</b> by default Wicket uses
* {@link org.apache.wicket.util.crypt.KeyInSessionSunJceCryptFactory} to encrypt the query-string.
* KeyInSessionSunJceCryptFactory creates a unique encryption key per session and and uses the
* session as persistence store. Hence stateless pages will create a session as well and are no
* longer stateless. You may avoid that by implementing your own ICryptFactory which e.g. uses an
* application wide encryption key and thus doesn't need a session. You can register your own
* ICryptFactory via Application.getSecuritySettings().setCryptFactory().
*
* @author Juergen Donnerstag
*/
public class CryptedUrlWebRequestCodingStrategy implements IRequestCodingStrategy
{
  /** log. */
  private static final Logger log = LoggerFactory.getLogger(CryptedUrlWebRequestCodingStrategy.class);

  /** The default request coding strategy most of the methods are delegated to */
  private final IRequestCodingStrategy defaultStrategy;

  /**
   * Construct.
   *
   * @param defaultStrategy
   *            The default strategy most requests are forwarded to
   */
  public CryptedUrlWebRequestCodingStrategy(final IRequestCodingStrategy defaultStrategy)
  {
    this.defaultStrategy = defaultStrategy;
  }

  /**
   * Decode the querystring of the URL
   *
   * @see org.apache.wicket.request.IRequestCodingStrategy#decode(org.apache.wicket.Request)
   */
  public RequestParameters decode(final Request request)
  {
    String url = request.decodeURL(request.getURL());
    String decodedQueryParams = decodeURL(url);
    if (decodedQueryParams != null)
    {
      // The difficulty now is that this.defaultStrategy.decode(request)
      // doesn't know the just decoded url which is why must create
      // a fake Request for.
      Request fakeRequest = new DecodedUrlRequest(request, url, decodedQueryParams);
      return defaultStrategy.decode(fakeRequest);
    }

    return defaultStrategy.decode(request);
  }

  /**
   * Encode the querystring of the URL
   *
   * @see org.apache.wicket.request.IRequestCodingStrategy#encode(org.apache.wicket.RequestCycle,
   *      org.apache.wicket.IRequestTarget)
   */
  public CharSequence encode(final RequestCycle requestCycle, final IRequestTarget requestTarget)
  {
    CharSequence url = defaultStrategy.encode(requestCycle, requestTarget);
    url = encodeURL(url);
    return url;
  }

  /**
   * @see org.apache.wicket.request.IRequestTargetMounter#mount(org.apache.wicket.request.target.coding.IRequestTargetUrlCodingStrategy)
   */
  public void mount(IRequestTargetUrlCodingStrategy urlCodingStrategy)
  {
    defaultStrategy.mount(urlCodingStrategy);
  }

  /**
   * @see org.apache.wicket.request.IRequestTargetMounter#unmount(java.lang.String)
   */
  public void unmount(String path)
  {
    defaultStrategy.unmount(path);
  }

  /**
   *
   * @see org.apache.wicket.request.IRequestTargetMounter#addIgnoreMountPath(java.lang.String)
   */
  public void addIgnoreMountPath(String path)
  {
    defaultStrategy.addIgnoreMountPath(path);
  }

  /**
   * @see org.apache.wicket.request.IRequestTargetMounter#urlCodingStrategyForPath(java.lang.String)
   */
  public IRequestTargetUrlCodingStrategy urlCodingStrategyForPath(String path)
  {
    return defaultStrategy.urlCodingStrategyForPath(path);
  }

  /**
   * @see org.apache.wicket.request.IRequestTargetMounter#pathForTarget(org.apache.wicket.IRequestTarget)
   */
  public CharSequence pathForTarget(IRequestTarget requestTarget)
  {
    return defaultStrategy.pathForTarget(requestTarget);
  }

  /**
   * @see org.apache.wicket.request.IRequestTargetMounter#targetForRequest(org.apache.wicket.request.RequestParameters)
   */
  public IRequestTarget targetForRequest(RequestParameters requestParameters)
  {
    return defaultStrategy.targetForRequest(requestParameters);
  }

  /**
   * Returns the given url encoded.
   *
   * @param url
   *            The URL to encode
   * @return The encoded url
   */
  protected CharSequence encodeURL(final CharSequence url)
  {
    // Get the crypt implementation from the application
    ICrypt urlCrypt = Application.get().getSecuritySettings().getCryptFactory().newCrypt();
    if (urlCrypt != null)
    {
      // The url must have a query string, otherwise keep the url
      // unchanged
      final int pos = url.toString().indexOf('?');
      if (pos > -1)
      {
        // The url's path
        CharSequence urlPrefix = url.subSequence(0, pos);

        // Extract the querystring
        String queryString = url.subSequence(pos + 1, url.length()).toString();

        // if the querystring starts with a parameter like
        // "x=", than don't change the querystring as it
        // has been encoded already
        if (!queryString.startsWith("x="))
        {
          // The length of the encrypted string depends on the
          // length of the original querystring. Let's try to
          // make the querystring shorter first without loosing
          // information.
          queryString = shortenUrl(queryString).toString();

          // encrypt the query string
          String encryptedQueryString = urlCrypt.encryptUrlSafe(queryString);

          encryptedQueryString = WicketURLEncoder.QUERY_INSTANCE.encode(encryptedQueryString);

          // build the new complete url
          return new AppendingStringBuffer(urlPrefix).append("?x=").append(
            encryptedQueryString);
        }
      }
    }

    // we didn't change anything
    return url;
  }

  /**
   * Decode the "x" parameter of the querystring
   *
   * @param url
   *            The encoded URL
   * @return The decoded 'x' parameter of the querystring
   */
  protected String decodeURL(final String url)
  {
    int startIndex = url.indexOf("?x=");
    if (startIndex != -1)
    {
      try
      {
        startIndex = startIndex + 3;
        final int endIndex = url.indexOf("&", startIndex);
        String secureParam;
        if (endIndex == -1)
        {
          secureParam = url.substring(startIndex);
        }
        else
        {
          secureParam = url.substring(startIndex, endIndex);
        }

        secureParam = WicketURLDecoder.QUERY_INSTANCE.decode(secureParam);

        // Get the crypt implementation from the application
        final ICrypt urlCrypt = Application.get()
          .getSecuritySettings()
          .getCryptFactory()
          .newCrypt();

        // Decrypt the query string
        String queryString = urlCrypt.decryptUrlSafe(secureParam);

        // The querystring might have been shortened (length reduced).
        // In that case, lengthen the query string again.
        queryString = rebuildUrl(queryString);
        return queryString;
      }
      catch (Exception ex)
      {
        return onError(ex, url);
      }
    }
    return null;
  }

  /**
   * @param ex
   *
   * @return xxx
   * @deprecated Use {@link #onError(Exception, String)}
   */
  @Deprecated
  protected String onError(final Exception ex)
  {
    throw new PageExpiredException("Invalid URL", ex);
  }

  /**
   *
   * @param ex
   * @param url
   * @return error text
   */
  protected String onError(final Exception ex, String url)
  {
    log.info("Invalid URL: " + url + ", exception type: " + ex.getClass().getName() +
      ", exception message:" + ex.getMessage());
    return onError(ex);
  }

  /**
   * Try to shorten the querystring without loosing information. Note: WebRequestWithCryptedUrl
   * must implement exactly the opposite logic.
   *
   * @param queryString
   *            The original query string
   * @return The shortened querystring
   */
  protected CharSequence shortenUrl(CharSequence queryString)
  {
    queryString = Strings.replaceAll(queryString,
      WebRequestCodingStrategy.BEHAVIOR_ID_PARAMETER_NAME + "=", "1*");
    queryString = Strings.replaceAll(queryString,
      WebRequestCodingStrategy.INTERFACE_PARAMETER_NAME + "=IRedirectListener", "2*");
    queryString = Strings.replaceAll(queryString,
      WebRequestCodingStrategy.INTERFACE_PARAMETER_NAME + "=IFormSubmitListener", "3*");
    queryString = Strings.replaceAll(queryString,
      WebRequestCodingStrategy.INTERFACE_PARAMETER_NAME + "=IOnChangeListener", "4*");
    queryString = Strings.replaceAll(queryString,
      WebRequestCodingStrategy.INTERFACE_PARAMETER_NAME + "=ILinkListener", "5*");
    queryString = Strings.replaceAll(queryString,
      WebRequestCodingStrategy.INTERFACE_PARAMETER_NAME + "=", "6*");
    queryString = Strings.replaceAll(queryString,
      WebRequestCodingStrategy.BOOKMARKABLE_PAGE_PARAMETER_NAME + "=", "7*");

    // For debugging only: determine possibilities to further shorten
    // the query string
    if (log.isDebugEnabled())
    {
      // Every word with at least 3 letters
      Pattern words = Pattern.compile("\\w\\w\\w+");
      Matcher matcher = words.matcher(queryString);
      while (matcher.find())
      {
        CharSequence word = queryString.subSequence(matcher.start(), matcher.end());
        log.debug("URL pattern NOT shortened: '" + word + "' - '" + queryString + "'");
      }
    }

    return queryString;
  }

  /**
   * In case the query string has been shortened prior to encryption, than rebuild (lengthen) the
   * query string now. Note: This implementation must exactly match the reverse one implemented in
   * WebResponseWithCryptedUrl.
   *
   * @param queryString
   *            The URL's query string
   * @return The lengthened query string
   */
  protected String rebuildUrl(CharSequence queryString)
  {
    queryString = Strings.replaceAll(queryString, "1*",
      WebRequestCodingStrategy.BEHAVIOR_ID_PARAMETER_NAME + "=");
    queryString = Strings.replaceAll(queryString, "2*",
      WebRequestCodingStrategy.INTERFACE_PARAMETER_NAME + "=IRedirectListener");
    queryString = Strings.replaceAll(queryString, "3*",
      WebRequestCodingStrategy.INTERFACE_PARAMETER_NAME + "=IFormSubmitListener");
    queryString = Strings.replaceAll(queryString, "4*",
      WebRequestCodingStrategy.INTERFACE_PARAMETER_NAME + "=IOnChangeListener");
    queryString = Strings.replaceAll(queryString, "5*",
      WebRequestCodingStrategy.INTERFACE_PARAMETER_NAME + "=ILinkListener");
    queryString = Strings.replaceAll(queryString, "6*",
      WebRequestCodingStrategy.INTERFACE_PARAMETER_NAME + "=");
    queryString = Strings.replaceAll(queryString, "7*",
      WebRequestCodingStrategy.BOOKMARKABLE_PAGE_PARAMETER_NAME + "=");

    return queryString.toString();
  }

  /**
   * @see org.apache.wicket.request.IRequestCodingStrategy#rewriteStaticRelativeUrl(java.lang.String)
   */
  public String rewriteStaticRelativeUrl(String string)
  {
    return UrlUtils.rewriteToContextRelative(string, RequestCycle.get().getRequest());
  }

  /**
   * IRequestCodingStrategy.decode(Request) requires a Request parameter and not a URL. Hence,
   * based on the original URL and the decoded 'x' parameter a new Request object must be created
   * to serve the default coding strategy as input for analyzing the URL.
   */
  private static class DecodedUrlRequest extends Request
  {
    /** The original request */
    private final Request request;

    /** The new URL with the 'x' param decoded */
    private final String url;

    /**
     * The new parameter map with the 'x' param removed and the 'new' one included
     */
    private final Map parameterMap;

    /**
     * Construct.
     *
     * @param request
     * @param url
     * @param encodedParamReplacement
     */
    public DecodedUrlRequest(final Request request, final String url,
      final String encodedParamReplacement)
    {
      this.request = request;

      // Create a copy of the original parameter map
      parameterMap = this.request.getParameterMap();

      // Remove the 'x' parameter which contains ALL the encoded params
      parameterMap.remove("x");
      // first replace all &amp; with & else the they wont be encoded because there where
      // encrypted.
      String decodedParamReplacement = Strings.replaceAll(encodedParamReplacement, "&amp;",
        "&").toString();

      decodedParamReplacement = WicketURLDecoder.QUERY_INSTANCE.decode(decodedParamReplacement);

      // Add ALL of the params from the decoded 'x' param
      ValueMap params = new ValueMap();
      RequestUtils.decodeParameters(decodedParamReplacement, params);
      parameterMap.putAll(params);

      // Rebuild the URL with the 'x' param removed
      int pos1 = url.indexOf("?x=");
      if (pos1 == -1)
      {
        throw new WicketRuntimeException("Programming error: we should come here");
      }
      int pos2 = url.indexOf("&");

      AppendingStringBuffer urlBuf = new AppendingStringBuffer(url.length() +
        encodedParamReplacement.length());
      urlBuf.append(url.subSequence(0, pos1 + 1));
      urlBuf.append(encodedParamReplacement);
      if (pos2 != -1)
      {
        urlBuf.append(url.substring(pos2));
      }
      this.url = urlBuf.toString();
    }

    /**
     * Delegate to the original request
     *
     * @see org.apache.wicket.Request#getLocale()
     */
    @Override
    public Locale getLocale()
    {
      return request.getLocale();
    }

    /**
     * @see org.apache.wicket.Request#getParameter(java.lang.String)
     */
    @Override
    public String getParameter(final String key)
    {
      if (key == null)
      {
        return null;
      }

      Object val = parameterMap.get(key);
      if (val == null)
      {
        return null;
      }
      else if (val instanceof String[])
      {
        String[] arrayVal = (String[])val;
        return arrayVal.length > 0 ? arrayVal[0] : null;
      }
      else if (val instanceof String)
      {
        return (String)val;
      }
      else
      {
        // never happens, just being defensive
        return val.toString();
      }
    }

    /**
     * @see org.apache.wicket.Request#getParameterMap()
     */
    @Override
    public Map getParameterMap()
    {
      return parameterMap;
    }

    /**
     * @see org.apache.wicket.Request#getParameters(java.lang.String)
     */
    @Override
    public String[] getParameters(final String key)
    {
      if (key == null)
      {
        return null;
      }

      Object val = parameterMap.get(key);
      if (val == null)
      {
        return null;
      }
      else if (val instanceof String[])
      {
        return (String[])val;
      }
      else if (val instanceof String)
      {
        return new String[] { (String)val };
      }
      else
      {
        // never happens, just being defensive
        return new String[] { val.toString() };
      }
    }

    /**
     * @see org.apache.wicket.Request#getPath()
     */
    @Override
    public String getPath()
    {
      // Hasn't changed. We only encoded the querystring
      return request.getPath();
    }

    /**
     * @see org.apache.wicket.Request#getRelativePathPrefixToContextRoot()
     */
    @Override
    public String getRelativePathPrefixToContextRoot()
    {
      return request.getRelativePathPrefixToContextRoot();
    }

    /**
     * @see org.apache.wicket.Request#getRelativePathPrefixToWicketHandler()
     */
    @Override
    public String getRelativePathPrefixToWicketHandler()
    {
      return request.getRelativePathPrefixToWicketHandler();
    }

    /**
     * @see org.apache.wicket.Request#getURL()
     */
    @Override
    public String getURL()
    {
      return url;
    }

    /**
     * @see org.apache.wicket.Request#getQueryString()
     */
    @Override
    public String getQueryString()
    {
      return request.getQueryString();
    }
  }

  /**
   *
   */
  public class HackAttackException extends WicketRuntimeException
  {
    private static final long serialVersionUID = 1L;

    /**
     * Construct.
     *
     * @param msg
     */
    public HackAttackException(final String msg)
    {
      super(msg);
    }

    /**
     * No stack trace. We won't tell the hackers about the internals of wicket
     *
     * @see java.lang.Throwable#getStackTrace()
     */
    @Override
    public StackTraceElement[] getStackTrace()
    {
      return new StackTraceElement[0];
    }

    /**
     * No additional information. We won't tell the hackers about the internals of wicket
     *
     * @see java.lang.Throwable#toString()
     */
    @Override
    public String toString()
    {
      return getMessage();
    }
  }
}
TOP

Related Classes of org.apache.wicket.protocol.http.request.CryptedUrlWebRequestCodingStrategy$HackAttackException

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.