Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Protecting Web Applications Against Cross Site Scripting (XSS) Attacks

DZone's Guide to

Protecting Web Applications Against Cross Site Scripting (XSS) Attacks

Cross Site Scripting attacks are one of the more prominent cyberattacks on today's web. Learn how to use an OWASP tool to protect your app from XSS.

· Security Zone
Free Resource

Address your unique security needs at every stage of the software development life cycle. Brought to you in partnership with Synopsys.

Web applications are vulnerable to Cross-site scripting (XSS) attacks, where an attacker can inject malicious client-side scripts into web pages. This article shows one method that developers of web applications can use to protect their web applications against XSS attacks.

First, I will introduce a tool which can help you detect if your web application is vulnerable. OWASP Zed Attack Proxy (ZAP) is a free security tool and is easy to use. OWASP is the Open Web Application Security Project which hosts a wealth of information on making applications secure. Once you have downloaded and installed ZAP, you need to set a proxy to your application.

Open Zap, Select Tool--> Options from the menu bar and select Local Proxy, enter the address and Port.

Image title

Then, in the browser which you will be using to test your application, enter the same port and address you entered in ZAP. The following example uses Firefox.

Now, when you open your application, you should see it in ZAP under the sites tap, if you have authentication in your web application then you should login to your web application before doing the test. Once logged in, you can scan your application using the various options available. I would suggest doing a spider scan and then an active scan. Spider scans should go through the entire web application so ZAP knows about all the web pages and the active scan will do a comprehensive scan on each of the web pages. To do the scan, just right-click on your application in ZAP and select Attack. ZAP can detect many kinds of vulnerabilities in addition to XSS, however, in the remainder of the article, I will show a solution which can protect your application against XSS attacks.

As part of the solution, I will be using AntiSamy which is also supported by OWASP. AntiSamy is designed to filter malicious code from user supplied HTML/CSS, so, for example, if you have a web application which excepts HTML in the user input fields and you want to make sure that code such as  < script>”bad script”</script> does not get executed, AnitSamy works nicely. However, will show how AnitSamy can be used for any kind of input and not just input where HTML is expected.

First, you will need to download and integrate AntiSamy with you application. To use AntiSamy you will need to create a wrapper. The scan method is where you will pass in the potentially malicious string which needs to be filtered. The other important item is the policy file. This is the file which dictates what you want to filter and basically defines the level of security which you want to apply. It is highly customizable and the good thing is that once you have deployed your code but later you want to modify the level of security in you application, you can just modify the policy file and don’t have to do a new build or modify any code. There are many different policy rules defined in this file, the rule we are concerned with in this specific exercise is <tag />. This will tell AntiSamy to remove any scripts, such as < script>”bad script”</script> from the input string.

Here is the AntiSamyWrapper:

public final class AntiSamyWrapper

{

	private Policy policy;

	private CleanResults cleanResults;

	private static AntiSamyWrapper antiSamyWrapper;

	private final AntiSamy antiSamy;

	private final String policyFilePath = "";

	private final String policyFileName = "antisamy-anythinggoes.xml";

	private final Log log = LogFactory.getLog(AntiSamyWrapper.class);

	private AntiSamyWrapper()

	{

		try

		{

			InputStream policyIn = this.getClass().getResourceAsStream

			(policyFilePath + policyFileName);

			policy = Policy.getInstance(policyIn);

		}

		catch(PolicyException ex)

		{

			throw new RuntimeException(ex);

		}

		antiSamy = new AntiSamy();

	}

	/* Returns the Singleton instance of the class */

	public static AntiSamyWrapper getInstance()

	{

		if (antiSamyWrapper == null)

		{

			antiSamyWrapper = new AntiSamyWrapper();

		}

		return antiSamyWrapper;

	}

	public String scan(String inputString)

	{

		try

		{

			cleanResults = antiSamy.scan(inputString, policy);

			if (cleanResults.getNumberOfErrors() > 0)

			{

				List errors = cleanResults.getErrorMessages();

				StringBuffer errorMessage = new StringBuffer(errors.size() * 128);

				for (Object oError: errors)

				errorMessage.append(oError);

				log.error("Malacious script detected: " + errorMessage.toString());

			}

		}

		catch(ScanException ex)

		{

			throw new RuntimeException(ex)

		}

		catch(PolicyException ex)

		{

			throw new RuntimeException(ex);

		}

		return cleanResults.getCleanHTML();

	}

}

Make sure to create test cases to confirm that the wrapper works with your application.

Next, we need to create a wrapper for our HttpServletRequest:

public class AntiSamyHttpServletRequestWrapper extends HttpServletRequestWrapper

{

	private ByteArrayOutputStream cachedBytes;

	public AntiSamyHttpServletRequestWrapper(HttpServletRequest request) throws IOException

