*** WiscMail warning: attached executable file was renamed ***
* 
* Attached to this message is an executable file or an archive
* that contains an executable file.  The attachment has been
* renamed for your protection.
*
* The file was scanned by the WiscMail anti-virus scanners and
* no threat was found.  However, executable files should be
* treated with great caution.  It's possible for viruses to
* slip through undetected in the minutes before anti-virus
* definitions are published.
*
* Please confirm with the sender that the file was sent
* intentionally, and that the file is safe to run before
* renaming the file by removing the "_renamed" from the end of
* the filename. 
*
* For more information and support, please visit the following
* link to the DoIT Help Desk
*
* http://kb.wisc.edu/wiscmail/page.php?id=6056
*
*** end of warning ***

Security Notification: XSS Vulnerability Proxy Exploit

This is a public notification of an identified uPortal security
vulnerability and workaround.

Summary:
uPortal ships with a proxy configuration that allows illicit
cross-site scripting. The Adversary can introduce arbitrary JavaScript
into a user's portal render cycle if he or she can get the end user's
web browser (e.g. via a hyperlink or a redirect) to open a cleverly
crafted portal URL. The kinds of things the Adversary could accomplish
with this JavaScript include capture of the user's otherwise secure
session cookie, thereby allowing session hijacking.

Versions Affected
All versions prior to uPortal 2.6.0, including 2.1, 2.2, 2.3, 2.4, 2.5.

Issue:
HttpProxyServlet:

uPortal ships with a proxy servlet allowing it to proxy over SSL
content that would otherwise be vended in the clear so that the
annoying "Mixed content" browser advisory message can be avoided.

While some (many?) uPortal deployments are not intentionally making
productive use of this servlet, its default configuration is to be
nonetheless latently available and so available for exploit using a
cleverly crafted URL. You can turn off the feature of proxying
resources via the portal.properties property
"org.jasig.portal.serialize.ProxyWriter.resource_proxy_enabled" but
this will not turn off the vulnerability. When this feature is
deliberately used, its use involves detecting URLs needing to be
re-written via a SAX filter and re-writing them to point at the proxy
servlet which then proxies the originally intended content.


CWebProxy:
The web proxy channel's proxying of URLs specified at runtime is
already throttled by configuration parameters added to address a
previous security vulnerability (that of illicitly proxying local
files). Portal administrators should configure each web proxy channel
instance as restrictively as possible, such that only trusted content
is included within the scope of allowable proxies. Web proxy channels
configured such that they will proxy arbitrary content provided by the
Adversary can in theory be exploited to execute cross-site-scripting
attacks.

Resolution:
Http proxy servlet

The uPortal Http proxy servlet will no longer proxy any content other
than elements with a mime type of image. Two changes are involved:

1. org.jasig.portal.serialize.ProxyWriter:

Summary:
Applets, IFrames and Scripts are no longer rewritten by the serializer
to use the proxy servlet.

Description:
When uPortal serializes its response to the browser it passes the
response through a filter (org.jasig.portal.serialize.ProxyWriter)
which inserts additional URL path elements as configured by the
property, org.jasig.portal.serialize.ProxyWriter.resource_proxy_rewrite_prefix.
 In the past the ProxyWriter class operated on 6 elements:
"image","img","script","input","applet","iframe". The patched version
will operate on three element types only: image, img and input.
Although it may be convenient to proxy other elements to avoid the
mixed content message, this vulnerability response opts for a simpler
approach of avoiding entirely these other types of content proxying.

This change comports the proxy writer behavior with the
now-more-restricted scope of the HttpProxy servlet -- this change to
ProxyWriter does not itself secure anything, as the nature of the
exploit is to generate proxy servlet instructions by means other than
the proxy writer.

2. org.jasig.portal.HttpProxyServlet

Summary:
HttpProxyServlet will respond with an error code (404) and no content
for all objects that do not have a MIME type that begins with:
"image".

Description:
This internal proxy handles requests for the proxied embedded content
as the browser renders the response. Previously, the proxy servlet
would proxy most anything. With this change, the proxy servlet will
proxy only images. Returning only elements with proper mime type of
image the browser is able to protect itself from illicitly proxied
scripts -- in order for the servlet to proxy, the content must appear
to have a mime type of "image", which will discourage the web browser
from executing the content.


Alternative solutions for resource proxy:

Deployments not using the HttpProxyServlet should remove the class
file (in WEB-INF/classes) and servlet declaration (in web.xml) from
their portals entirely as an unnecessary security vulnerability
surface.

