From 7b54a81cccf6b4c12269e9d6897d608b1a99537a Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 6 Jan 2022 11:16:35 +0100 Subject: [PATCH] Prevent video import on non unicast ips --- .../validators/videos/video-imports.ts | 18 ++++++++++++ .../tests/api/check-params/video-imports.ts | 28 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/server/middlewares/validators/videos/video-imports.ts b/server/middlewares/validators/videos/video-imports.ts index 640139c73..e4b54283f 100644 --- a/server/middlewares/validators/videos/video-imports.ts +++ b/server/middlewares/validators/videos/video-imports.ts @@ -13,6 +13,7 @@ import { CONFIG } from '../../../initializers/config' import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' import { areValidationErrors, doesVideoChannelOfAccountExist } from '../shared' import { getCommonVideoEditAttributes } from './videos' +import { isValid as isIPValid, parse as parseIP } from 'ipaddr.js' const videoImportAddValidator = getCommonVideoEditAttributes().concat([ body('channelId') @@ -71,6 +72,23 @@ const videoImportAddValidator = getCommonVideoEditAttributes().concat([ return res.fail({ message: 'Should have a magnetUri or a targetUrl or a torrent file.' }) } + if (req.body.targetUrl) { + const hostname = new URL(req.body.targetUrl).hostname + + if (isIPValid(hostname)) { + const parsed = parseIP(hostname) + + if (parsed.range() !== 'unicast') { + cleanUpReqFiles(req) + + return res.fail({ + status: HttpStatusCode.FORBIDDEN_403, + message: 'Cannot use non unicast IP as targetUrl.' + }) + } + } + } + if (!await isImportAccepted(req, res)) return cleanUpReqFiles(req) return next() diff --git a/server/tests/api/check-params/video-imports.ts b/server/tests/api/check-params/video-imports.ts index d6d745488..6c31daa9b 100644 --- a/server/tests/api/check-params/video-imports.ts +++ b/server/tests/api/check-params/video-imports.ts @@ -108,6 +108,34 @@ describe('Test video imports API validator', function () { await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) }) + it('Should fail with localhost', async function () { + const fields = { ...baseCorrectParams, targetUrl: 'http://localhost:8000' } + + await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) + }) + + it('Should fail with a private IP target urls', async function () { + const targetUrls = [ + 'http://127.0.0.1:8000', + 'http://127.0.0.1', + 'http://127.0.0.1/hello', + 'https://192.168.1.42', + 'http://192.168.1.42' + ] + + for (const targetUrl of targetUrls) { + const fields = { ...baseCorrectParams, targetUrl } + + await makePostBodyRequest({ + url: server.url, + path, + token: server.accessToken, + fields, + expectedStatus: HttpStatusCode.FORBIDDEN_403 + }) + } + }) + it('Should fail with a long name', async function () { const fields = { ...baseCorrectParams, name: 'super'.repeat(65) }