Personal Access Token Disclosure in Asana Desktop Application

This post gives an insight into a sensitive data exposure vulnerability in Asana for Mac that was rated as P1 and was awarded a bounty.

This was the very first report of that kind for me. Still, I think this type of deployment and build chain issue is more common than one may think.

The issue was reported to Asana via their Bugcrowd program on June 16th and addressed within a few hours. In fact, the initial triage by Bugcrowd was lightning fast so that the leaked secret could be revoked within hours after it was reported.


Table of Contents

  1. Approaching Electron-based applications
  2. Disclosure of Secrets
  3. Proving Impact
  4. Responsible Disclosure Timeline
  5. Learnings for Application Developers
  6. Learnings for Security Researchers

Approaching Electron-based applications

Asana’s desktop application is based on Electron, a framework to build “native applications” based on websites using web technologies such as HTML, Javascript and CSS.

Electron applications are packed using the asar (Electron Archive) format. The asar command-line utility can be used to extract files from a packed asar bundle.

Therefore, we need to install asar first, before we can start our actual analysis:

$ npm install -g asar

Afterward, we obtain the subject of our analysis from https://desktop-downloads.asana.com/darwin_universal/prod/latest/Asana.dmg:

$ cd /tmp
$ wget https://desktop-downloads.asana.com/darwin_universal/prod/latest/Asana.dmg

On macOS, the disk image (*.dmg file) can be mounted using the open command. Afterward, we can copy the application itself (Asana.app) to an arbitrary destination:

$ open Asana.dmg
$ cp -r /Volumes/Asana/Asana.app /tmp/Asana.app

Running the file command reveals that the copied Asana.app is actually a folder. Using the asar extract command, we can aim to extract the actual “web application” sources from the wrapper bundle:

$ file /tmp/Asana.app
/tmp/Asana.app: directory
$ asar extract /tmp/Asana.app/Contents/Resources/app.asar /tmp/sources

The sources can then be opened in a text editor or IDE of your choice: VS Code - Asana Sources


Disclosure of Secrets

After browsing a bit around and trying to get a first impression of the folder structure and bundled contents, I almost could not believe my eyes. Within a folder named release_notes_bot, there was a file named .env that immediately aroused my interest. And indeed, the contents looked a bit odd:

$ cat /tmp/sourcecode/release_notes_bot/.env 
DESKTOP_RELEASE_NOTES_PERSONAL_ACCESS_TOKEN='0/a7f89e98g007e0s07da763a'

As the variable name indicates, the value appears to be a Personal Access Token (the above example uses the default one from the documentation 😉). Hardcoding or bundling secrets is definitely a bad practice. The Asana documentation advises, for good reasons:

Remember to keep your tokens secret; treat them just like passwords! They act on your behalf when interacting with the API. Don’t hardcode them into your programs. Instead, opt to use them as environment variables.

But: The token could for instance be bound to a limited service account without access to anything from interest, or be invalid anyway.


Proving Impact

Thus, to determine whether the obtained token inherits sensitive privileges, we need to consult the API documentation, again. This almost immediately nudges us to the following request:

GET /api/1.0/users/me HTTP/1.1
Host: app.asana.com
Authorization: Bearer [PERSONAL-ACCESS-TOKEN]

If we send this request with the obtained token, we receive something like this in response:

HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
[...]

{
    "data":{
        "gid":"[REDACTED]",
        "email":"[john.smith]@asana.com",
        "name":"[john.smith]@asana.com",
        "photo":{
            "image_21x21":"https://s3.amazonaws.com/profile_photos/[REDACTED].png",
            [...]
        },"resource_type":"user",
        "workspaces":[{
            "gid":"[REDACTED]",
            "name":"[REDACTED]",
            "resource_type":"workspace"
        },{
            "gid":"[REDACTED]",
            "name":"Asana, Inc",
            "resource_type":"workspace"
        }]
    }
}

Bingo! The token is valid, bound to a real user account (not just a limited service account), and has access to multiple workspaces.
One of these workspaces appears to be the internal Asana workspace for their employees. 😮

To finally prove the impact, after consulting the documentation again, I obtained the limited outputs of the request to https://app.asana.com/api/1.0/projects?limit=10&workspace=[REDACTED] which included the names of some internal Asana projects, and then filed the report at Bugcrowd.


Responsible Disclosure Timeline


Learnings for Application Developers

Make sure that development artifacts do not make their way to production builds. Build processes are often fragile chains of multiple tools - regularly evaluate if each component really acts as expected.

More generally, handle sensitive secrets like personal access tokens or API tokens with care. Limit their scope to their intended use case and regularly rotate and revoke your secrets to limit the impact of leakage.


Learnings for Security Researchers

Many bug bounty programs include native applications such as Android, iOS, or desktop apps. For Asana, there was even a promotion for their desktop application. Still, this relatively trivial to identify issue made its way to production - I have to admit that I do not know how long the build process was broken, though.

This leads me to the conclusion, that probably fewer researchers spend time looking at these native assets. Thus, do not fear native applications. Give them a shot, you do not have much to lose 🙂



Thank you for reading this post! If you have any feedback, feel free to reach out via Mastodon, Twitter or LinkedIn. 🙂

If there is any interest in more educational posts, I could go for a post explaining how to debug Electron applications using Chrome with a hands-on example. In case you are interested, ping me on Twitter or LinkedIn.

You can directly tweet about this post using this link. 🤓


Sources