CVE-2022-0133 (TypeScript, published 2022-01-10T14:12Z)
Description: value: peertube is vulnerable to Improper Access Control
Severity: MEDIUM (CVSS v3 base score: 7.5)
Weakness: CWE-284 \u2014 Improper Access Control
The product does not restrict or incorrectly restricts access to a resource from an unauthorized actor.
Repository: https://github.com/chocobozzz/peertube
Files changed:
server/controllers/api/videos/captions.ts
server/controllers/api/videos/files.ts
server/middlewares/validators/shared/videos.ts
server/middlewares/validators/videos/video-captions.ts
server/middlewares/validators/videos/videos.ts
server/tests/api/check-params/video-captions.ts
Commit message: Prevent caption listing of private videos


Vulnerable code:
  const data = await VideoCaptionModel.listVideoCaptions(res.locals.videoId.id)
  videoFilesDeleteWebTorrentValidator
import { Response } from 'express'
  MVideoThumbnail
  checkUserCanManageVideo
import { UserRight } from '../../../../shared'
import { areValidationErrors, checkUserCanManageVideo, doesVideoCaptionExist, doesVideoExist, isValidVideoIdParam } from '../shared'
    if (!await doesVideoExist(req.params.videoId, res, 'id')) return
import { authenticatePromiseIfNeeded } from '../../auth'
        await authenticatePromiseIfNeeded(req, res, authenticateInQuery)
        const user = res.locals.oauth ? res.locals.oauth.token.User : null

        // Only the owner or a user that have blocklist rights can see the video
        if (!user || !user.canGetVideo(video)) {
          return res.fail({
            status: HttpStatusCode.FORBIDDEN_403,
            message: 'Cannot get this private/internal or blocklisted video'
          })
        }

        return next()
import { HttpStatusCode, VideoCreateResult } from '@shared/models'

Fixed code:
  const data = await VideoCaptionModel.listVideoCaptions(res.locals.onlyVideo.id)
  videoFilesDeleteWebTorrentValidator,
  videosGetValidator
  asyncMiddleware(videosGetValidator),
import { Request, Response } from 'express'
import { authenticatePromiseIfNeeded } from '@server/middlewares/auth'
import { VideoModel } from '@server/models/video/video'
  MVideo,
  MVideoThumbnail,
  MVideoWithRights
async function checkCanSeeVideoIfPrivate (req: Request, res: Response, video: MVideo, authenticateInQuery = false) {
  if (!video.requiresAuth()) return true

  const videoWithRights = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.id)

  return checkCanSeePrivateVideo(req, res, videoWithRights, authenticateInQuery)
}

async function checkCanSeePrivateVideo (req: Request, res: Response, video: MVideoWithRights, authenticateInQuery = false) {
  await authenticatePromiseIfNeeded(req, res, authenticateInQuery)

  const user = res.locals.oauth ? res.locals.oauth.token.User : null

  // Only the owner or a user that have blocklist rights can see the video
  if (!user || !user.canGetVideo(video)) {
    return false
  }

  return true
}

  checkUserCanManageVideo,
  checkCanSeeVideoIfPrivate,
  checkCanSeePrivateVideo
import { HttpStatusCode, UserRight } from '../../../../shared'
import {
  areValidationErrors,
  checkCanSeeVideoIfPrivate,
  checkUserCanManageVideo,
  doesVideoCaptionExist,
  doesVideoExist,
  isValidVideoIdParam
} from '../shared'
    if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return

    const video = res.locals.onlyVideo

    if (!await checkCanSeeVideoIfPrivate(req, res, video)) {
      return res.fail({
        status: HttpStatusCode.FORBIDDEN_403,
        message: 'Cannot list captions of private/internal/blocklisted video'
      })
    }
  checkCanSeePrivateVideo,
        if (await checkCanSeePrivateVideo(req, res, video, authenticateInQuery)) return next()
        return res.fail({
          status: HttpStatusCode.FORBIDDEN_403,
          message: 'Cannot get this private/internal or blocklisted video'
        })
import { HttpStatusCode, VideoCreateResult, VideoPrivacy } from '@shared/models'
  let privateVideo: VideoCreateResult
    privateVideo = await server.videos.upload({ attributes: { privacy: VideoPrivacy.PRIVATE } })
    it('Should fail with a private video without token', async function () {
      await makeGetRequest({
        url: server.url,
        path: path + privateVideo.shortUUID + '/captions',
        expectedStatus: HttpStatusCode.UNAUTHORIZED_401
      })
    })

    it('Should fail with another user token', async function () {
      await makeGetRequest({
        url: server.url,
        token: userAccessToken,
        path: path + privateVideo.shortUUID + '/captions',
        expectedStatus: HttpStatusCode.FORBIDDEN_403
      })
    })


      await makeGetRequest({
        url: server.url,
        path: path + privateVideo.shortUUID + '/captions',
        token: server.accessToken,
        expectedStatus: HttpStatusCode.OK_200
      })