Hacking Mattermost: From Unauthenticated to System Admin
After my post about Apache OpenMeetings, Claudio Guarnieri asked me on Twitter if I would consider taking a look at Mattermost. While I don’t usually take requests (I get enough of that at work), I decided to take a look. This is a write-up of a couple of early issues I found in version 2.1.0 that can be used to accomplish some (in my humble opinion) interesting things.
Sidenote: I will be giving a presentation called “Hacking Mattermost: An Assessment of an Open Source Messaging Platform for Hipsters” at conferences later this year (one confirmed but not made official so far). In this talk I will present an in-depth analysis of the design, implementation, general issues, bugs, and the general security posture of Mattermost and its components.
Mattermost is an "open source, self-hosted Slack-alternative”, which basically means that it’s a real-time messaging solution for teams and enterprises that you host yourself. It’s written in the Go programming language, which was a new experience for me as I had not looked at any Go apps before this. Go makes it fairly hard for developers to introduce bugs such as (reflected) Cross Site Scripting and SQL injection, and does not to my knowledge have an easy way to dynamically execute code in the way that PHP and Java does. Fortunately (for me), developers still make mistakes and I was able to find a number of issues to play with.
The first bug is an information leak that enables an unauthenticated attacker to register for a team, without being invited by an existing member. This is a serious issue as there is likely to be internal or sensitive information available in the different chat channels within the application.
Mattermost has 2 ways to invite users to join a team.
1. Invite Team Member generates an email with an invitation link to the invited user.
2. Team Invite Link is for when a team member wants to manually email or otherwise send an invitation link to a user.
As can be seen in the image above, the important part is the id parameter as this is what enables you to register for the team. This value is also static, meaning that the same invite ID is used for all invitations to the team (although it can be manually regenerated by an admin). When I realized this, I started looking for some way that this information could be leaked to an unauthenticated attacker, and it did not take me long to find it.
Right after logging in to the application, a number of API calls are made to determine what the user should see, etc. One of those calls returns, among other things, the team invite ID for the user specified in the request payload.
POST /api/v1/teams/find_teams HTTP/1.1
Host: 192.168.2.9:8065
[...]
Content-Length: 27
Connection: close
{"iytnsn9hcpg3ud4g61y73b9opw":{"id":"iytnsn9hcpg3ud4g61y73b9opw","create_at":1460105434669,"update_at":1460375885784,"delete_at":0,"display_name":"team1","name":"jokers","email":"","type":"O","company_name":"","allowed_domains":"","invite_id":"2068a534882ad1d2a90853eb75ff590c","allow_open_invite":false,"allow_team_listing":false}}
When done automatically by the application, the request contains a session cookie but I discovered that this is not necessary, meaning that an unauthenticated user would only need to know the email address of a team member to be able to construct a valid sign-up URL.
Once signed up, the attacker becomes a Member, which is the role with the lowest permissions within the application.
As it turns out however, I had already found another bug that with some social engineering could allow an attacker to escalate to System Admin without too much work.
Stored XSS in attachments
Mattermost, like many messaging applications, has the functionality to upload attachments to chat channels and private messages.
The attachment is written to disk as-is, and a link such as the one below is created so that others can download it.
http://192.168.2.9:8065/api/v1/files/get/e1objp7t3ift8dq1s37zh35fmc/r5kcfatee7d6zfnjjiss9xwn1c/geb8dieqhib43g3u9bedqiqn1o/xss.html?session_token_index=0&download=1
The URL after the get folder is made up of the channel ID, the uploading user’s ID (this ID will be relevant later), a random string, and then the file name. When the link is clicked, the file will be delivered to the user’s browser as an attachment, i.e. with the Content-Disposition header set like this:
Content-Disposition: attachment; filename="xss.html"
I discovered that by removing the “&download=1″ part of the URL, or even just changing the value of the download parameter to 0, this header would not be set and the browser would instead render the attachment, and execute it if the file is an HTML file.
This stored XSS vulnerability is what could, with a little bit of Social Engineering, be used to escalate to System Admin.
Sprinkle some Social Engineering
Since Mattermost users can set their username (display name) to anything they want, an attacker could simply change their username to something similar to the name of a legitimate user and then send the admin either a direct link to an uploaded attachment or to another website containing the attachment link in an iframe. I consider this to be a plausible scenario as most people will follow links received from people who they think they know, and the assumption will likely be that a logged-in user is a legitimate one.
1. Upload an HTML file containing the following code. Note the user ID of our attacker in the payload.
2. When the admin visits the website, the following HTTP POST request is made.
POST /api/v1/users/update_roles HTTP/1.1
Host: 192.168.2.9:8065
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://192.168.2.9:8065/api/v1/files/get/e1objp7t3ift8dq1s37zh35fmc/r5kcfatee7d6zfnjjiss9xwn1c/geb8dieqhib43g3u9bedqiqn1o/xss.html
Content-Length: 67
Content-Type: text/plain;charset=UTF-8
Cookie: MMTOKEN=ps1zawhno7rjubawyw7ez5ysja
Connection: close
Cache-Control: max-age=0
{"user_id":"r5kcfatee7d6zfnjjiss9xwn1c","new_roles":"system_admin"}
3. The attacker now has several more interesting options in their menu.
Besides upgrading to a patched version of Mattermost (v2.2.0 released on 2016-04-16, updates are automatically checked for during startup), a fairly useful mitigation to at least the privilege escalation is to not use admin accounts as regular users, as this would limit the possibility for the attacker to interact with an admin. Also, keep a look out for users that you don’t recognize (may be easier said than done).
Auditing Mattermost has so far been (as it’s still ongoing) an interesting, and despite the somewhat low-hanging fruit-type bugs described in this post, quite a challenging project. I really like how Go encourages developers to code securely, and will most certainly be looking at more Go apps in the future.
Reporting the bugs to Mattermost has been an extremely smooth experience (hi Alice Evans), the issues in this post were validated and accepted within days, with fixes being pushed within a week. Very impressive.
Information about Mattermost security updates and how to apply patches can be found on the Mattermost Security Updates page.