uPortal deployers should consider running the proxy on a host other
than the portal or other application host, such that if the proxy is
exploited to proxy JavaScript, the JS will appear to be coming from an
unrelated domain and so in-brower cross-site-scripting protections
will apply.

uPortal deployers should consider running external standalone web
proxy solutions, such as Squid. External dedicated proxy solutions
have their own security issues and configuration needs, but these tend
to be well documented and worked through by a larger community
dedicated to the problems of proxying.

uPortal 2.6.0 GA ships with a fix similar to the attached patches,
pre-applied. An alternative to patching an existing install to deal
with this issue is therefore a full upgrade to uPortal 2.6.

Configuration to block the exploit in Web Proxy and CGenericXSLT channels

The uPortal Web Proxy and CGenericXSLT channel includes features for
restricting the URLs a channel instance will proxy, as implemented in
a previous security fix. uPortal deployments must configure all web
proxy and CGenericXSLT channel publications with a maximally
restrictive match prefix. For example, if a web proxy instance proxies
a remote application at

http://www.myschool.edu/apps/dining_hall_signup/startpage.html

that links to other parallel paths like

http://www.myschool.edu/apps/dining_hall_signup/pick_a_plan.html

but not like

http://www.myschool.edu/other_cool_stuff/dining_menus.html

An appropriate URI match prefix would be:

"http://www.myschool.edu/apps/dining_hall_signup/";

This would prevent the channel from proxying content at

http://www.bad.com/exploitive_javascript_bearing_page

even if a user were to be convinced by the Adversary to click an
external link illicitly instructing the channel to proxy that URL.

Patching:

Source Replacement
Replace HttpProxyServlet.java and ProxyWriter.java in your local
uPortal source tree with the attached files. Rebuild and re-deploy
your uPortal. Rebuilding your uPortal class files with this updated
source is the recommended way to address this exploit in your local
uPortal environment.

Binary Replacement
Replace HttpProxyServlet.class and ProxyWriter.class with the attached
.class files intended for use in uPortal 2.5.x. When using this
approach, you'll need to also fix the issue in your local build
environment (e.g. by replacing the .java files as described
previously) so that when you perform a new build, the exploit remains
fixed.

Previous patch
If you applied a previous patch for this issue, you will first need to
undo the steps you performed in applying that patch before applying
these steps and this patch.

-- 
You are currently subscribed to [email protected] as: [EMAIL 
PROTECTED]
To unsubscribe, change settings or access archives, see 
http://www.ja-sig.org/wiki/display/JSG/uportal-dev

Attachment: HttpProxyServlet.class_renamed
Description: Binary data

/* Copyright 2003 The JA-SIG Collaborative.  All rights reserved.
*  See license distributed with this file and
*  available online at http://www.uportal.org/license.html
*/

package org.jasig.portal;

import java.io.*;
import java.net.*;
import java.util.StringTokenizer;

import javax.servlet.*;
import javax.servlet.http.*;

import org.jasig.portal.properties.PropertiesManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Proxy embedded content such as images for portal sessions.
 * When portal is running over ssl, HttpProxyServlet can be used
 * to deliver insecure content such as images over ssl to avoid 
 * mixed content in the browser window.
 * 
 * @author Drew Mazurek ([EMAIL PROTECTED])
 * @author Susan Bramhall ([EMAIL PROTECTED])
 * @version 1.0
 * @since uPortal 2.2
 */
public class HttpProxyServlet extends HttpServlet {

