CVE-2022-0540 - Authentication bypass in Seraph

1. Foreword

Sorry for the late writeup. When the Advisory for CVE-2022-0540 was released, some of my reports were triaged and  I was hyped. But later, I lost motivation to complete this write up because all of those reports were marked as out of scope, not eligible for a bounty and even duplicated ??:D??
Anyway, I must keep my promise. So, let's get started

2. Payload

;
Yes ; really is the payload to bypass authentication in Jira. If you wonder how a semicolon can cause problem to Jira, please read the detail below.

3. Analysis

When looking at Jira Architecture overview I noticed that Seraph is the core authentication mechanism of Jira for a long time but there is no known bug with it (at least I don't know XD). So, I decided to take a look at it.

3.1. The Seraph filter

Jira implemented seraph as a filter com.atlassian.jira.security.JiraSecurityFilter. The doFilter() method call parent method:

The Seraph filter will use the security config services to get the roles required base on the request:

There are 3 services were implemented in Jira:

  1. JiraPathService: If the requested servlet path start with /secure/admin/, it will require the admin role.
  2. WebworkService: Get roles-required config of webwork in the actions.xml file
  3. JiraSeraphSecurityService: Get roles-required config of webwork action in all plugin's atlassian-plugin.xml file

The JiraPathService is easy to understand, our concern now is WebworkService and JiraSeraphSecurityService.

The WebworkService.getRequiredRoles() code:

Next is the JiraSeraphSecurityService. Follow the call chain

  • JiraSeraphSecurityService.getRequiredRoles() ->
  • LoginManagerImpl.getRequiredRoles() ->
  • AuthorisationManagerImpl.getRequiredRoles() ->
  • WebworkPluginSecurityServiceHelper.getRequiredRoles()

I noticed that WebworkService.getRequiredRoles() and WebworkPluginSecurityServiceHelper.getRequiredRoles() code are the same:

Both of them will get the string from last index of char "/" in the Request URI and use a mapper to find which action is it.
For example if the request URI is  /jira/secure/somerandomstringasdf/AdminConfigurationAction.jspa,
It will take the AdminConfigurationAction.jspa part as the key to find the action with actionMapper then get all the roles required within action configuration.

3.2. The webwork dispatcher

JiraWebworkActionDispatcher is mapped at servlet path pattern *.jspa. When service, it will get the action name from request and use a chain of action factories to create action:

JiraWebworkActionDispatcher get action name by substring from last index of "/" to last index of ".jspa" in the Servlet Path:

For example if the request servlet path is  /jira/secure/somerandomstringasdf/AdminConfigurationAction.jspa, the action name will be AdminConfigurationAction

The action factories chain:

3.3. The inconsistency

As you can see, the Seraph filter use getRequestURI to find required roles but The webwork dispatcher use getServletPath  to find the action name.
While searching the difference between getRequestURI and getServletPath, I found a good answer at stackoverflow

getRequestURI does contain path parameter (the ";jsessionid=S+ID" in the picture). After some debugging, I found out getServletPath does NOT contain path parameter. That mean if we put some path parameter to the URI (eg. "AdminAction.jspa;" ), Seraph won't be able to find any match case in actionMapper but the webwork dispatcher still can find the action :D

We can bypass the Seraph filter now, but why nobody found this bug for a long time?

3.4. The problem

In the debugger I can see it bypass the Seraph filter and I was hyped but...

It still require authentication ??:D??

I continue debug and found out that when action factories chain create action, it did check the authentication again:

I was about to give up. But when looking at the documentation for roles-required in Jira it say you can add a roles-required attribute to the parent WebWork element, or to a child action element (or both!).

I manually check all the case and found out if roles-required attribute was added to the WebWork element but not the action element, the LookupAliasActionFactoryProxy won't be able to authorise action request :D

InsightPluginShowGeneralConfiguration.jspa;

Another problem is SafeParameterSettingActionFactoryProxy check the websudo status if the requested action is annotated with @WebSudoRequired

That explain why nobody found this bug for a long time.

4. Some plugins

Atlassian provides a long list of affected plugins in Advisory but most of them are false positive, no impact or have a little install number.
I won't provide the detailed poc here but I will give you the name of some affected plugins and the impact and you can create yourself a poc.
This is an incomplete list, I have just checked some plugin with numerous installs. There are more plugins affected but I don't know what is the impact. If you know one, please name it in the comment section.

  • WBS Gantt-Chart for Jira: Preauth RCE (you can write any Java code with beanshell).
  • Insight - Asset Management: Bundled with Jira Service Management Server and Data Center, View, change Insight configuration (can RCE with object schema manager permissions by change the groovy script whitelist).
  • sumUp for Jira: Stored XSS (it's a template injection but Jira blacklisted some Velocity package)
  • SQL+JQL Driver: SQL Injection (this one is very hard to exploit)

5. Ending

This is a sad ending, you can skip it
2022-02-14 Jira version 8.20.6 & 8.13.18 is released with the fix but they delayed the Advisory many time and then it was indefinitely postponed. I did asked them but there is no respond
2022-04-14 I created a RCE report to a hackerone program which required two month to patch there system & report triaged.
2022-04-20 The advisory for CVE-2022-0540 goes out. I got some more reports triaged after this time.
Later, all of my report was invalidated.

Whatever happened, it's all gone now and I give up with this bug. Hope you can find some bug with this.