CVE-2023-40020: Improper Authentication (CWE-287)
value: PrivateUploader is an open source image hosting server written in Vue and TypeScript. In affected versions `app/routes/v3/admin.controller.ts` did not correctly verify whether the user was an administrator (High Level) or moderator (Low Level) causing the request to continue processing. The response would be a 403 with ADMIN_ONLY, however, next() would call leading to any updates/changes in the route to process. This issue has been addressed in version 3.2.49. Users are advised to upgrade. There are no known workarounds for this vulnerability.

CWE description: When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
CVSS v3 base score: 8.3
Published: 2023-08-14T21:15Z
Repository: https://github.com/PrivateUploader/PrivateUploader
Language: TypeScript
Files changed: app/controllers/v3/admin.controller.ts, app/controllers/v3/workspace.controller.ts, app/services/admin.service.ts, app/services/note.service.ts, app/validators/setup.ts, frontend/package.json
Commit date: 2023-08-12 16:36:33 +1000
Commit message: 3.2.49

Diff: diff --git a/app/controllers/v3/admin.controller.ts b/app/controllers/v3/admin.controller.ts
index 48d7f1b9..358e05a8 100644
--- a/app/controllers/v3/admin.controller.ts
+++ b/app/controllers/v3/admin.controller.ts
@@ -33,19 +33,28 @@ import { SetupControllerV3 } from "@app/controllers/v3/setup.controller"
 import { mergeDeep } from "@app/lib/deepMerge"
 import { OauthApp } from "@app/models/oauthApp.model"
 
 @Service()
 @Middleware({ type: "before" })
-@UseBefore(Auth("*"))
 class HighLevel implements ExpressMiddlewareInterface {
   async use(
     request: RequestAuth,
     response: Response,
     next: (err?: any) => any
   ) {
-    await authSystem("admin", false, request, response, next)
-    if (!request.user || !request.user.administrator) throw Errors.ADMIN_ONLY
+    const session = await authSystem(
+      "admin",
+      false,
+      request,
+      response,
+      () => {}
+    )
+    if (!session || !session.user?.administrator) {
+      throw Errors.ADMIN_ONLY
+    } else {
+      next()
+    }
   }
 }
 
 @Service()
 @Middleware({ type: "before" })
@@ -53,16 +62,19 @@ class LowLevel implements ExpressMiddlewareInterface {
   async use(
     request: RequestAuth,
     response: Response,
     next: (err?: any) => any
   ) {
-    await authSystem("admin", false, request, response, next)
+    await authSystem("admin", false, request, response, () => {})
     if (
       !request.user ||
       (!request.user?.administrator && !request.user?.moderator)
-    )
+    ) {
       throw Errors.ADMIN_ONLY
+    } else {
+      next()
+    }
   }
 }
 
 @Service()
 @JsonController("/admin")
diff --git a/app/controllers/v3/workspace.controller.ts b/app/controllers/v3/workspace.controller.ts
index 8aa686fb..f898928f 100644
--- a/app/controllers/v3/workspace.controller.ts
+++ b/app/controllers/v3/workspace.controller.ts
@@ -193,13 +193,17 @@ export class WorkspaceControllerV3 {
   ) {
     const folder = await this.noteService.getFolder(id, user.id)
     const zip: JSZip = new JSZip()
     for (const { dataValues } of folder.children) {
       try {
-        console.log(type)
         const data = await this.noteService.downloadNote(dataValues, type)
-        zip.file(`${dataValues.name.replaceAll("/", "-")}.${type}`, data)
+        zip.file(
+          `${dataValues.name
+            .replaceAll("/", "-")
+            .replaceAll("\\", "-")}.${type}`,
+          data
+        )
       } catch (e) {
         console.error(e)
       }
     }
     const data = await zip.generateAsync({ type: "nodebuffer" })
diff --git a/app/services/admin.service.ts b/app/services/admin.service.ts
index d21fe751..828d98ad 100755
--- a/app/services/admin.service.ts
+++ b/app/services/admin.service.ts
@@ -425,11 +425,11 @@ export class AdminService {
   }
 
   async updatePlanId(userId: number, planId: number) {
     const user = await User.findByPk(userId)
     if (!user) throw Errors.USER_NOT_FOUND
-    if (userId === 6 && planId === 6) {
+    if (userId === 6 && planId === 6 && config.officialInstance) {
       throw Errors.HANDLED_BY_PAYMENT_PROVIDER
     }
     await User.update(
       {
         planId
diff --git a/app/services/note.service.ts b/app/services/note.service.ts
index 1099a426..379e0adf 100644
--- a/app/services/note.service.ts
+++ b/app/services/note.service.ts
@@ -484,14 +484,15 @@ export class NoteService {
           ]
         }
       ]
     })
     if (!folder) throw Errors.NOT_FOUND
+    console.log(folder.name)
     const workspace = await this.getWorkspace(
       folder.workspaceId,
       userId,
-      "folder"
+      "workspace"
     )
     if (!workspace) throw Errors.NOT_FOUND
     return folder
   }
 
diff --git a/app/validators/setup.ts b/app/validators/setup.ts
index ef5284e6..e90ab50d 100644
--- a/app/validators/setup.ts
+++ b/app/validators/setup.ts
@@ -10,11 +10,15 @@ export const HostnameValidator = z.string().refine((value) => {
 }, "Invalid hostname")
 
 export const TpuConfigValidator = z.object({
   hostnameWithProtocol: z.string().url(),
   hostname: HostnameValidator,
-  maintenance: z.boolean(),
+  maintenance: z.object({
+    enabled: z.boolean(),
+    message: z.string().nullable().optional(),
+    statusPage: z.string().nullable().optional()
+  }),
   siteName: z.string(),
   release: z.string(),
   storage: z.string(),
   jitsiToken: z.string().nullable().optional(),
   registrations: z.boolean(),
diff --git a/frontend/package.json b/frontend/package.json
index 75941b2d..7f529f17 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,8 +1,8 @@
 {
   "name": "tpu-frontend",
-  "version": "3.2.48",
+  "version": "3.2.49",
   "private": true,
   "author": "Troplo <troplo@troplo.com>",
   "type": "module",
   "scripts": {
     "dev": "vite",

Vulnerable code: @UseBefore(Auth("*"))
    await authSystem("admin", false, request, response, next)
    if (!request.user || !request.user.administrator) throw Errors.ADMIN_ONLY
    await authSystem("admin", false, request, response, next)
    )
        console.log(type)
        zip.file(`${dataValues.name.replaceAll("/", "-")}.${type}`, data)
    if (userId === 6 && planId === 6) {
      "folder"
  maintenance: z.boolean(),
  "version": "3.2.48",
Fixed code:     const session = await authSystem(
      "admin",
      false,
      request,
      response,
      () => {}
    )
    if (!session || !session.user?.administrator) {
      throw Errors.ADMIN_ONLY
    } else {
      next()
    }
    await authSystem("admin", false, request, response, () => {})
    ) {
    } else {
      next()
    }
        zip.file(
          `${dataValues.name
            .replaceAll("/", "-")
            .replaceAll("\\", "-")}.${type}`,
          data
        )
    if (userId === 6 && planId === 6 && config.officialInstance) {
    console.log(folder.name)
      "workspace"
  maintenance: z.object({
    enabled: z.boolean(),
    message: z.string().nullable().optional(),
    statusPage: z.string().nullable().optional()
  }),
  "version": "3.2.49",