Jira and Confluence app security and external apps [updated – see below].
This article describes how security for apps for Jira and Confluence works. Jira and Confluence are the two main Atlassian applications, and in use by large and small teams. A large ecosystem of apps working together with and enhancing Jira and Confluence is built by external partners and available at https://marketplace.atlassian.com . Atlassian needed to built an integration system (called Connect) in order to secure the interaction of the external apps and their own systems against unauthorized access. I was part of a group of programmers who discussed Atlassian security, and we decided to engage with Atlassian in order to improve external app security. We found a potential issue in an Atlassian protocol, and as a result of our findings Atlassian has published new requirements for external app security: https://community.developer.atlassian.com/t/action-required-atlassian-connect-vulnerability-allows-bypass-of-app-qsh-verification-via-context-jwts/47072/
The Connect security system relies on multiple security layers. In this article I describe some of the technical aspects of the system. Atlassian requires transport encryption through TLS for all data. That means that all data flowing between Atlassian and an external app is encrypted. However this is not enough to guarantee security. Additionally most data are secured by a JWT (JSON Web Token). A JWT is a cryptographically signed token, which contains claims / data. For example, a JWT could contain an expiration time, and a data payload like a customer ID, and data from e.g. a Jira issue or data from a Confluence document. The data in the JWT is not encrypted, and if an adversary could hack TLS, they could read the JWT data. However the JWT is cryptographically signed, and if an adversary would tamper with the JWT, an external app must detect this and reject the JWT.
Atlassian and the external app both have access to a (kind of) secret key to sign the JWT’s. As long as nobody else has access to this secret key, only Atlassian and the external app can sign the JWT which is then mutually accepted by Atlassian and the external app.
However not all interaction between an app and Atlassian is through a JWT. Some data are passed as URL arguments, and others are passed through POST requests. In order to secure the data passed as URL arguments, Atlassian calculates a cryptographic hash of the URL (called QSH – Query String Hash), and adds this hash to the JWT claims. When an external app receives such a JWT with a signed URL hash, the app can check if the URL has been tampered with. In principle it is straightforward to check all URL’s with this method. However in practice there are multiple edge cases, where you can’t calculate a hash of the URL securely.
Atlassian has published a notice on April 14 2021 (https://community.developer.atlassian.com/t/action-required-atlassian-connect-vulnerability-allows-bypass-of-app-qsh-verification-via-context-jwts/47072/), which requires external apps to improve their security. Here are the steps an external app developer has to take:
- If you use Atlassian Connect Express (ACE) or Atlassian Spring Boot: Update to the latest available version. These versions contain enhancements which enable additional security.
- Make sure you signal your readiness with the new requirements to Atlassian by adding:
"apiMigrations": {
"context-qsh": true
}
To your app descriptor (usuallyatlassian-connect.json
). - Check all code paths were you either send or receive a request to or from Atlassian’s servers, the Connect iframe, or your frontend. All direct server to server communication (i.e. you app server to/from an Atlassian server) and Connect calls for the iframe MUST check for and verify the QSH. If you use the Atlassian node sdk called ACE, your code must use:
app.get('/path/to/webpage', addon.authenticate(), (req, res) => {
// return your response / html
});
However if your app server is called by frontend code, you can’t check for the specific QSH, and you must do a different check, by calling either:
app.get('/path/to/frontend/page', addon.authenticate(true), (req, res) => {
// return your response / html / JSON
});
Or:app.get('/path/to/frontend/page', addon.checkValidToken(), (req, res) => {
// return your response / html / JSON
});
Both methods are equivalent. - If you use your own implementation of the Atlassian JWT signing,
make sure you verify the QSH on the server. Do NOT attempt to sign
a JWT with a QSH on your frontend, this can make you app insecure!
Update 2021-04-21: You can read more about the CVE score here: https://confluence.atlassian.com/pages/viewpage.action?pageId=1051986099 and here https://nvd.nist.gov/vuln/detail/CVE-2021-26073
Feel free to contact me if you have further questions.