CVE-2022-0727: Incorrect Authorization (CWE-863)
value: Improper Access Control in GitHub repository chocobozzz/peertube prior to 4.1.0.
CWE description: The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. This allows attackers to bypass intended access restrictions.
CVSS v3 base score: 5.4
Published: 2022-02-23T14:15Z
Repository: https://github.com/chocobozzz/peertube
Language: TypeScript
Files changed: server/middlewares/validators/videos/video-comments.ts, server/middlewares/validators/videos/video-rates.ts, server/tests/api/check-params/video-comments.ts, server/tests/api/check-params/videos.ts
Commit date: 2022-02-22 14:16:51 +0100
Commit message: Check video privacy when creating comments/rates

Diff: diff --git a/server/middlewares/validators/videos/video-comments.ts b/server/middlewares/validators/videos/video-comments.ts
index 91ae31ec23..91e85711d3 100644
--- a/server/middlewares/validators/videos/video-comments.ts
+++ b/server/middlewares/validators/videos/video-comments.ts
@@ -98,10 +98,18 @@ const addVideoCommentThreadValidator = [
   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
     logger.debug('Checking addVideoCommentThread parameters.', { parameters: req.params, body: req.body })
 
     if (areValidationErrors(req, res)) return
     if (!await doesVideoExist(req.params.videoId, res)) return
+
+    if (!await checkCanSeeVideoIfPrivate(req, res, res.locals.videoAll)) {
+      return res.fail({
+        status: HttpStatusCode.FORBIDDEN_403,
+        message: 'Cannot access to this ressource'
+      })
+    }
+
     if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return
     if (!await isVideoCommentAccepted(req, res, res.locals.videoAll, false)) return
 
     return next()
   }
@@ -117,10 +125,18 @@ const addVideoCommentReplyValidator = [
   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
     logger.debug('Checking addVideoCommentReply parameters.', { parameters: req.params, body: req.body })
 
     if (areValidationErrors(req, res)) return
     if (!await doesVideoExist(req.params.videoId, res)) return
+
+    if (!await checkCanSeeVideoIfPrivate(req, res, res.locals.videoAll)) {
+      return res.fail({
+        status: HttpStatusCode.FORBIDDEN_403,
+        message: 'Cannot access to this ressource'
+      })
+    }
+
     if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return
     if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoAll, res)) return
     if (!await isVideoCommentAccepted(req, res, res.locals.videoAll, true)) return
 
     return next()
diff --git a/server/middlewares/validators/videos/video-rates.ts b/server/middlewares/validators/videos/video-rates.ts
index 6e0bb0ad1e..923bf3eaf9 100644
--- a/server/middlewares/validators/videos/video-rates.ts
+++ b/server/middlewares/validators/videos/video-rates.ts
@@ -6,11 +6,11 @@ import { isAccountNameValid } from '../../../helpers/custom-validators/accounts'
 import { isIdValid } from '../../../helpers/custom-validators/misc'
 import { isRatingValid } from '../../../helpers/custom-validators/video-rates'
 import { isVideoRatingTypeValid } from '../../../helpers/custom-validators/videos'
 import { logger } from '../../../helpers/logger'
 import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
