fixing JForum XSS error in PM module with quotes

A member reported a XSS vulnerability in stock JForum 2.1.9. We confirmed it was a vulnerability/exposure on CodeRanch as well and fixed our fork. Luckily, it was an easy fix unlike the CSRF problems last year.

In addition to saying how to fix the issue in this post, I’m going to outline some of the other techniques JForum uses to defeat XSS.  For the actual (two character) fix, scroll down to “the fix.”

What is XSS?

XSS (Cross site scripting) is a security attack. OWASP describes in well on their XSS page. In brief, XSS injects code into a web page that runs on the target computer. The injected script code can do anything that the web page can do. Which means it can use JavaScript to steal your cookies, mount other attacks, etc. Scary stuff!

  • Reflected XSS – A reflected XSS attack targets specific users but is not stored in the database of the server with the issue. You might see a reflected XSS attack if you click on a link that takes you to the page. Others going to the page normally wouldn’t see the issue.
  • Persistent XSS – A persistent XSS attack gets the attack code stored in the database of the server with the issue. It could still target a specific user (in the case of the private message issue reported here.) Or it could target all users – even non logged in users – if the same attack was made in a post instead of a private message.  I was able to reproduce this problem in posts as well.

Both types of XSS attack are bad and up to the website to prevent. So how does Jforum 2.1.X protect against XSS attacks?

Approach #1 – Use Freemarker HTML escape sequence

JForum uses Freemarker as the view technology. Freemarker allows you to specify that all HTML should be escaped. This means attacks that reply on outputting HTML characters like < (tags) or ” (attributes) will be prevented.  Instead the raw characters of &lt; and &quot; will be output instead. Which the browser will not run. As an example of this technique, the code writes:


${post.subject?default("")?html}

Approach #2 – Escape characters in Java

Approach #1 is very powerful, but it has a limitation. Forum posts typically contain HTML code. For example, you write code in a special format, bold posts, etc. JForum uses Java code to do a search and replace on the special characters in text before adding the HTML formatting. Since the Freemarker view has to be able to render the HTML formatting, it can’t use approach #1. See an example of just one of these transformations:

ViewCommon.replaceAll(text, "<", "&lt;");

This approach is not foolproof because it relies on a blacklist of “not allowed” characters and hackers are creative. But it is really hard to come up with a whitelist of allowed characters in forum posts. And worse, the characters used in attacks are ones that are used in normal writing.

Approach #3 – Limit raw HTML

While JForum does allow HTML in posts, it only allows a limited set of tags and attributes. This one does use a whitelist with code like:


private static Set welcomeTags;

private static Set welcomeAttributes;

Approach #4 – Use BB code instead of HTML

The forum also allows use of BB (bulletin board) codes. This lets you write [b] instead of <b>. If the user isn’t entering HTML, the chance of a problem is lower.

The actual problem here

The XSS vulnerability reported was caused by the interaction between approach #2 and approach #4.

Approach #2 guarantees the quotes are safe with


ViewCommon.replaceAll(tmp, "\"", "&quot;");

Approach #4 contains the following BB mapping code in bb_config.xml


<!-- COLOR -->

<match name="color" removeQuotes="true">

<regex>(?s)(?i)\[color=['"]?(.*?[^'"])['"]?\](.*?)\[/color\]</regex>

<replace>

<![CDATA[

<font color='$1'>$2</font>

]]>

</replace>

</match>

This is a problem because the replace uses single quotes instead of double quotes. The system doesn’t escape single quotes. Allowing all manners of code to be injected in the color attribute.

The fix

Luckily, there is an easy fix. Just change this one line of code in bb_config.xml to:


<font color="$1">$2</font>

I’ve tested and this does in fact solve the problem.

For more learning about XSS

If you want to learn more about XSS, I recommend reading the OWASP cheat sheet.  In particular, notice that you need to escape the code differently depending on whether you are looking at HTML or JavaScript injection. In our case, it was HTML injection because the injection was occurring as a textual HTML attribute. If it was in <script> tag or JavaScript event handler, we’d need to call a JavaScript encoding library. Also, you can learn about DOM based XSS attacks.

