mirror of
https://github.com/immich-app/immich.git
synced 2025-01-02 12:48:35 +02:00
feat(server): sanitized path for asset creation process to avoid security risk (#717)
* feat(server): sanitized path for asset creation process to avoid security risk * Sanitize resize path
This commit is contained in:
parent
ece94f6bdc
commit
e3ccc3ee6b
@ -6,6 +6,7 @@ import { diskStorage } from 'multer';
|
|||||||
import { extname, join } from 'path';
|
import { extname, join } from 'path';
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
|
import sanitize from 'sanitize-filename';
|
||||||
|
|
||||||
export const assetUploadOption: MulterOptions = {
|
export const assetUploadOption: MulterOptions = {
|
||||||
fileFilter: (req: Request, file: any, cb: any) => {
|
fileFilter: (req: Request, file: any, cb: any) => {
|
||||||
@ -19,17 +20,13 @@ export const assetUploadOption: MulterOptions = {
|
|||||||
storage: diskStorage({
|
storage: diskStorage({
|
||||||
destination: (req: Request, file: Express.Multer.File, cb: any) => {
|
destination: (req: Request, file: Express.Multer.File, cb: any) => {
|
||||||
const basePath = APP_UPLOAD_LOCATION;
|
const basePath = APP_UPLOAD_LOCATION;
|
||||||
// TODO these are currently not used. Shall we remove them?
|
|
||||||
// const fileInfo = req.body as CreateAssetDto;
|
|
||||||
|
|
||||||
// const yearInfo = new Date(fileInfo.createdAt).getFullYear();
|
|
||||||
// const monthInfo = new Date(fileInfo.createdAt).getMonth();
|
|
||||||
|
|
||||||
if (!req.user) {
|
if (!req.user) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const originalUploadFolder = join(basePath, req.user.id, 'original', req.body['deviceId']);
|
const sanitizedDeviceId = sanitize(req.body['deviceId']);
|
||||||
|
const originalUploadFolder = join(basePath, req.user.id, 'original', sanitizedDeviceId);
|
||||||
|
|
||||||
if (!existsSync(originalUploadFolder)) {
|
if (!existsSync(originalUploadFolder)) {
|
||||||
mkdirSync(originalUploadFolder, { recursive: true });
|
mkdirSync(originalUploadFolder, { recursive: true });
|
||||||
@ -41,8 +38,9 @@ export const assetUploadOption: MulterOptions = {
|
|||||||
|
|
||||||
filename: (req: Request, file: Express.Multer.File, cb: any) => {
|
filename: (req: Request, file: Express.Multer.File, cb: any) => {
|
||||||
const fileNameUUID = randomUUID();
|
const fileNameUUID = randomUUID();
|
||||||
|
const fileName = `${fileNameUUID}${req.body['fileExtension'].toLowerCase()}`;
|
||||||
|
|
||||||
cb(null, `${fileNameUUID}${req.body['fileExtension'].toLowerCase()}`);
|
cb(null, sanitize(fileName));
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
@ -5,6 +5,7 @@ import { existsSync, mkdirSync } from 'fs';
|
|||||||
import { diskStorage } from 'multer';
|
import { diskStorage } from 'multer';
|
||||||
import { extname } from 'path';
|
import { extname } from 'path';
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
|
import sanitize from 'sanitize-filename';
|
||||||
|
|
||||||
export const profileImageUploadOption: MulterOptions = {
|
export const profileImageUploadOption: MulterOptions = {
|
||||||
fileFilter: (req: Request, file: any, cb: any) => {
|
fileFilter: (req: Request, file: any, cb: any) => {
|
||||||
@ -35,8 +36,9 @@ export const profileImageUploadOption: MulterOptions = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const userId = req.user.id;
|
const userId = req.user.id;
|
||||||
|
const fileName = `${userId}${extname(file.originalname)}`;
|
||||||
|
|
||||||
cb(null, `${userId}${extname(file.originalname)}`);
|
cb(null, sanitize(fileName));
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { APP_UPLOAD_LOCATION } from '@app/common';
|
||||||
import { ImmichLogLevel } from '@app/common/constants/log-level.constant';
|
import { ImmichLogLevel } from '@app/common/constants/log-level.constant';
|
||||||
import { AssetEntity, AssetType } from '@app/database/entities/asset.entity';
|
import { AssetEntity, AssetType } from '@app/database/entities/asset.entity';
|
||||||
import {
|
import {
|
||||||
@ -19,9 +20,11 @@ import { Job, Queue } from 'bull';
|
|||||||
import ffmpeg from 'fluent-ffmpeg';
|
import ffmpeg from 'fluent-ffmpeg';
|
||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
import { existsSync, mkdirSync } from 'node:fs';
|
import { existsSync, mkdirSync } from 'node:fs';
|
||||||
|
import sanitize from 'sanitize-filename';
|
||||||
import sharp from 'sharp';
|
import sharp from 'sharp';
|
||||||
import { Repository } from 'typeorm/repository/Repository';
|
import { Repository } from 'typeorm/repository/Repository';
|
||||||
import { CommunicationGateway } from '../../../immich/src/api-v1/communication/communication.gateway';
|
import { join } from 'path';
|
||||||
|
import { CommunicationGateway } from 'apps/immich/src/api-v1/communication/communication.gateway';
|
||||||
|
|
||||||
@Processor(thumbnailGeneratorQueueName)
|
@Processor(thumbnailGeneratorQueueName)
|
||||||
export class ThumbnailGeneratorProcessor {
|
export class ThumbnailGeneratorProcessor {
|
||||||
@ -46,9 +49,12 @@ export class ThumbnailGeneratorProcessor {
|
|||||||
|
|
||||||
@Process({ name: generateJPEGThumbnailProcessorName, concurrency: 3 })
|
@Process({ name: generateJPEGThumbnailProcessorName, concurrency: 3 })
|
||||||
async generateJPEGThumbnail(job: Job<JpegGeneratorProcessor>) {
|
async generateJPEGThumbnail(job: Job<JpegGeneratorProcessor>) {
|
||||||
const { asset } = job.data;
|
const basePath = APP_UPLOAD_LOCATION;
|
||||||
|
|
||||||
const resizePath = `upload/${asset.userId}/thumb/${asset.deviceId}/`;
|
const { asset } = job.data;
|
||||||
|
const sanitizedDeviceId = sanitize(asset.deviceId);
|
||||||
|
|
||||||
|
const resizePath = join(basePath, asset.userId, 'thumb', sanitizedDeviceId);
|
||||||
|
|
||||||
if (!existsSync(resizePath)) {
|
if (!existsSync(resizePath)) {
|
||||||
mkdirSync(resizePath, { recursive: true });
|
mkdirSync(resizePath, { recursive: true });
|
||||||
|
1133
server/package-lock.json
generated
1133
server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -37,7 +37,6 @@
|
|||||||
"@nestjs/mapped-types": "*",
|
"@nestjs/mapped-types": "*",
|
||||||
"@nestjs/passport": "^8.2.2",
|
"@nestjs/passport": "^8.2.2",
|
||||||
"@nestjs/platform-express": "^8.4.7",
|
"@nestjs/platform-express": "^8.4.7",
|
||||||
"@nestjs/platform-fastify": "^8.4.7",
|
|
||||||
"@nestjs/platform-socket.io": "^8.4.7",
|
"@nestjs/platform-socket.io": "^8.4.7",
|
||||||
"@nestjs/schedule": "^2.0.1",
|
"@nestjs/schedule": "^2.0.1",
|
||||||
"@nestjs/swagger": "^5.2.1",
|
"@nestjs/swagger": "^5.2.1",
|
||||||
@ -56,13 +55,14 @@
|
|||||||
"fluent-ffmpeg": "^2.1.2",
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
"joi": "^17.5.0",
|
"joi": "^17.5.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"passport": "^0.5.2",
|
"passport": "^0.6.0",
|
||||||
"passport-jwt": "^4.0.0",
|
"passport-jwt": "^4.0.0",
|
||||||
"pg": "^8.7.1",
|
"pg": "^8.7.1",
|
||||||
"redis": "^3.1.2",
|
"redis": "^3.1.2",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rxjs": "^7.2.0",
|
"rxjs": "^7.2.0",
|
||||||
|
"sanitize-filename": "^1.6.3",
|
||||||
"sharp": "^0.28.0",
|
"sharp": "^0.28.0",
|
||||||
"socket.io-redis": "^6.1.1",
|
"socket.io-redis": "^6.1.1",
|
||||||
"swagger-ui-express": "^4.4.0",
|
"swagger-ui-express": "^4.4.0",
|
||||||
|
Loading…
Reference in New Issue
Block a user