    private static final Log log = LogFactory.getLog(HttpProxyServlet.class);
    
/**
 * Returns content retreived from location following context (Path Info)
 * If no content found returns 404
 */
    public void doGet(HttpServletRequest request, HttpServletResponse response)
	throws ServletException, IOException {

	// check referrer property - return 404 if incorrect.
	final String checkReferer = PropertiesManager.getProperty(HttpProxyServlet.class.getName()
		+ ".checkReferer", null);
	
	String target;
	
	// if checking referer then only supply proxied content for specific referer
	// Ensures requests come from pages in the portal
	if (null!=checkReferer && !checkReferer.equals("")){
		StringTokenizer  checkedReferers = new StringTokenizer (checkReferer, " ");
		boolean refOK = false;
		String referer = request.getHeader("Referer");
		
        if (log.isDebugEnabled())
            log.debug("HttpProxyServlet: HTTP Referer: " + referer);
        if (null!=referer) {
            while (checkedReferers.hasMoreTokens()) {
                String goodRef = checkedReferers.nextToken();
                if (log.isDebugEnabled()) 
                    log.debug("HttpProxyServlet: checking for "+goodRef);
                if (referer.startsWith(goodRef)) {
                    refOK = true;
                    if (log.isDebugEnabled()) 
                        log.debug("HttpProxyServlet: referer accepted "+goodRef);
                    break;
                }
            }
    		if (!refOK) {
            if (log.isWarnEnabled())
    			    log.warn("HttpProxyServlet: bad Referer: " + referer);
    			response.setStatus(404);
    			return;
    		}
            
        }
	}
	
	if (request.getSession(false)==null) {
		if (log.isWarnEnabled())
		    log.warn("HttpProxyServlet: no session");
		response.setStatus(404);
		return;		
	}
	
	// pathinfo is "/host/url"
	if(request.getPathInfo() != null && !request.getPathInfo().equals("")) {
	    target = "http:/" + request.getPathInfo();
	    String qs = request.getQueryString();
	    if (qs != null)
	        target +="?"+request.getQueryString(); 	    
	} else {
		response.setStatus(404);
		log.warn("HttpProxyServlet: getPathInfo is empty");
	    return;
	}

	InputStream is = null;
    ServletOutputStream out = null;
    
    try {
        URL url = new URL(target);
        URLConnection uc = url.openConnection();
        final String contentType = uc.getContentType();
        log.debug("httpProxyServlet examining element with content type = "+contentType);
        if (!contentType.startsWith("image")){
            response.setStatus(404);
            log.info("httpProxyServlet returning response 404 after receiving element with contentType ="+contentType);
        }
        
        response.setContentType(contentType);
        
        is = uc.getInputStream();
    
	    out = response.getOutputStream();

	byte[] buf = new byte[4096];
	int bytesRead;
	while ((bytesRead = is.read(buf)) != -1) {
	    out.write(buf, 0, bytesRead);
	}
    } catch (MalformedURLException e) {
		response.setStatus(404);
		log.warn("HttpProxyServlet: target="+target,e);
		
    } catch (IOException e) {
		response.setStatus(404);
		log.warn(e,e);
		
    } finally {
        if(is != null)
        is.close();
        if(out != null)
        out.close();
    }
    }
}

Attachment: ProxyWriter.class_renamed
Description: Binary data

/* Copyright 2004 The JA-SIG Collaborative.  All rights reserved.
*  See license distributed with this file and
*  available online at http://www.uportal.org/license.html
*/

package org.jasig.portal.serialize;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;

import org.jasig.portal.properties.PropertiesManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.portal.utils.AddressTester;
import org.jasig.portal.utils.CommonUtils;

/**
 * This Class allows appending PROXY_REWRITE_PREFIX String in front of all the references to images, javascript files, etc..
 * that are on a remote location. This allows the browser while portal is running in https assume that these resources
 * are secure resources(are referenced by https rather than http). This is because the resource URI insteadof
 * http://www.abc.com/image.gif will be rewriten as as https://[portal address]/PROXY_REWRITE_PREFIX/www.abc.com/image.gif
 * This class does the proxy rewrite in the following exceptional situations as well:
 * 1. If the return code poting to the image is 3XX (the image refrence, refrences is a mapping to a diffrent location)
 *    In this case the final destination iddress n which the image or the resource is located is e
 *    and then the rewrite points to this location.
 * 2. If the content of a channel is an include javascript file the file is rewriten to a location on a local virtual host
 *    and at the same time the image or other resources references are rewritten.
 *
 *
 * @author <a href="mailto:[EMAIL PROTECTED]">Kazem Naderi</a>
 * @version $Revision: 36721 $
 * @since uPortal 2.2
 */

public class ProxyWriter {

    private static final Log log = LogFactory.getLog(ProxyWriter.class);
    
    /**
     * True if allow rewriting certain elements for proxying.
     */
    protected boolean _proxying;

    /**
     * The list of elemnets which src attribute is rewritten with proxy.
     */
    // private static final String[] _proxiableElements = {"image","img","script","input","applet","iframe"};
    private static final String[] _proxiableElements = {"image","img","input"};

    /*
     * If enabled the references to images or any external browser loadable resources will be proxied.
     */
    private static boolean PROXY_ENABLED=PropertiesManager.getPropertyAsBoolean("org.jasig.portal.serialize.ProxyWriter.resource_proxy_enabled");

