# Abusing functionality to exploit a super SSRF in Jira Server
(CVE-2022-26135)
Jun 26, 2022
TL;DR Jira is vulnerable to SSRF which requires authentication to exploit.
There are multiple ways to create user accounts on Jira in order to exploit
this issue depending on the configuration of the Jira instance. As an attack
chain, it may be possible for an attacker to exploit this issue without known
credentials. The advisory from Atlassian can be found
[here](https://confluence.atlassian.com/jira/jira-server-security-
advisory-29nd-june-2022-1142430667.html).
The exploit code for this vulnerability can be found
[here](https://github.com/assetnote/jira-mobile-ssrf-exploit). This exploit
attempts to register an account on Jira Core or Jira Service Desk and then
exploits the SSRF vulnerability automatically.
# Introduction
Jira is the most popular issue tracking software on the internet. It is used
by businesses of all sizes, from small businesses to the Fortune 100. As
security researchers, researching vulnerabilities within Jira holds
significant value due to the potential impacts they may have across a wide
range of businesses.
Our security research team discovered a vulnerability within Jira Server Core,
which allows attackers to make requests to arbitrary URLs, with any HTTP
method, headers and body. While this SSRF vulnerability was quite powerful, by
default, this vulnerability is only exploitable after you have authenticated
to the Jira instance.
We spent a few days looking for an authentication bypass in Jira so that we
could exploit the SSRF we discovered pre-authentication, until we had a
lightbulb moment. We realised that we could abuse the functionalities of Jira
Service Desk to obtain an account on a Jira instance. This chain allowed us to
exploit this SSRF vulnerability without knowing a valid pair of credentials
(Jira Service Desk is often configured to provision accounts).
# The Discovery Process
Over the last decade, we've tracked, analysed and focused on Atlassian's suite
of products when it comes to security advisories. The software that Atlassian
produces is extremely popular and the diversity of businesses that use it have
always motivated us to stay on top of the various security issues discovered
in their products.
In April this year, a [security
advisory](https://confluence.atlassian.com/jira/jira-security-
advisory-2022-04-20-1115127899.html) was published for Jira regarding an
authentication bypass vulnerability in Seraph. My colleague, Dylan, and I,
were tasked with writing checks for our [Attack Surface Management
Platform](https://assetnote.io). One of the plugins listed in the advisory
that was vulnerable to the Seraph authentication bypass was the [Mobile Plugin
for Jira](https://marketplace.atlassian.com/apps/1220151/mobile-plugin-for-
jira-data-center-and-server?tab=overview).
While analysing this security advisory, we made ourselves very familiar with
all of the affected plugins, and most importantly, the Mobile Plugin for Jira,
which is where we discovered our server-side request forgery vulnerability.
Let our experiences in analysing patches, advisories and published security
issues be yet another reason for you to consider doing the same. You never
know what you may find in the process of reverse engineering.
# Code Analysis
With Jira plugins, it is possible to register REST routes which can typically
be called through `jira-base-url/rest/`. When looking at the Mobile Plugin for
Jira, we noticed the following block of code inside
`com/atlassian/jira/plugin/mobile/rest/v1_0/BatchResource.java`:
/* */ @Tag(name = "Batch API", description = "Contains all operations for batch requests")
/* */ @Path("/batch")
/* */ @Consumes({"application/json"})
/* */ @Produces({"application/json"})
/* */ @Component
/* */ public class BatchResource
/* */ {
/* */ private final BatchService batchService;
/* */
/* */ @Autowired
/* */ public BatchResource(BatchService batchService) {
/* 50 */ this.batchService = batchService;
/* */ }
/* */ @Operation(description = "Executes a batch request", responses = {@ApiResponse(responseCode = "200", description = "List of responses to all requests in the batch", content = {@Content(array = @ArraySchema(schema = @Schema(implementation = BatchResponseBean.class)))}), @ApiResponse(responseCode = "400", description = "In any of the following conditions are met:\n- A request method is not set\n- A request location is not set\n- The amount of batch requests given is greater than 5")})
/* */ @POST
/* */ public Response executeBatch(@Context HttpServletRequest httpRequest, RequestsBean<BatchRequestBean> requestsBean) {
/* 70 */ List<String> errors = validate(requestsBean);
/* 71 */ if (!errors.isEmpty()) {
/* 72 */ return Response.status(400).entity(errors).build();
/* */ }
/* */
/* 75 */ Map<String, String> headers = UriUtils.extractValidHeaders(httpRequest);
/* 76 */ List<BatchResponseBean> responseBeans = this.batchService.batch(requestsBean, headers);
/* 77 */ return Response.ok().entity(new ResponsesBean((List)ImmutableList.copyOf(responseBeans))).build();
/* */ }
/* */
/* */ private List<String> validate(RequestsBean<BatchRequestBean> requestsBean) {
/* 81 */ List<String> errors = new ArrayList<>();
/* */
/* 83 */ if (requestsBean.getRequests().size() > 5) {
/* 84 */ errors.add("Number of requests exceed the maximum number of requests");
/* */ }
/* */
/* 87 */ for (BatchRequestBean batchRequestBean : requestsBean.getRequests()) {
/* 88 */ if (batchRequestBean.getMethod() == null) {
/* 89 */ errors.add("HTTP method may not be null");
/* */ }
/* 91 */ if (StringUtils.isBlank(batchRequestBean.getLocation())) {
/* 92 */ errors.add("Location may not be null or empty");
/* */ }
/* */ }
/* */
/* 96 */ return errors;
/* */ }
/* */ }
The `batch` API built into the Mobile Plugin for Jira looks like it was
intended to take in multiple requests and execute them on the server side. The
line that we will be following through is `List<BatchResponseBean>
responseBeans = this.batchService.batch(requestsBean, headers);` which is
responsible for actually sending the HTTP requests. The `batchService` is
located in
`com/atlassian/jira/plugin/mobile/service/impl/BatchServiceImpl.java`.
The code for this can be found below:
/* */ public List<BatchResponseBean> batch(RequestsBean<BatchRequestBean> requestsBean, Map<String, String> headers) {
/* 64 */ AtomicBoolean skipBatch = new AtomicBoolean(false);
/* 65 */ return (List<BatchResponseBean>)requestsBean.getRequests().stream()
/* 66 */ .sequential()
/* 67 */ .map(requestBean -> {
/* */ if (skipBatch.get()) {
/* */ return buildResponse(requestBean.getLocation(), 503);
/* */ }
/* */
/* */ Optional<BatchResponseBean> responseBean = execute(requestBean, headers);
/* */
/* */ if (!responseBean.isPresent()) {
/* */ skipBatch.set(true);
/* */
/* */ return buildResponse(requestBean.getLocation(), 500);
/* */ }
/* */ if (!isValidResponse(responseBean.get())) {
/* */ skipBatch.set(true);
/* */ }
/* */ return responseBean.get();
/* 83 */ }).collect(Collectors.toList());
/* */ }
The line we're interested in from the above snippet of code is
`Optional<BatchResponseBean> responseBean = execute(requestBean, headers);`.
The source code for this can be found below:
/* */ private Optional<BatchResponseBean> execute(BatchRequestBean requestBean, Map<String, String> headers) {
/* 92 */ String relativeLocation = requestBean.getLocation();
/* 93 */ URL jiraLocation = toJiraLocation(relativeLocation);
/* 94 */ if (jiraLocation == null) {
/* 95 */ return Optional.of(buildResponse(relativeLocation, 400));
/* */ }
/* 101 */ Request request = (new Request.Builder()).url(jiraLocation).headers(Headers.of(headers)).method(requestBean.getMethod().name(), (requestBean.getBody() == null) ? null : RequestBody.create(JSON, requestBean.getBody().toString())).build();
/* */
/* */ try {
/* 104 */ Response response = this.httpClientProvider.sendRequest(request);
/* 105 */ BatchResponseBean responseBean = toResponseBean(relativeLocation, response);
/* 106 */ return Optional.of(responseBean);
/* 107 */ } catch (Exception e) {
/* 108 */ log.error("Error when calling url: [" + relativeLocation + "]", e);
/* 109 */ return Optional.empty();
/* */ }
/* */ }
We can see that the URL is crafted through the following line of code: `String
relativeLocation = requestBean.getLocation(); URL jiraLocation =
toJiraLocation(relativeLocation);`. The code is obtaining the location from
the JSON object we are sending this to API, and then it is constructing a
"Jira Location" by passing in the location we specify in our request.
We can see the source code for `toJiraLocation` and it's associated functions
below:
/* */ private URL toJiraLocation(String relativeLocation) {
/* */ try {
/* 145 */ return this.linkBuilder.forRelativePath(relativeLocation).toURL();
/* 146 */ } catch (Exception e) {
/* 147 */ log.warn("Cannot parse relative location: [" + relativeLocation + "]");
/* 148 */ return null;
/* */ }
/* */ }
linkBuilder.forRelativePath can be found in com/atlassian/jira/plugin/mobile/util/LinkBuilder.java
/* */ public URI forRelativePath(String path) {
/* 27 */ return URI.create(this.jiraBaseUrls.baseUrl() + path);
/* */ }
Have you spotted the issue?
The URL is constructed through a simple concatenation:
`URI.create(this.jiraBaseUrls.baseUrl() + path);`.
As an attacker, you can simply specify `@targethost.com` which will ultimately
construct the URL `https://[[email protected]](/cdn-cgi/l/email-protection)`.
When this URL hits the HTTP client, the client will send a request to
`targethost.com`.
After the URL has been constructed, the sink for this issue is
`this.httpClientProvider.sendRequest(request);` which uses `OkHttpClient` to
send the HTTP requests.
# SSRF Capabilities & Limitations
**Capabilities** :
* We can send up to 5 requests at a time through this Batch API
* We can send requests with any HTTP method (method parameter in JSON)
* We can send requests with any HTTP headers (takes headers from our request and copies them over)
* We can send requests with any HTTP body (body parameter in JSON)
**Limitations** :
* The protocol is not controllable, you have to use a HTTP redirect if you want to request a URL which is a different protocol (https -> http for example)
* The host header may not be controllable depending on webserver configuration
# The Lightbulb Moment
While it is great that we have found an SSRF vulnerability in Jira, we're
still not able to weaponise it as it is post-authentication. As mentioned
previously in this blog post, we spent a few days looking for an
authentication bypass, but we were unable to find one.
The lightbulb moment occurred when we considered the context of Atlassian's
Jira ecosystem and how different components may interact with each other.
Atlassian also have a product called Jira Service Desk, which is often
installed along side Jira Core. It is very common to see scenarios where Jira
Service Desk signups are enabled. This is often the case because companies
would like their self service mechanisms to be … self service.
We were able to successfully exploit this post-authentication vulnerability by
first registering on Jira Service Desk, and then using that account to access
the Jira Core REST APIs.
This vulnerability is rated high severity due to this exploit chain.
You can sign up to Jira Service Desk through the following URL:
`/servicedesk/customer/user/signup`. Note: this endpoint being exposed is not
an indicator that signups are actually enabled. In order to determine if
signups are enabled, you must actually send a signup request.
Our exploit that is published on GitHub attempts to sign up to Jira Service
Desk and then exploit the SSRF issue, however, this can not always be
automated due to some signup flows requiring manual user interaction (i.e.
captchas, email).
# Exploit
You can obtain our exploit code for this issue by visiting the following URL:
<https://github.com/assetnote/jira-mobile-ssrf-exploit>.
The following HTTP request can be used to reproduce this issue, once
authenticated to the Jira instance:
POST /rest/nativemobile/1.0/batch HTTP/2
Host: issues.example.com
Cookie: JSESSIONID=44C6A24A15A1128CE78586A0FA1B1662; seraph.rememberme.cookie=818752%3Acc12c66e2f048b9d50eff8548800262587b3e9b1; atlassian.xsrf.token=AES2-GIY1-7JLS-HNZJ_db57d0893ec4d2e2f81c51c1a8984bde993b7445_lin
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36
Content-Type: application/json
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
Origin: https://issues.example.com
Referer: https://issues.example.com/plugins/servlet/desk
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Content-Length: 63
{"requests":[{"method":"GET","location":"@example.com"}]}
## Solution
The remediation details provided from Atlassian's advisory are satisfactory
and will ensure that this vulnerabilty cannot be exploited.
The knowledge base article detailing the patches or workaround to apply can be
found [here](https://confluence.atlassian.com/jira/jira-server-security-
advisory-29nd-june-2022-1142430667.html).
## Timeline
The timeline for this disclosure process can be found below:
* **Apr 21st, 2022** : Disclosure of SSRF vulnerability affecting Jira server & Jira Cloud to Atlassian's security team
* **Apr 21st, 2022** : Atlassian confirms security vulnerability and triages it in their internal issue tracker.
* **June 22nd, 2022** : Atlassian confirms advisory publication date and asks us for credit information.
* **June 29th, 2022** : Atlassian publishes advisory with patches.
The Atlassian team worked with us to quickly remediate this issue in Jira.
# Conclusion
Assessing vendor advisories, patches and reverse engineering the affected
components can sometimes lead to the discovery of new vulnerabilities.
In this case, we discovered a full read SSRF vulnerability while reverse
engineering some patches from Atlassian from earlier this year.
Additionally, even when it is not possible to bypass authentication through
vulnerabilities, consider the full context of the application and its
functionalities to determine alternative methods to exploit the issues that
were discovered in the post-authentication attack surface.
As always, customers of our [Attack Surface Management](https://assetnote.io)
platform were the first to know when this vulnerability affected them. We
continue to perform original security research in an effort to inform our
customers about zero-day vulnerabilities in their attack surface.
暂无评论