-import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared'
+import { areValidationErrors, checkCanSeeVideoIfPrivate, doesVideoExist, isValidVideoIdParam } from '../shared'
 
 const videoUpdateRateValidator = [
   isValidVideoIdParam('id'),
 
   body('rating').custom(isVideoRatingTypeValid).withMessage('Should have a valid rate type'),
@@ -19,10 +19,17 @@ const videoUpdateRateValidator = [
     logger.debug('Checking videoRate parameters', { parameters: req.body })
 
     if (areValidationErrors(req, res)) return
     if (!await doesVideoExist(req.params.id, res)) return
 
+    if (!await checkCanSeeVideoIfPrivate(req, res, res.locals.videoAll)) {
+      return res.fail({
+        status: HttpStatusCode.FORBIDDEN_403,
+        message: 'Cannot access to this ressource'
+      })
+    }
+
     return next()
   }
 ]
 
 const getAccountVideoRateValidatorFactory = function (rateType: VideoRateType) {
diff --git a/server/tests/api/check-params/video-comments.ts b/server/tests/api/check-params/video-comments.ts
index 829f3c8b12..624d657d3d 100644
--- a/server/tests/api/check-params/video-comments.ts
+++ b/server/tests/api/check-params/video-comments.ts
@@ -17,14 +17,18 @@ import {
 const expect = chai.expect
 
 describe('Test video comments API validator', function () {
   let pathThread: string
   let pathComment: string
+
   let server: PeerTubeServer
+
   let video: VideoCreateResult
+
   let userAccessToken: string
   let userAccessToken2: string
+
   let commentId: number
   let privateCommentId: number
   let privateVideo: VideoCreateResult
 
   // ---------------------------------------------------------------
@@ -201,26 +205,36 @@ describe('Test video comments API validator', function () {
       await makePostBodyRequest({ url: server.url, path: pathThread, token: server.accessToken, fields })
     })
 
     it('Should fail with an incorrect video', async function () {
       const path = '/api/v1/videos/ba708d62-e3d7-45d9-9d73-41b9097cc02d/comment-threads'
-      const fields = {
-        text: 'super comment'
-      }
+      const fields = { text: 'super comment' }
+
       await makePostBodyRequest({
         url: server.url,
         path,
         token: server.accessToken,
         fields,
         expectedStatus: HttpStatusCode.NOT_FOUND_404
       })
     })
 
+    it('Should fail with a private video of another user', async function () {
+      const fields = { text: 'super comment' }
+
+      await makePostBodyRequest({
+        url: server.url,
+        path: '/api/v1/videos/' + privateVideo.shortUUID + '/comment-threads',
+        token: userAccessToken,
+        fields,
+        expectedStatus: HttpStatusCode.FORBIDDEN_403
+      })
+    })
+
     it('Should succeed with the correct parameters', async function () {
-      const fields = {
-        text: 'super comment'
-      }
+      const fields = { text: 'super comment' }
+
       await makePostBodyRequest({
         url: server.url,
         path: pathThread,
         token: server.accessToken,
         fields,
@@ -228,10 +242,11 @@ describe('Test video comments API validator', function () {
       })
     })
   })
 
   describe('When adding a comment to a thread', function () {
+
     it('Should fail with a non authenticated user', async function () {
       const fields = {
         text: 'text'
       }
       await makePostBodyRequest({
@@ -274,10 +289,22 @@ describe('Test video comments API validator', function () {
         fields,
         expectedStatus: HttpStatusCode.NOT_FOUND_404
       })
     })
 
+    it('Should fail with a private video of another user', async function () {
+      const fields = { text: 'super comment' }
+
+      await makePostBodyRequest({
+        url: server.url,
+        path: '/api/v1/videos/' + privateVideo.uuid + '/comments/' + privateCommentId,
+        token: userAccessToken,
+        fields,
+        expectedStatus: HttpStatusCode.FORBIDDEN_403
+      })
+    })
+
     it('Should fail with an incorrect comment', async function () {
       const path = '/api/v1/videos/' + video.uuid + '/comments/124'
       const fields = {
         text: 'super comment'
       }
diff --git a/server/tests/api/check-params/videos.ts b/server/tests/api/check-params/videos.ts
index 4cc70f5cc4..41064d2ffd 100644
--- a/server/tests/api/check-params/videos.ts
+++ b/server/tests/api/check-params/videos.ts
@@ -26,10 +26,11 @@ describe('Test videos API validator', function () {
   let userAccessToken = ''
   let accountName: string
   let channelId: number
   let channelName: string
   let video: VideoCreateResult
+  let privateVideo: VideoCreateResult
 
   // ---------------------------------------------------------------
 
   before(async function () {
     this.timeout(30000)
@@ -47,10 +48,14 @@ describe('Test videos API validator', function () {
       const body = await server.users.getMyInfo()
       channelId = body.videoChannels[0].id
       channelName = body.videoChannels[0].name
       accountName = body.account.name + '@' + body.account.host
     }
+
+    {
+      privateVideo = await server.videos.quickUpload({ name: 'private video', privacy: VideoPrivacy.PRIVATE })
+    }
   })
 
   describe('When listing videos', function () {
     it('Should fail with a bad start pagination', async function () {
       await checkBadStartPagination(server.url, path)
@@ -781,10 +786,23 @@ describe('Test videos API validator', function () {
         rating: 'likes'
       }
       await makePutBodyRequest({ url: server.url, path: path + videoId + '/rate', token: server.accessToken, fields })
     })
 
+    it('Should fail with a private video of another user', async function () {
+      const fields = {
+        rating: 'like'
+      }
+      await makePutBodyRequest({
+        url: server.url,
+        path: path + privateVideo.uuid + '/rate',
+        token: userAccessToken,
+        fields,
+        expectedStatus: HttpStatusCode.FORBIDDEN_403
+      })
+    })
+
     it('Should succeed with the correct parameters', async function () {
       const fields = {
         rating: 'like'
       }
       await makePutBodyRequest({

Vulnerable code: import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared'
      const fields = {
        text: 'super comment'
      }
      const fields = {
        text: 'super comment'
      }
Fixed code: 
    if (!await checkCanSeeVideoIfPrivate(req, res, res.locals.videoAll)) {
      return res.fail({
        status: HttpStatusCode.FORBIDDEN_403,
        message: 'Cannot access to this ressource'
      })
    }


    if (!await checkCanSeeVideoIfPrivate(req, res, res.locals.videoAll)) {
      return res.fail({
        status: HttpStatusCode.FORBIDDEN_403,
        message: 'Cannot access to this ressource'
      })
    }

import { areValidationErrors, checkCanSeeVideoIfPrivate, doesVideoExist, isValidVideoIdParam } from '../shared'
    if (!await checkCanSeeVideoIfPrivate(req, res, res.locals.videoAll)) {
      return res.fail({
        status: HttpStatusCode.FORBIDDEN_403,
        message: 'Cannot access to this ressource'
      })
    }





      const fields = { text: 'super comment' }

    it('Should fail with a private video of another user', async function () {
      const fields = { text: 'super comment' }

      await makePostBodyRequest({
        url: server.url,
        path: '/api/v1/videos/' + privateVideo.shortUUID + '/comment-threads',
        token: userAccessToken,
        fields,
        expectedStatus: HttpStatusCode.FORBIDDEN_403
      })
    })

      const fields = { text: 'super comment' }


    it('Should fail with a private video of another user', async function () {
      const fields = { text: 'super comment' }

      await makePostBodyRequest({
        url: server.url,
        path: '/api/v1/videos/' + privateVideo.uuid + '/comments/' + privateCommentId,
        token: userAccessToken,
        fields,
        expectedStatus: HttpStatusCode.FORBIDDEN_403
      })
    })

  let privateVideo: VideoCreateResult

    {
      privateVideo = await server.videos.quickUpload({ name: 'private video', privacy: VideoPrivacy.PRIVATE })
    }
    it('Should fail with a private video of another user', async function () {
      const fields = {
        rating: 'like'
      }
      await makePutBodyRequest({
        url: server.url,
        path: path + privateVideo.uuid + '/rate',
        token: userAccessToken,
        fields,
        expectedStatus: HttpStatusCode.FORBIDDEN_403
      })
    })