This page contains some extra notes, resources, links, and other information that didn't fit into a 40-minute presentation.

Getting Started

Download the slide deck.

Download the example plugin - bear in mind that this is deliberately bad code for demonstration purposes, please do not copy it for your plugin!

Cross-Site Scripting (XSS) Vulnerabilities

  • In these examples, the Persisted XSS is inserted into an HTML context and the Reflected XSS is inserted into a JavaScript context. These could easily be the other way around.

Persisted XSS Demo

To reproduce the persisted XSS demo using the example JIRA plugin:
1) Change the current user's full name to "<script>alert('xss')</script>" in the profile page.
2) Perform an issue transition (such as closing or reopening an issue).
3) Click on the Workflow tab on the View Issue page for that issue.

The cause of this vulnerability is the unescaped $userDisplayName in workflow-entry.vm:

<div class="issue-data-block activity-comment action-details flooded">
$userDisplayName moved from <B>$sourceStatus</B> to <B>$destStatus</B> on <span class=date>$date</span>

As this is in a pure HTML context (no JavaScript), fix by HTML-encoding the values:

<div class="issue-data-block activity-comment action-details flooded">
${textUtils.htmlEncode($userDisplayName)} moved from <B>${textUtils.htmlEncode($sourceStatus)}</B> to <B>${textUtils.htmlEncode($destStatus)}</B> on <span class=date>$date</span>

Reflected XSS Demo

1) Still on the View Issue page, select "Only My Transitions" from the dropdown on the Workflow tab.
2) In the URL, change "showAll=false" to "showAll=false;alert(document.cookie)"

The cause of this vulnerability is the insertion of $showAll into a JavaScript block in workflow-header.vm:

<script type="text/javascript">
var loading = true;
var workflowSelect = document.getElementById ("workflow-show");
var showAll = $showAll;
if (showAll)
workflowSelect.selectedIndex = 0;

To fix, first move the value into an HTML context and escape it:

 <ul id="params" style="display:none">
<li id="showAll">$textutils.htmlEncode($showAll)</li>

Then replace the dangerous JavaScript line with:

 var showAll = document.getElementById("showAll").firstChild.nodeValue;

Restricting the data type of showAll in to a boolean would also fix the vulnerability.

HTML Encoding

  • HTML-encoding can be done from Java using com.opensymphony.util.TextUtils.htmlEncode (...) but it is highly recommended to generate your HTML using template files instead.
  • The syntax for HTML-encoding in a template file varies by app:
    • JIRA Velocity Templates - $textutils.htmlEncode (...)
    • Confluence Velocity Templates - $generalUtil.htmlEncode (...)
    • Bamboo Freemarker Templates - ${...?html}
    • FishEye/Crucible doesn't supply a built-in function for this purpose. A good library to import is StringEscapeUtils from Apache.

Confluence Anti-XSS

Please see the Anti-XSS Documentation (particularly the child page, Enabling XSS Protection in Pluginsection in Plugins) for the full documentation on how to use Anti-XSS in Confluence.

Cross-Site Request Forgery (XSRF) Vulnerabilities

To reproduce the XSRF example in the JIRA plugin:
1) Go to JIRA Administration and click the "Workflow Tab Settings" link in "Global Settings".
2) Enter a value into the imageUrl field such as "foo" and click Update. This submits the form as a POST request.
3) We can also submit the same form with a GET request. e.g. getting the admin's browser to request WorkflowEditConfig.jspa?imageUrl=bar will change the stored value to "bar".

  • Although this example is vulnerable to an XSRF attack via a GET request, POSTs are also vulnerable - they just take a little more work to exploit.

The code for this action is in There is nothing in particular that causes this vulnerability - everything is vulnerable by default.

To fix, add the @RequiresXsrfCheck annotation to doExecute()
protected String doExecute () throws Exception {
if (!getPermissionManager().hasPermission(Permissions.ADMINISTER, getRemoteUser())) {
workflowSettings.setValue(IMAGE_URL_KEY, request.getParameter(IMAGE_URL_KEY));
return showForm();
  • Be sure to add it to doExecute() and not doDefault(). This is because in this example, doDefault() is not an action that needs protection - all it does is display the form. Protecting doDefault() would inconvenience the users with no security gain.

Then add the atl_token to the form itself:

<td class="jiraformbody">
<td class="jiraformbody">
<input type="hidden" name="atl_token" value="$atl_token"/>
<input type="submit" name="submit" default="true" value="Update"/>
  • If you add the Java annotation but not the atl_token, users will get an XSRF error every time they perform the action.
  • If you add the atl_token but not the Java annotation, everything will appear fine but there is no XSRF protection in place.

Confluence WebSudo

File Execution Vulnerabilities

To reproduce the example file execution vulnerability using the example JIRA plugin:
1) As an administrator, go to 500page.jsp to find where JIRA is running. For example:
*Location of entityengine.xml: file:/D:/JIRA/atlassian-jira/WEB-INF/classes/entityengine.xml*
tells us that JIRA is running from D:\JIRA\atlassian-jira.
2) Under JIRA Administration, go to "Custom CSS Settings"
3) Set the path to the path you found in step 1 and click Update.
4) In the top menu bar, click "Custom CSS Settings".
5) Using this form, upload a JSP file.
6) To make life easier for the would-be hacker, the uploaded path is displayed - e.g. D:\JIRA\atlassian-jira\92668751\Malicious.jsp. Take the part of the file name after the JIRA path (92668751\Malicious.jsp) and append it to the JIRA context path (*http://localhost:8080/92668751/Malicious.jsp*). Then visit that path.

Random Number Vulnerabilities

  • Many thanks to James Roper for his information on the Random Number vulnerabilities and for providing the sample code for cracking the examples.

To reproduce the java.util.Random example using the example JIRA plugin:
1) Go to JIRA Administration, then "Verify Identity". Click the "Generate Token" button.
2) Go to the "atlassian-jira.log" file in the "log" directory of your JIRA_HOME.
3) Copy the generated token from the last entry in the log file. This is just a convenience for demo purposes - in practice, the token might be emailed to a user for them to verify an account.
4) From the command line, if you have a Groovy interpreter installed, pass in the token to CrackRandom. e.g.
        groovy CrackRandom.groovy fd326406e3f073cb
It will give you the next 5 values for this token.

To reproduce the example:
1) Go to JIRA Administration, then "Verify Identity Securely". Click the "Generate Token" button.
2) Go to the "atlassian-jira.log" file in the "log" directory of your JIRA_HOME.
3) This SecureRandom is seeded with the system time the first time that one of these tokens is generated. Copy the system time and the token that was generated.
4) From the command line, pass in the system time (it doesn't need to be precise, you can round down a minute) and the token to CrackRandom. e.g.
       groovy CrackRandom.groovy -secure 2010-09-30 14:31:00 8f698945870959a8

Further Reading

Please don't hesitate to email me at if you have any questions!


  1. Lots of excellent talks this year, but this was definitely one of my favorites.

    The use of XSRF to 'assist' in the procurement of a new beer fridge at Atlassian was truly priceless.

    Thanks Penny!

    1. And I second that!

    2. I agree, I learned a lot from this presentation!

      Definitely one of my favorites.

      1. I couldn't agree more, this was by far among the best presentations. I'm already implementing many of the suggestions