using spring RestTemplate and 2 factor authenication to add issues to github

Last month, I figured out how to use Spring’s RestTemplate to add issues to github programmatically figuring it would save me time in the future.  Well, the future is here.  I needed to add 16 issues (one per milestone.)  I run my program and get a 401.  In particular, I got

Apr 20, 2014 6:42:18 PM org.springframework.web.client.RestTemplate handleResponseError

WARNING: GET request for "https://api.github.com/repos/boyarsky/repoName/issues" resulted in 401 (Unauthorized); invoking error handler

Exception in thread "main" org.springframework.web.client.HttpClientErrorException: 401 Unauthorized

at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91)

at org.springframework.web.client.RestTemplate.handleResponseError(RestTemplate.java:588)

at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:546)

at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:502)

at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:444)

at jb.Trial2.main(Trial2.java:29)

Right.  I’ve changed my password AND enabled two factor authentication on github in the past month.  Luckily, it was easy to switch my program to two factor.  Here’s what I did.

Create personal use token

I created a second personal use token just like I did for for my git commmand line use by going to the applications page and clicking “generate new token”.  I chose to create a separate token so I could revoke access as soon as I’m done running the web service.

Testing the token

Per the getting started with oauth doc, I ran a command line to test the token:

curl -i -H 'Authorization: token notMy40CharToken' \

https://api.github.com/user

 Wrote another trial program

I adapted my second trial program from the initial iterations to test out two factor.

package jb;

import org.springframework.http.*;
import org.springframework.web.client.*;

/**
 * Call a web service that uses authenticated user to test passing credentials
 *
 * @author jeanne
 *
 */
public class Trial3 {

	public static void main(String[] args) {
		String url = "https://api.github.com/repos/boyarsky/oca-ocp-book/issues";

		HttpHeaders headers = new HttpHeaders();

		String personalToken = "notMy40CharToken";
		headers.add("Authorization", "token " + personalToken);

		// ignore result because just testing connectivity
		HttpEntity<String> request = new HttpEntity<String>(headers);
		RestTemplate template = new RestTemplate();
		template.exchange(url, HttpMethod.GET, request, Object[].class);
		System.out.println("success");
	}

}

I then replaced the authentication part of my real program and it worked like a charm.  Even with the changes to the program, it was faster than creating 16 issues by hand with the proper text/assignee/milestone.

Delete the personal token

I deleted the token to ensure it never gets used again.  I don’t want to run the program by accident with my credentials.  Or accidentally post the token here.

using spring RestTemplate to add issues to github

I’m using github as a project management tool for a (non-work) project.  That’s been working well for being able to track things and have it close to the project artifacts.  Milestones have dates (to remember what was committed to) and issues correspond to tasks or follow up items within the milestone.  I reached a point where I needed to add the same three tasks to over 15 milestones.  (And possibly more in the future.)

It probably would have taken an hour to do this manually.  It’s a boring task so I imagine I’d have gotten distracted.  The boredom of even contemplating the task plus the possibility that I might have to do again make me realize I should spend a little longer and write code.  Which meant the prep time was a little over an hour but the time to run the program was less than a minute.

I worked in iterations because I wanted to learn early on if this was feasible (or a time sink.)

Iteration 1 – setup a project and call a github web service

I used Maven because I knew I’d need a couple libraries and dependencies.  In Eclipse, I created a new Maven project with

  • group id = jb
  • artifact = github-issue-creator

I then added one dependency.  I knew I’d need Spring Web because I wanted to use RestTemplate.  At this point, my pom.xml looks like

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>jb</groupId>
  <artifactId>github-issue-creator</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>4.0.2.RELEASE</version>
    </dependency>
  </dependencies>
</project>

It was then time to actually call RestTemplate.  I quickly reviewed the docs to confirm my memory of the flow.  Then I wrote the simplest program I could that would call a Github web service.  I had learned from the GitHub API docs of the Octocat Hello World test repository.   I confirmed the issues query web service command in the GitHub docs and went to https://api.github.com/repos/octocat/Hello-World/issues in a browser.  Perfect – I get JSON back.

I then wrote the RestTemplate code using this URL.

package jb;

import org.springframework.web.client.*;