	{

		super(request);

	}

	@Override

	public ServletInputStream getInputStream() throws IOException

	{

		return new CachedServletInputStream();

	}

	@Overide

	public BufferedReader getReader() throws IOException

	{

		return new BufferedReader(new InputStreamReader(getInputStream()));

	}

	@Override

	public String[] getParameterValues(String paramName)

	{

		if (cachedBytes != null)

		String cachedString = cachedBytes.toString();

		if (cachedString.contains(paramName))

		{

			int startIndex = cachedString.indexOf(paramName) + paramName.length() + 1;

			// make sure addressLine does not match addressLine2 

			if (paramName.equals("addressForm.addressLine") &&

			startIndex == (cachedString.indexOf("addressForm.addressLine2") + paramName.length() + 1))

			{

				startIndex = cachedString.indexOf(paramName, startIndex) + paramName.length() + 1;

			}

			if (startIndex > cachedString.length())

			startIndex--;

			int endIndex = cachedString.indexOf("&", startIndex);

			String result = "";

			if (endIndex > 0)

			{

				result = cachedString.substring(startIndex, endIndex);

			}

			else

			{

				result = cachedString.substring(startIndex);

			}

			List < String > results = new ArrayList < String > ();

			results.add(result);

			return results.toArray(new String[results.size()]);

		}

	}

	return null;

}

public void setCache(String cache)

{

	try

	{

		if (cache != null)

		{

			cachedBytes = new ByteArrayOutputStream();

			cachedBytes.write(cache.getBytes());

		}

	} catch(IOException e)

	{

		e.printStackTrace();

	}

}

public class CachedServletInputStream extends ServletInputStream

{

	private final ByteArrayInputStream input;

	public CachedServletInputStream()

	{

		input = new ByteArrayInputStream(cachedBytes.toByteArray());

	}

	@Override

	public int read() throws IOException

	{

		return input.read(

	}

}

}

Two important points to note about the RequestWrapper are that we will be saving the filtered data into the cache variable and the overriding of the getParameterValues method. Note that your getParameterValues method will probably need to be modified, for example, I have a little calculation which makes sure addressline and addressline2 are not mixed up. Alternatively, if you find a more efficient way for returning the values, you can create your own implementation of the getParameterValues method. You may or may not need to override other methods in this class depending on how your application works; again, do some tests here to make sure everything works.

Finally, we need to create a filter as follows:

public class GenericFilter implements javax.servlet.Filter

{

	public FilterConfig filterConfig;

	public void doFilter(final ServletRequest request,

	final ServletResponse response,

	FilterChain chain)

	throws java.io.IOException,
	javax.servlet.ServletException

	{

		chain.doFilter(request, response);

	}

	public void init(final FilterConfig filterConfig)

	{

		this.filterConfig = filterConfig;

	}

	public void destroy()

	{

}

}

public class RequestFilter extends GenericFilter

{

	@Override

	public void doFilter(ServletRequest request,

	final ServletResponse response,

	FilterChain chain)

	throws IOException,
	ServletException

	{

		if ("POST".

		equalsIgnoreCase(((HttpServletRequest) request).getMethod()))

		{

			Map < String,
			String[] > map = request.getParameterMap();

			request =

			new AntiSamyHttpServletRequestWrapper(

			(HttpServletRequest) request);

			((AntiSamyHttpServletRequestWrapper) request).

			setCache(getParams(map));

		}

		chain.doFilter(request, response);

	}

	private String getParams(Map < String, String[] > map)

	{

		String result = ""

		for (String paramName: map.keySet())

		{

			String[] paramValues = map.get(paramName);

			for (String valueOfParam: paramValues)

			{

				result = result.concat(paramName + "=" + valueOfParam + "&");

			}

		}

		return AntiSamyWrapper.getInstance().

		scan(result.substring(0, result.length() - 1));

	}

}

The filter basically does the following:

  • Wraps the request into the wrapper.
  • Gets the parameters and the values.
  • Scans parameters and values using AntiSammy to filter out malicious scripts.
  • Saves the result into the cache.

In web.xml, we have to define the filter as follows:

<filter>
      <filter-name>RequestFilter</filter-name>

      <filter-class>ca.gc.ci.iss.antiSamy.RequestFilter</filter-class>
</filter>

<filter-mapping> 
      <filter-name>RequestFilter</filter-name>
      
        <url-pattern>/your_webpage/*</url-pattern>

</filter-mapping>

That completes the demonstration on how you can filter out malicious scripts from you application and hopefully gives you a fundamental understanding of application security.

Find out how Synopsys can help you build security and quality into your SDLC and supply chain. We offer application testing and remediation expertise, guidance for structuring a software security initiative, training, and professional services for a proactive approach to application security.

Topics:
web security ,appsec ,security ,cross site scripting ,xss

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}