   /*
    * The URI of location on virtual host on the same server as portal. This URI is used for rewriting proxied files.
    */
    private static String PROXIED_FILES_URI=PropertiesManager.getProperty("org.jasig.portal.serialize.ProxyWriter.proxy_files_uri");

   /*
    * The path of location on virtual host on the same server as portal. This path is used for rewriting proxied files.
    */
    private static String PROXIED_FILES_PATH=PropertiesManager.getProperty("org.jasig.portal.serialize.ProxyWriter.proxy_files_path");

    /*
     * The prefix used for proxying
     */
    private static final String PROXY_REWRITE_PREFIX=PropertiesManager.getProperty("org.jasig.portal.serialize.ProxyWriter.resource_proxy_rewrite_prefix");

    /*
     * The local domain that does not do redirection
     */
    private static final String PROXY_REWRITE_NO_REDIRECT_DOMAIN = PropertiesManager.
      getProperty("org.jasig.portal.serialize.ProxyWriter.no_redirect_domain");

    /**
     * Examines whther or not the proxying should be done and if so handles differnt situations by delegating
     * the rewrite to other methods n the class.
     * @param name
     * @param localName
     * @param value
     * @return value
     */
    protected static String considerProxyRewrite(String name, String localName, String value)
    {
        if (PROXY_ENABLED && (name.equalsIgnoreCase("src") || name.equalsIgnoreCase("archive")) &&
            value.indexOf("http://";)!=-1)
        {

            //capture any resource redirect and set the value to the real address while proxying it
            value = capture3XXCodes(value);

            //if there is a script element with a src attribute the src should be rewriten
            if(localName.equalsIgnoreCase("script")){
                value = reWrite(value);
                return value;
            }

            //handle normal proxies
            for (int i=0; i<_proxiableElements.length; i++)
            {
                if (localName.equalsIgnoreCase(_proxiableElements[i]))
                {
                    value = PROXY_REWRITE_PREFIX + value.substring(7);
                    break;
                }
            }
        }
        return value;
    }


   /**
    * Capture 3xx return codes - specifically, if 301/302, then go tp the
    * redirected url - note, this in turn may also be redirected.
    * Note - do as little network connecting as possible. So as a start, assume
    * "ubc.ca" domain images will not be redirected so skip these ones.
    * @param value
    * @return value
    */
    private static String capture3XXCodes(String value){
        try{
            String skip_protocol = value.substring(7);
            String domain_only = skip_protocol.substring(0,skip_protocol.indexOf("/"));
            String work_value = value;
            if (PROXY_REWRITE_NO_REDIRECT_DOMAIN.length() > 0 && !domain_only.endsWith(PROXY_REWRITE_NO_REDIRECT_DOMAIN)) {
              while (true) {
                AddressTester tester = new AddressTester(work_value, true);
                try {
                  int responseCode = tester.getResponseCode();
                  if (responseCode != HttpURLConnection.HTTP_MOVED_PERM &&
                      responseCode != HttpURLConnection.HTTP_MOVED_TEMP) {
                    log.debug(
                      "ProxyWriter::capture3XXCodes(): could not get deeper in getting the image.");
                    return work_value;
                  }

                  /* At this point we will have a redirect directive */

                  HttpURLConnection httpUrlConnect = (HttpURLConnection) tester.getConnection();
                  httpUrlConnect.connect();

                  work_value = httpUrlConnect.getHeaderField("Location"); ;
                } finally {
                  tester.disconnect();
                }
              }
            }

            return value;

         } catch(Exception e) {
             log.error("Failed to rewrite the value", e);
             return value;
         }
    }

