The vulnerability is a reflected Cross-Site Scripting (XSS) via the OAuth flow. A victim clicking a malicious link pointing to the target Mattermost host will trigger the XSS. If the victim is a regular user, it is possible to obtain all of their Mattermost chat contents; if itβs an administrator, it is possible to create a new administrator.
The application fails to sanitize an HTTP query parameter before reflecting it within the HTML response during the OAuth flow.
if props != nil {
action = props["action"]
isMobile = action == model.OAUTH_ACTION_MOBILE
if val, ok := props["redirect_to"]; ok {
[1] redirectURL = val
hasRedirectURL = redirectURL != ""
}
}
renderError := func(err *model.AppError) {
if isMobile && hasRedirectURL {
[2] utils.RenderMobileError(c.App.Config(), w, err, redirectURL)
} else {
utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey())
}
}
The file β/web/oauth.goβ (https://github.com/mattermost/mattermost-server/blob/master/web/oauth.go) contains the function βcompleteOAuthβ which on line 284 values the variable βredirectURLβ with the parameter βredirect_toβ [1] of the query string of the HTTP GET request. Subsequently always inside of the same function to the line 291 comes called the function βutils.RenderMobileErrorβ to which it comes passed like argument the variable βredirectURLβ [2].
func RenderMobileError(config *model.Config, w http.ResponseWriter, err *model.AppError, redirectURL string) {
RenderMobileMessage(w, `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512" style="width: 64px; height: 64px; fill: #ccc">
<path d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"/>
</svg>
<h2> `+i18n.T("error")+` </h2>
<p> `+err.Message+` </p>
[1] <a href>
`+i18n.T("api.back_to_app", map[string]interface{}{"SiteName": config.TeamSettings.SiteName})+`
</a>
`)
}
The function βRenderMobileErrorβ is contained within the file βutils/api.goβ (https://github.com/mattermost/mattermost-server/blob/master/utils/api.go) at line 103, and the fourth argument of this function is βredirectURLβ. At line 104 the βRenderMobileMessageβ function is called and at line 111 the variable βredirectURLβ is concatenated (without being sanitised) with another string argument of the βRenderMobileMessageβ function [1].
[...]
</head>
<body>
<div>
[1] `+message+`
</div>
</body>
</html>
`)
Inside the βRenderMobileMessageβ function (declared at line 117 of utils/api.go) βfmt.Fprintlnβ is called to print the HTTP response and the HTML page is dynamically built concatenating the βmessageβ variable [1] (second argument of the function).
Call graph:
completeOAuth -(redirectURL=redirect_to)-> util.RenderMobileError(,redirectURL) -(message=string+redirectURL)-> RenderMobileMessage(,message) -> fmt.Fprintln(string+message)
Since the HTTP GET request parameter βredirect_toβ is never sanitized and is appended to the HTML page, it is possible to trigger a reflected XSS.
Visit the following URL after replacing <mattermost_url> with the domain/ip of the mattermost server instance:
https://<mattermost_url>/oauth/shielder/mobile_login?redirect_to=%22%3E%3Cimg%20src=%22%22%20onerror=%22alert(%27zi0Black%20@%20Shielder%27)%22%3E
Notice the JavaScriptβs generated pop-up
The following attack scenarios have been identified: