This is mostly a tour of how Jira does CSRF (cross-site request forgery), written for my own benefit while investigating the problems eventually logged as JRASERVER-73880 - Getting issue details... STATUS .
Have you ever tried to attach a file or image to a Jira issue, and got the error Jira could not attach the file as there was a missing token. Please try attaching the file again. ? This commonly happens when:
- You are viewing a Jira issue
- You leave your computer for over 5 hours --or-- in another tab you log out and back in to Jira.
- You return to the issue, and without refreshing the page try to attach a file or image to the issue.
It also happens if you click the 'Create' button to create a new issue, and on the 'Attachment' field try to upload an attachment.
Background: CSRF attacks
The 'missing token' in question is a CSRF token used to prevent CSRF (cross site request forgery) attacks.
An example of a CSRF attack would be as follows. An attacker creates a HTML page at https://hax0rs.com containing:
If you can trick a Jira administrator into visiting that site and clicking that link, a new
hacker account is created in Jira.
Why did Jira, upon receiving that
AddUser.jspa HTTP request, trust the sender? Because your browser identified the request as "coming from you" by including a identifying JSESSIONID cookie, which is scoped to
Until 2020, you could even embed the malicious URL in a
<img> tag, and not even need the user to click That is no longer possible since
SameSite=Lax became the default, 'Lax' meaning the
JSESSIONID cookie is sent when a link is clicked, but not for non-interactive URLs like in <img>.
Jira's CSRF defenses
atl_token CSRF token
Jira's main defense against CSRF is to generate a random bit of text (the 'CSRF token') just for you when you log in, and embed it in every HTML form that, if submitted, would changes server state (like adding users). The field is called
When the user submits a form in Jira (on the 'Add User' admin page, for example), this
atl_token parameter is submitted in the POST body. Jira checks that the
atl_token value is correct before performing the operation. The CSRF token value is a secret known only to Jira and your browser, and changes after every login or session expiry (5h of inactivity). An attacker won't know the correct
atl_token parameter to include in their forged URL.
This is known in the literature as the synchronizer token pattern (OWASP)
Only some Jira operations get token-based CSRF protection. It seems a bit haphazard, e.g. creating a Jira issue is CSRF protected, but adding a comment isn't. Specifically, CSRF protection is applied to:
- *.jspa Webwork actions like CreateIssueDetails.jspa, whose
execute()method has a
@requiresXsrfCheckannotation. For the implementation, see JiraActionFactory.java which invokes
- POST, DELETE AND put operations on REST resources, like , submitted via HTTP forms. See Atlassian REST API design guidelines for the rules here. This is implemented in XsrfResourceFilter, which also eventually leads to
In addition to the
atl_token mechanism, Jira also implements the 'Defence In Depth Techniques' on the OWASP page. Specifically, the
Referer request headers are checked for REST resources (but perhaps not yet for
.jspa requests - see
Getting issue details...
Referer tell Jira where the request originated – https://hax0rs.com in our example. This is implemented in
Jira's CSRF Implementation Details
Back to those
We know that the token is initially generated by Jira when you log in, and is embedded in the returned HTML:
But where does Jira store its copy of the parameter? In two places actually:
- the user session
- in the user's browser as a cookie.
atlassian.xsrf.token session attribute
While you are actively using Jira, Jira maintains a 'session' for you. A session is just a handful of key:value pairs in memory, grouped by 'session id', which is associated with your browser requests with the JSESSIONID cookie your browser sends.
If you are a Jira administator you can see your session's attributes by dropping sessionattributes.jsp into your
atlassian-jira/secure/ directory, and hitting the
There in the session, you see where Jira is storing your CSRF token: in the atlassian.xsrf.token attribute (Jira uses 'XSRF' as a synonym for CSRF).
There are a few more wrinkles, but that is the essence of Jira's CSRF protection: all state-modifying HTTP requests must have an
atl_token field, whose value must match the atlassian.xsrf.token session parameter.
If you examine the cookies your browser associates with Jira, you'll see an
This is sent along with every HTTP request.
It would therefore be easy to think that this atlassian.xsrf.token cookie is the thing in the request that needs validating. Isn't the server just comparing the atlassian.xsrf.token request cookie with the atlassian.xsrf.token session attribute is knows is correct, and if they match, passing the CSRF check?
atl_token form parameter which needs validating, not the atlassian.xsrf.token request cookie.
In fact, when the server receives a request, it could compare the
atl_token form parameter with either the atlassian.xsrf.token request cookie, or the atlassian.xsrf.token session attribute. In fact Jira does both (in
Why does Jira even set the atlassian.xsrf.token cookie? No idea. The general question of "why send the CSRF token as a cookie" is asked and answered on Stackoverflow. I'm not sure if any of those advantages actually apply to Jira. Certainly Jira does not populate the
When sessions expire..
One nice thing Jira could have done with the incoming atlassian.xsrf.token cookie it is re-initialize the atlassian.xsrf.token session parameter from the cookie if the session has expired. Then if your session expired overnight, and you had a Jira form half-filled in, the submit (next morning) would work, as the session atlassian.xsrf.token would be seeded from the atlassian.xsrf.token cookie. Currently that doesn't happen: if the session has expired,
XsrfTokenAdditionRequestFilter.java (very early in the filter chain) just generates a new atlassian.xsrf.token session attribute, which is then compared to the
atl_token field, fails to match, and you get a 'missing token' error to brighten your Monday morning:
..to Be Continued
Apologies to anyone reading, but there is no conclusion yet. The fix suggested above (copy the token from cookie to session) could be implemented as a custom servlet filter, even implemented as a plugin. If I ever do so I will update this post.