    /**
     * This method rewrites include javascript files and replaces the refrences in these files
     * to images' sources to use proxy.
     * @param scriptUri: The string representing the address of script
     * @return value: The new address of the script file which image sources have been rewritten
     */
     private static String  reWrite(String scriptUri){
        String filePath = null;
        String fileName = null;
        try{
            fileName = fileNameGenerator(scriptUri);
            filePath = PROXIED_FILES_PATH + fileName;
            File outputFile = new File(filePath);
            if (!outputFile.exists() || (System.currentTimeMillis()-outputFile.lastModified() > 1800 * 1000))
            {
              try{
                AddressTester tester = new AddressTester(scriptUri);
                try {
                  if (!tester.URLAvailable()){
                    log.error("ProxyWriter::rewrite(): The adress " + scriptUri + " is not available. ");
                    return scriptUri;
                  }
                  HttpURLConnection httpUrlConnect = (HttpURLConnection) tester.getConnection();
                  httpUrlConnect.connect();
                  BufferedReader in = new BufferedReader(new InputStreamReader(httpUrlConnect.getInputStream()));
                  try {
                    FileWriter out = new FileWriter(outputFile);
                    try {
                      String line;
                      while ((line = in.readLine()) != null) {
                        out.write(processLine(line) + "\t\n");
                      }
                    } finally {
                      out.close();
                    }
                  } finally {
                    in.close();
                  }
                } finally {
                  tester.disconnect();
                }
              } catch(Exception e) {
                 log.error(
                                "ProxyWriter::rewrite():Failed to rewrite the file for: " +
                                scriptUri, e);
                 outputFile.delete();
                 return scriptUri;
               } //end catch
             }
             String newScriptPath = PROXIED_FILES_URI + fileName;
             AddressTester tester = new AddressTester(newScriptPath);
             try {
               if (!tester.URLAvailable()) {
                 log.error(
                                "ProxyWriter::rewrite(): The file  " + filePath +
                                " is written but cannot be reached at " +
                                newScriptPath);
                 return scriptUri;
               } else {
                 return PROXY_REWRITE_PREFIX + PROXIED_FILES_URI.substring(7) +
                   fileName;
               }
             } finally {
               tester.disconnect();
             }

         } catch(Exception e) {
             log.error("Failed to read the file at : "  + filePath, e);
             return scriptUri;
          }
    }

   /**
    * This method uses a URI and creates an html file name by simply omiting some characters from the URI.
    * The purpose of using the address for the file name is that the file names will be unique and map to addresses.
    * @param addr: is the address of the file
    * @newName: is the name built form the address
    */
    private static String fileNameGenerator(String addr)
    {
        String newName = CommonUtils.replaceText(addr, "/", "");
        newName = CommonUtils.replaceText(newName, "http:", "");
        newName = CommonUtils.replaceText(newName, "www.", "");
        newName = CommonUtils.replaceText(newName, ".", "");
        newName = CommonUtils.replaceText(newName, "?", "");
        newName = CommonUtils.replaceText(newName, "&", "");

        return newName.substring(0, Math.min(16, newName.length())) + ".html";
    }


   /**
    * This method parses a line recursivley and replaces all occurances of image references
    * with a proxied reference.
    * @param line - is the portion of the line or the whole line to be processed.
    * @return line - is the portion of the line or the line that has been processed.
    */
    private static String processLine(String line) throws Exception
    {
      try {
        if (line.indexOf(" src") != -1 && line.indexOf("http://";) != -1) {
          String srcValue = extractURL(line);
          String srcNewValue = createProxyURL(srcValue);
          line = CommonUtils.replaceText(line, srcValue, srcNewValue);
          int firstPartIndex = line.lastIndexOf(srcNewValue) + srcNewValue.length();
          String remaining = line.substring(firstPartIndex);
          return line.substring(0,firstPartIndex) + "  " + processLine(remaining);
        } else {
          return line;
        }
      } catch(Exception e) {

        log.error("Failed to process a line : "  + line, e);
        throw e;
       }
    }

    /**
     *
     * This method takes a String (line) and parses out the value of  src attribute
     * in that string.
     * @param line - String
     * @return srcValue - String
     */
    private static String extractURL(String line)
    {
      int URLStartIndex = 0;
      int URLEndIndex = 0;
      //need this to make sure only image paths are pointed to and not href.
      int srcIndex = line.indexOf(" src");
      if (line.indexOf("https://";, srcIndex) != -1) {
        return "";
      }
      if (line.indexOf("http://";, srcIndex) != -1) {
        URLStartIndex = line.indexOf("http", srcIndex);
      } else {
        return "";
      }

      URLEndIndex = line.indexOf(" ", URLStartIndex);
      String srcValue = line.substring(URLStartIndex, URLEndIndex);
      return srcValue;
    }

    /**
     *
     * This method receives an image source URL and modified
     * it to be proxied.
     * @param srcValue - String
     * @return srcNewValue - String
     */
    private static String createProxyURL(String srcValue)
    {
      String srcNewValue = "";
      if (srcValue.indexOf("https://";) != -1) {
        return srcValue;
      } else if (srcValue.indexOf("http://";) != -1) {
        srcNewValue = CommonUtils.replaceText(srcValue, "http://";,
                                              PROXY_REWRITE_PREFIX);
      }  else {
        srcNewValue = "";
      }
      return srcNewValue;
    }

}

Reply via email to