Vulnerability: HedgeDoc is an open-source, web-based, self-hosted, collaborative markdown editor. Images uploaded with HedgeDoc version 1.9.1 and later have an enumerable filename after the upload, resulting in potential information leakage of uploaded documents. This is especially relevant for private notes and affects all upload backends, except Lutim and imgur. This issue is patched in version 1.9.3 by replacing the filename generation with UUIDv4. If you cannot upgrade to HedgeDoc 1.9.3, it is possible to block POST requests to `/uploadimage`, which will disable future uploads.
Vulnerability ID: CVE-2022-24837
CWE: CWE-434
Severity score: 2.9
Project: https://github.com/hedgedoc/hedgedoc
Published: 2022-04-11
File: lib/web/imageRouter/index.js
Commit message: fix(imageRouter): Fix enumerable image upload issue

This patch adds an own filename function for `formidable`, which will
make sure to generate a random file name, using UUIDv4. This should
resolve GHSA-q6vv-2q26-j7rx.

This change is required due to a change in behaviour from version 1 to
version 2 of formidable. Formidable version 2 will generate predictable
filenames by default, which results in potential access to images, that
were uploaded while formidable v2 was used in Hedgedoc. This affects the
versions `1.9.1` and `1.9.2`.

Files generated previous to this commit will look like this:

```
<random string generated on app start><counter>.<file-extension>
38e56506ec2dcab52e9282c00.jpg
38e56506ec2dcab52e9282c01.jpg
38e56506ec2dcab52e9282c02.jpg
```

After this patch it'll look like this:

```
<uuid v4>.<file-extension>
a67f36b8-9afb-43c2-9ef2-a567a77d8628.jpg
56b3d5d0-c586-4679-9ae6-d2044843c2cd.jpg
2af727ac-a2d4-4aad-acb5-73596c2a7eb6.jpg
```

This patch was implemented using `uuid` since we already utilise this
package elsewhere in the project as well as using a secure function to
generate random strings. UUIDv4 is ideal for that. In order to be
consumable by formidable, it was wrapped in a function that makes sure
to keep the file extension.

This vulnerability was reported by Matias from [NCSC-FI](https://www.kyberturvallisuuskeskus.fi/).

References:
https://github.com/node-formidable/formidable/blob/v2-latest/src/Formidable.js#L574
https://github.com/node-formidable/formidable/issues/808#issuecomment-1007090762
https://www.npmjs.com/package/uuid
Commit author: Sheogorath

Vulnerable version (before fix):
 const Router = require('express').Router const formidable = require('formidable') const path = require('path') const fs = require('fs') const os = require('os') const rimraf = require('rimraf') const isSvg = require('is-svg') imageRouter.post('/uploadimage', function (req, res) {   const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hedgedoc-'))   const form = formidable({     keepExtensions: true,     uploadDir: tmpDir   })    form.parse(req, async function (err, fields, files) {

Fixed version (after fix):
 const Router = require('express').Router const formidable = require('formidable') const path = require('path') const fs = require('fs') const { v4: uuidv4 } = require('uuid'); const os = require('os') const rimraf = require('rimraf') const isSvg = require('is-svg') imageRouter.post('/uploadimage', function (req, res) {   const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hedgedoc-'))   const form = formidable({     keepExtensions: true,     uploadDir: tmpDir,     filename: function(filename, ext) {         if (typeof ext !== "string") {             ext = ".invalid"         }         return uuidv4() + ext     }   })    form.parse(req, async function (err, fields, files) {

Patch:
patch: @@ -4,6 +4,7 @@ const Router = require('express').Router
 const formidable = require('formidable')
 const path = require('path')
 const fs = require('fs')
+const { v4: uuidv4 } = require('uuid');
 const os = require('os')
 const rimraf = require('rimraf')
 const isSvg = require('is-svg')
@@ -70,7 +71,13 @@ imageRouter.post('/uploadimage', function (req, res) {
   const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hedgedoc-'))
   const form = formidable({
     keepExtensions: true,
-    uploadDir: tmpDir
+    uploadDir: tmpDir,
+    filename: function(filename, ext) {
+        if (typeof ext !== "string") {
+            ext = ".invalid"
+        }
+        return uuidv4() + ext
+    }
   })
 
   form.parse(req, async function (err, fields, files) {
path: lib/web/imageRouter/index.js