The JIRAIFrameAction action is vulnerable to reflected XSS when passing an unsanitized url parameter to the Jira-iframe.vm velocity template. Exploitation of the issue first requires a JIRA issue id with read-access.
File: src\main\Resources\Atlassian-plugin.xml
<action name="com.pyxis.greenhopper.jira.actions.JIRAIFrameAction" alias="JIRAIFrameAction">
...
<command name="genericIssueIFrame" alias="GenericIssueIFrame">
<view name="success">/templates/greenhopper/jira/jira-iframe/jira-iframe.vm</view>
<view name="error">/templates/greenhopper/jira/boards/error.vm</view>
</command>
...
</action>
File: greenhopper\src\main\java\com\pyxis\greenhopper\jira\Actions\JIRAIFrameAction.java
package com.pyxis.greenhopper.jira.actions;
import com.atlassian.jira.config.SubTaskManager;
import com.atlassian.jira.issue.link.IssueLinkManager;
import com.pyxis.greenhopper.GreenHopper;
import com.pyxis.greenhopper.GreenHopperException;
import com.pyxis.greenhopper.jira.license.GreenHopperLicenseManager;
import com.pyxis.greenhopper.jira.util.JiraUtil;
@SuppressWarnings("serial")
public class JIRAIFrameAction extends BoardAction
{
private String url;
private Long issueType;
private long displayedTime;
private boolean absoluteUrl;
...
public String doGenericIssueIFrame()
{
try
{
if(JiraUtil.getIssue(getId()) == null)
{
throw new GreenHopperException("Issue not found", "gh.issue.notfound");
}
}
catch (Exception e)
{
addError("Issue not found", "Issue not found");
}
try
{
if (url == null) {
throw new GreenHopperException("URL not defined", "gh.issue.notfound");
}
}
catch (Exception e)
{
addError("URL not defined", "URL not defined");
}
return hasAnyErrors() ? ERROR : SUCCESS;
}
...
public String getUrl()
{
return url;
}
public void setUrl(String url)
{
this.url = url;
}
public boolean isAbsoluteUrl()
{
return absoluteUrl;
}
public void setAbsoluteUrl(boolean absoluteUrl)
{
this.absoluteUrl = absoluteUrl;
}
}
File: greenhopper\src\main\resources\templates\greenhopper\jira\Jira-iframe\Jira-iframe.vm
#disable_html_escaping()
##POSSIBLEXSS
<script type="text/javascript">
Boards.dynAJSPopup(1050, 620);
Boards.AJSpopup.addHeader("$action.getText('gh.boards.iframe')");
AJS.$('.aui-dialog').addClass('gh-dialog gh-dialog-iframe');
Boards.AJSpopup.addPanel("", "panel1");
Boards.AJSpopup.getCurrentPanel().html(AJS.$("#jira-iframe"));
Boards.AJSpopup.addCloseAction('$action.getText('gh.boards.close')', '${action.modifierKey}');
Boards.AJSpopup.cancelCallback = function() {
Boards.closeIFrame('$action.issueObject.id', '$action.displayedTime');
## don't close the popup yet
return true;
}
## set the src here, as otherwise a stale request is issued for the non-visible html added to the dom
AJS.$("iframe", Boards.AJSpopup.popup.element).attr('src', '#if(!$action.absoluteUrl)${requestContext.baseUrl}/#end$action.url');
Boards.showAJSPopup(0, 0);
</script>
<div style="display:none;">
<div id="jira-iframe" style="height:100%;">
<div class="body" style="height:100%;">
<div class="iframecontainer" style="height:100%;">
<iframe id="iframe" name="iframe" style="height:100%;width:100%;"></iframe>
</div>
</div>
</div><!-- </div>
An example URL that will exploit the issue:
http://10.211.55.9:8082/secure/GenericIssueIFrame.jspa?url=');</script><script>alert(document.cookie);</script>&absoluteUrl=true&id=10028
Screenshot of the XSS attack:
