mirror of
https://github.com/Chocobozzz/PeerTube.git
synced 2025-10-03 09:49:20 +02:00
Fix path traversal when getting a private playlist
This commit is contained in:
parent
71744313f0
commit
69c851c8e6
5 changed files with 119 additions and 20 deletions
|
@ -21,6 +21,7 @@ import './registrations.js'
|
|||
import './runners.js'
|
||||
import './search.js'
|
||||
import './services.js'
|
||||
import './static.js'
|
||||
import './transcoding.js'
|
||||
import './two-factor.js'
|
||||
import './upload-quota.js'
|
||||
|
|
94
packages/tests/src/api/check-params/static.ts
Normal file
94
packages/tests/src/api/check-params/static.ts
Normal file
|
@ -0,0 +1,94 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
||||
|
||||
import { getHLS } from '@peertube/peertube-core-utils'
|
||||
import { HttpStatusCode, VideoDetails, VideoPrivacy } from '@peertube/peertube-models'
|
||||
import {
|
||||
cleanupTests,
|
||||
createSingleServer,
|
||||
makeGetRequest,
|
||||
PeerTubeServer,
|
||||
setAccessTokensToServers,
|
||||
waitJobs
|
||||
} from '@peertube/peertube-server-commands'
|
||||
import { expect } from 'chai'
|
||||
import { basename } from 'path'
|
||||
|
||||
describe('Test static endpoints validators', function () {
|
||||
let server: PeerTubeServer
|
||||
|
||||
let privateVideo: VideoDetails
|
||||
let privateM3U8: string
|
||||
|
||||
let publicVideo: VideoDetails
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
before(async function () {
|
||||
this.timeout(300_000)
|
||||
|
||||
server = await createSingleServer(1)
|
||||
await setAccessTokensToServers([ server ])
|
||||
await server.config.enableMinimumTranscoding({ hls: true })
|
||||
|
||||
{
|
||||
const { uuid } = await server.videos.quickUpload({ name: 'video 1', privacy: VideoPrivacy.PRIVATE })
|
||||
await waitJobs([ server ])
|
||||
|
||||
privateVideo = await server.videos.getWithToken({ id: uuid })
|
||||
privateM3U8 = basename(getHLS(privateVideo).playlistUrl)
|
||||
}
|
||||
|
||||
{
|
||||
const { uuid } = await server.videos.quickUpload({ name: 'video 2', privacy: VideoPrivacy.PUBLIC })
|
||||
await waitJobs([ server ])
|
||||
|
||||
publicVideo = await server.videos.get({ id: uuid })
|
||||
}
|
||||
|
||||
await waitJobs([ server ])
|
||||
})
|
||||
|
||||
describe('Getting m3u8 playlist', function () {
|
||||
it('Should fail with an invalid video UUID', async function () {
|
||||
await makeGetRequest({
|
||||
url: server.url,
|
||||
token: server.accessToken,
|
||||
path: '/static/streaming-playlists/hls/private/toto/' + privateM3U8
|
||||
})
|
||||
})
|
||||
|
||||
it('Should fail with an invalid playlist name', async function () {
|
||||
await makeGetRequest({
|
||||
url: server.url,
|
||||
token: server.accessToken,
|
||||
path: '/static/streaming-playlists/hls/private/' + privateVideo.uuid + '/' + privateM3U8.replace('.m3u8', '.mp4')
|
||||
})
|
||||
})
|
||||
|
||||
it('Should fail with another m3u8 playlist of another video', async function () {
|
||||
await makeGetRequest({
|
||||
url: server.url,
|
||||
headers: {
|
||||
'x-peertube-video-password': 'fake'
|
||||
},
|
||||
path: '/static/streaming-playlists/hls/private/' + publicVideo.uuid + '/..%2f' + privateVideo.uuid + '%2f' + privateM3U8
|
||||
})
|
||||
})
|
||||
|
||||
it('Should succeed with the correct params', async function () {
|
||||
const { text } = await makeGetRequest({
|
||||
url: server.url,
|
||||
token: server.accessToken,
|
||||
path: '/static/streaming-playlists/hls/private/' + privateVideo.uuid + '/' + privateM3U8,
|
||||
expectedStatus: HttpStatusCode.OK_200
|
||||
})
|
||||
|
||||
expect(text).to.contain('#EXTM3U')
|
||||
expect(text).to.contain(basename(getHLS(privateVideo).files[0].playlistUrl))
|
||||
})
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
await cleanupTests([ server ])
|
||||
})
|
||||
})
|
|
@ -12,7 +12,7 @@ import {
|
|||
waitJobs
|
||||
} from '@peertube/peertube-server-commands'
|
||||
|
||||
describe('Test videos files', function () {
|
||||
describe('Test videos files API validators', function () {
|
||||
let servers: PeerTubeServer[]
|
||||
|
||||
let userToken: string
|
||||
|
|
|
@ -55,7 +55,7 @@ const privateHLSStaticMiddlewares = CONFIG.STATIC_FILES.PRIVATE_FILES_REQUIRE_AU
|
|||
: []
|
||||
|
||||
staticRouter.use(
|
||||
STATIC_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS + ':videoUUID/:playlistName.m3u8',
|
||||
STATIC_PATHS.STREAMING_PLAYLISTS.PRIVATE_HLS + ':videoUUID/:playlistNameWithoutExtension.m3u8',
|
||||
...privateHLSStaticMiddlewares,
|
||||
asyncMiddleware(servePrivateM3U8)
|
||||
)
|
||||
|
@ -81,8 +81,8 @@ export {
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function servePrivateM3U8 (req: express.Request, res: express.Response) {
|
||||
const path = join(DIRECTORIES.HLS_STREAMING_PLAYLIST.PRIVATE, req.params.videoUUID, req.params.playlistName + '.m3u8')
|
||||
const filename = req.params.playlistName + '.m3u8'
|
||||
const path = join(DIRECTORIES.HLS_STREAMING_PLAYLIST.PRIVATE, req.params.videoUUID, req.params.playlistNameWithoutExtension + '.m3u8')
|
||||
const filename = req.params.playlistNameWithoutExtension + '.m3u8'
|
||||
|
||||
let playlistContent: string
|
||||
|
||||
|
|
|
@ -1,21 +1,27 @@
|
|||
import { HttpStatusCode } from '@peertube/peertube-models'
|
||||
import { exists, isSafePeerTubeFilenameWithoutExtension, isUUIDValid, toBooleanOrNull } from '@server/helpers/custom-validators/misc.js'
|
||||
import {
|
||||
exists,
|
||||
isSafePeerTubeFilenameWithoutExtension,
|
||||
isUUIDValid,
|
||||
toBooleanOrNull
|
||||
} from '@server/helpers/custom-validators/misc.js'
|
||||
import { logger } from '@server/helpers/logger.js'
|
||||
import { LRU_CACHE } from '@server/initializers/constants.js'
|
||||
import { VideoFileModel } from '@server/models/video/video-file.js'
|
||||
import { VideoModel } from '@server/models/video/video.js'
|
||||
import { MStreamingPlaylist, MVideoFile, MVideoThumbnailBlacklist } from '@server/types/models/index.js'
|
||||
import express from 'express'
|
||||
import { query } from 'express-validator'
|
||||
import { param, query } from 'express-validator'
|
||||
import { LRUCache } from 'lru-cache'
|
||||
import { basename, dirname } from 'path'
|
||||
import { basename } from 'path'
|
||||
import { areValidationErrors, checkCanAccessVideoStaticFiles, isValidVideoPasswordHeader } from './shared/index.js'
|
||||
|
||||
type LRUValue = {
|
||||
allowed: boolean
|
||||
video?: MVideoThumbnailBlacklist
|
||||
file?: MVideoFile
|
||||
playlist?: MStreamingPlaylist }
|
||||
playlist?: MStreamingPlaylist
|
||||
}
|
||||
|
||||
const staticFileTokenBypass = new LRUCache<string, LRUValue>({
|
||||
max: LRU_CACHE.STATIC_VIDEO_FILES_RIGHTS_CHECK.MAX_SIZE,
|
||||
|
@ -62,6 +68,13 @@ const ensureCanAccessVideoPrivateWebVideoFiles = [
|
|||
]
|
||||
|
||||
const ensureCanAccessPrivateVideoHLSFiles = [
|
||||
param('videoUUID')
|
||||
.custom(isUUIDValid),
|
||||
|
||||
param('playlistNameWithoutExtension')
|
||||
.optional()
|
||||
.custom(v => isSafePeerTubeFilenameWithoutExtension(v)),
|
||||
|
||||
query('videoFileToken')
|
||||
.optional()
|
||||
.custom(exists),
|
||||
|
@ -71,22 +84,12 @@ const ensureCanAccessPrivateVideoHLSFiles = [
|
|||
.customSanitizer(toBooleanOrNull)
|
||||
.isBoolean().withMessage('Should be a valid reinjectVideoFileToken boolean'),
|
||||
|
||||
query('playlistName')
|
||||
.optional()
|
||||
.customSanitizer(isSafePeerTubeFilenameWithoutExtension),
|
||||
|
||||
isValidVideoPasswordHeader(),
|
||||
|
||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
if (areValidationErrors(req, res)) return
|
||||
|
||||
const videoUUID = basename(dirname(req.originalUrl))
|
||||
|
||||
if (!isUUIDValid(videoUUID)) {
|
||||
logger.debug('Path does not contain valid video UUID to serve static file %s', req.originalUrl)
|
||||
|
||||
return res.sendStatus(HttpStatusCode.FORBIDDEN_403)
|
||||
}
|
||||
const videoUUID = req.params.videoUUID
|
||||
|
||||
const token = extractTokenOrDie(req, res)
|
||||
if (!token) return
|
||||
|
@ -122,7 +125,8 @@ const ensureCanAccessPrivateVideoHLSFiles = [
|
|||
]
|
||||
|
||||
export {
|
||||
ensureCanAccessPrivateVideoHLSFiles, ensureCanAccessVideoPrivateWebVideoFiles
|
||||
ensureCanAccessPrivateVideoHLSFiles,
|
||||
ensureCanAccessVideoPrivateWebVideoFiles
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue