Skip to content

Commit b77fe13

Browse files
fix: avoid to get files with other extensions than the proper ones for custom-sounds and emoji-custom endpoints (#38531)
Co-authored-by: Guilherme Gazzo <[email protected]>
1 parent 3048477 commit b77fe13

File tree

5 files changed

+61
-29
lines changed

5 files changed

+61
-29
lines changed

.changeset/clean-ears-fly.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@rocket.chat/meteor': patch
3+
---
4+
5+
Fixes a cross-resource access issue that allowed users to retrieve emojis from the Custom Sounds endpoint and sounds from the Custom Emojis endpoint when using the FileSystem storage mode.

apps/meteor/app/custom-sounds/server/startup/custom-sounds.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { CustomSounds } from '@rocket.chat/models';
12
import { Meteor } from 'meteor/meteor';
23
import { WebApp } from 'meteor/webapp';
34

@@ -47,7 +48,17 @@ Meteor.startup(() => {
4748
return;
4849
}
4950

51+
const sound = await CustomSounds.findOneById(fileId.split('.')[0], { projection: { _id: 1 } });
52+
53+
if (!sound) {
54+
res.writeHead(404);
55+
res.write('Not found');
56+
res.end();
57+
return;
58+
}
59+
5060
const file = await RocketChatFileCustomSoundsInstance.getFileWithReadStream(fileId);
61+
5162
if (!file) {
5263
res.writeHead(404);
5364
res.write('Not found');

apps/meteor/app/emoji-custom/server/startup/emoji-custom.js

Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { EmojiCustom } from '@rocket.chat/models';
12
import { Meteor } from 'meteor/meteor';
23
import { WebApp } from 'meteor/webapp';
34
import _ from 'underscore';
@@ -8,6 +9,35 @@ import { settings } from '../../../settings/server';
89

910
export let RocketChatFileEmojiCustomInstance;
1011

12+
const writeSvgFallback = (res, req) => {
13+
res.setHeader('Content-Type', 'image/svg+xml');
14+
res.setHeader('Cache-Control', 'public, max-age=0');
15+
res.setHeader('Expires', '-1');
16+
res.setHeader('Last-Modified', 'Thu, 01 Jan 2015 00:00:00 GMT');
17+
18+
const reqModifiedHeader = req.headers['if-modified-since'];
19+
if (reqModifiedHeader != null) {
20+
if (reqModifiedHeader === 'Thu, 01 Jan 2015 00:00:00 GMT') {
21+
res.writeHead(304);
22+
res.end();
23+
return;
24+
}
25+
}
26+
27+
const color = '#000';
28+
const initials = '?';
29+
30+
const svg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
31+
<svg xmlns="http://www.w3.org/2000/svg" pointer-events="none" width="50" height="50" style="width: 50px; height: 50px; background-color: ${color};">
32+
<text text-anchor="middle" y="50%" x="50%" dy="0.36em" pointer-events="auto" fill="#ffffff" font-family="Helvetica, Arial, Lucida Grande, sans-serif" style="font-weight: 400; font-size: 28px;">
33+
${initials}
34+
</text>
35+
</svg>`;
36+
37+
res.write(svg);
38+
res.end();
39+
};
40+
1141
Meteor.startup(() => {
1242
let storeType = 'GridFS';
1343

@@ -48,39 +78,19 @@ Meteor.startup(() => {
4878
return;
4979
}
5080

51-
const file = await RocketChatFileEmojiCustomInstance.getFileWithReadStream(encodeURIComponent(params.emoji));
52-
5381
res.setHeader('Content-Disposition', 'inline');
5482

83+
const emoji = await EmojiCustom.findOneByName(params.emoji.split('.')[0], { projection: { _id: 1 } });
84+
85+
if (!emoji) {
86+
return writeSvgFallback(res, req);
87+
}
88+
89+
const file = await RocketChatFileEmojiCustomInstance.getFileWithReadStream(encodeURIComponent(params.emoji));
90+
5591
if (!file) {
5692
// use code from username initials renderer until file upload is complete
57-
res.setHeader('Content-Type', 'image/svg+xml');
58-
res.setHeader('Cache-Control', 'public, max-age=0');
59-
res.setHeader('Expires', '-1');
60-
res.setHeader('Last-Modified', 'Thu, 01 Jan 2015 00:00:00 GMT');
61-
62-
const reqModifiedHeader = req.headers['if-modified-since'];
63-
if (reqModifiedHeader != null) {
64-
if (reqModifiedHeader === 'Thu, 01 Jan 2015 00:00:00 GMT') {
65-
res.writeHead(304);
66-
res.end();
67-
return;
68-
}
69-
}
70-
71-
const color = '#000';
72-
const initials = '?';
73-
74-
const svg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
75-
<svg xmlns="http://www.w3.org/2000/svg" pointer-events="none" width="50" height="50" style="width: 50px; height: 50px; background-color: ${color};">
76-
<text text-anchor="middle" y="50%" x="50%" dy="0.36em" pointer-events="auto" fill="#ffffff" font-family="Helvetica, Arial, Lucida Grande, sans-serif" style="font-weight: 400; font-size: 28px;">
77-
${initials}
78-
</text>
79-
</svg>`;
80-
81-
res.write(svg);
82-
res.end();
83-
return;
93+
return writeSvgFallback(res, req);
8494
}
8595

8696
const fileUploadDate = file.uploadDate != null ? file.uploadDate.toUTCString() : undefined;

packages/model-typings/src/models/IEmojiCustomModel.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ export interface IEmojiCustomModel extends IBaseModel<IEmojiCustom> {
1212
setETagByName(name: string, etag: string): Promise<UpdateResult>;
1313
create(data: InsertionModel<IEmojiCustom>): Promise<InsertOneResult<WithId<IEmojiCustom>>>;
1414
countByNameOrAlias(name: string): Promise<number>;
15+
findOneByName(name: string, options?: FindOptions<IEmojiCustom>): Promise<IEmojiCustom | null>;
1516
}

packages/models/src/models/EmojiCustom.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,9 @@ export class EmojiCustomRaw extends BaseRaw<IEmojiCustom> implements IEmojiCusto
9090

9191
return this.countDocuments(query);
9292
}
93+
94+
// TODO: convert name: string to branded type using to enforce validation also replace this type cross the models/apis
95+
findOneByName(name: string, options?: FindOptions<IEmojiCustom>): Promise<IEmojiCustom | null> {
96+
return this.findOne({ name }, options);
97+
}
9398
}

0 commit comments

Comments
 (0)