During a recent code review, a discussion evolved about whether we should sanitise arguments of JQL queries. I was quite surprised to learn about the JIRA community’s stand on this. I understand the points raised there and generally agree with most of them. However, I wonder whether statements like
JQL is not SQL, and you can’t inject anything dangerous into it.
might create a false sense of security. So, let’s have a closer look.
JQL is a query language to search for tickets on a JIRA instance. Unlike SQL, it doesn’t allow to modify any data. It is just a convenient tool for filtering issues.
A typical query may look like this:
project = TeamA AND created > startOfMonth() AND assignee = ‘Tom’
This example will return all tickets in the project
Team A created after the start of the current month and currently assigned to the user
Let’s have a look at an example and see whether we can identify possible attack vectors for JQL injection.
Imagine you want to build a simple web application which fetches data from a JIRA instance to create monthly PDF reports of tickets assigned to a specific user from your team. With JQL, this is straightforward. Let’s call our (imaginary) tool TicketReporter:
The code to build the query will look something like this:
String jqlQuery = String.format("project = TeamA AND created > startOfMonth() AND assignee = '%s'", user);
We pass the assignee based on the text input from the web interface and send the query to JIRA. If this were SQL, we would have a classic example of injection. Now, how different are things with JQL?
First, let’s think about which attack vectors our scenario offers. As mentioned before JQL is nothing more than a filter, so there is no way to modify any data on JIRA. The classic Little Bobby Tables example — or the one from the title picture ;-) — will not work. So far, so good.
The JQL query in our case limits the data to one project and a specific (and rather short) time range (the current month). This restriction is hard-coded and should not change. However, a crafted
user value allows us to remove this limitation:
Martin Schneider' OR created < '2021-01-01
Now the query becomes
project = TeamA AND created > startOfMonth() AND assignee = 'Martin Schneider' OR created < '2021-01-01'
will fetch all tickets from all projects of the entire JIRA instance!
I can see two issues with this. The first is visibility. The TicketReporter user might not be meant to have access to data of other JIRA projects or historical data. In this case, I partially agree with the JIRA developers that this is mostly a problem of data access control, not query sanitisation. We can restrict the data that the user used for the JQL query can access to solve most of the problem. The critical part here is that we must do so to prevent this kind of “attack”. Not having input sanitisation makes this our only line of defence. I would argue that in a good design, there should be multiple layers of security.
However, the options to limit access to JIRA might not be sufficient enough for our use-case. We can restrict a user’s access to specific projects but limiting visibility based on other fields (for example, the creation date) might not always be possible. Moreover, in practice, access control might prove challenging if there’s no 1:1 mapping between the users of our web interface and JIRA. Usually, tools that utilise JQL to read information are created to aggregate, visualise or summarise JIRA data for users who themselves don’t have access to JIRA. It might not be good practice, but the use of some type of superuser is very tempting and thus common for such use-cases.
The second problem is slightly trickier. By removing the filter, the result set can grow large, depending on the instance, we are talking about many 100 thousand or even millions of tickets. This number of results is a long shot from what our TicketReporter script was designed to handle. Depending on what actions we perform on the data, the time it takes to run, and the number of resources used can grow significantly. Imagine, for example, a user requesting a PDF report of a million tickets.
A second attack vector is to craft JQL queries with bad performance and thus long runtime. I didn’t explore this in detail, but it’s easy to imagine that queries using JQL functions, especially custom ones, can be designed to become quite expensive. JIRA has some safeguards in place, but this might still open an attack vector to perform DoS attacks by sending multiple such queries at the same time.
So what to do?
As a security threat, JQL injection is far from being on the same level as SQL injection. However, there are ways an attacker can exploit it. It is important to understand under which circumstances it poses a threat and how to prevent it.
With security, having multiple layers of protection is a fundamental principle. In my opinion, input sanitisation should always be one of them.