/**
 * View a public repo's issue via a web service on github to make sure can
 * connect. This particular repo is referenced int he documentation.
 *
 * @author jeanne
 *
 */
public class Trial1 {

	public static void main(String[] args) {
		String url = "https://api.github.com/repos/octocat/Hello-World/issues";

		RestTemplate template = new RestTemplate();

		// ignore result because just testing connectivity
		template.getForObject(url, Object[].class);
		System.out.println("success");
	}

}

You’ll notice I’m telling Spring to return an array of Objects.  I don’t need a more specific class since I’m ignoring the result.  It’s just a proof of concept – my real code will do a POST and own’t care about getting the data back.  I knew an array was needed because the JSON in the browser returned a list.  If you do need to deal with the returned values, look at this Jackson example with Spring and RestTemplate.

Iteration 2 – use my credentials to access a github web service

So far so good.  I have two more steps left to accomplish my goal.  I can either test passing credentials or try a POST.  I decided to test passing credentials first so I would be doing harmless GET operations during the testing.

I’ve only used basic authentication with RestTemplate once so I had to look up what to do.  This blog post was helpful showing how to use the Apache Commons Codec library to deal with credentials.

First, I had to add this dependency to my POM.

<dependency>
  <groupId>commons-codec</groupId>
  <artifactId>commons-codec</artifactId>
  <version>1.9</version>
</dependency>

This code obtains all the issues for myRepoName.  And no, that isn’t my GitHub password.  It isn’t my repository name for that matter.

package jb;

import org.apache.commons.codec.binary.*;
import org.springframework.http.*;
import org.springframework.web.client.*;

/**
 * Call a web service that uses authenticated user to test passing credentials
 *
 * @author jeanne
 *
 */
public class Trial2 {

	public static void main(String[] args) {
		String url = "https://api.github.com/repos/boyarsky/myRepoName/issues";

		String plainCreds = "boyarsky:notMyGitHubPassword";
		byte[] plainCredsBytes = plainCreds.getBytes();
		byte[] base64CredsBytes = Base64.encodeBase64(plainCredsBytes);
		String base64Creds = new String(base64CredsBytes);

		HttpHeaders headers = new HttpHeaders();
		headers.add("Authorization", "Basic " + base64Creds);

		// ignore result because just testing connectivity
		HttpEntity<String> request = new HttpEntity<String>(headers);
		RestTemplate template = new RestTemplate();
		template.exchange(url, HttpMethod.GET, request, Object[].class);
		System.out.println("success");
	}

}

As you can see, I encode the bytes of my username/password combination. I then set that as the basic authentication header. I had to switch to “exchange” instead of “getForObject” as noted in that blog post for this to work.

Iteration 3 – actually creating an issue

There are only four differences between this and iteration 2 from a web services perspective.

  1. From the github create issue docs, I learned the POST URL is/repos/:owner/:repo/issues.
    private static final String URL = “https://api.github.com/repos/boyarsky/myRepoName/issues”;
  2. Use HttpMethod.POST instead of HttpMethod.GET.  I actually wasted some time with this step.  I forgot to change it so the program was running without error and returning  issues rather than setting them.
  3. Pass in the JSON for the values you want to set in the issue.
  4. Now I tell Spring to excpect an Object instead of an array because the JSON returned is no longer an array.
String json = "{\"title\": \"" + title + "\", \"assignee\": \""
				+ assignee + "\", \"milestone\": " + milestone + "}";

		// ignore result because just testing connectivity
		HttpEntity<String> request = new HttpEntity<String>(json, headers);
		RestTemplate template = new RestTemplate();
		template.exchange(URL, HttpMethod.POST, request, Object.class);

I tested this once and it worked. I then added the logic to get the proper values (it is formulaic) and let it fly.

Getting the milestone id was easy – it is in the URL: https://github.com/boyarsky/myRepoName/issues?milestone=4&state=open

Conclusion
While it would have been faster to add all the issues by hand, this was certainly more interesting and more fun. And the next time I need to add an issue to each milestone, it will be trivial. Thanks github for the wonderful documentation!

 

edit: if you need to do this for two factor authentication, see my updated post