Vulnerability: In Wiki.js before version 2.5.151, directory traversal outside of Wiki.js context is possible when a storage module with local asset cache fetching is enabled. A malicious user can potentially read any file on the file system by crafting a special URL that allows for directory traversal. This is only possible when a storage module implementing local asset cache (e.g Local File System or Git) is enabled and that no web application firewall solution (e.g. cloudflare) strips potentially malicious URLs. Commit 084dcd69d1591586ee4752101e675d5f0ac6dcdc fixes this vulnerability by sanitizing the path before it is passed on to the storage module. The sanitization step removes any directory traversal (e.g. `..` and `.`) sequences as well as invalid filesystem characters from the path. As a workaround, disable any storage module with local asset caching capabilities such as Local File System and Git.
Vulnerability ID: CVE-2020-15236
CWE: CWE-22
Severity score: 2.9
Project: https://github.com/Requarks/wiki
Published: 2020-10-05
File: server/helpers/page.js
Commit message: fix: strip directory traversal sequences from asset paths
Commit author: NGPixel

Vulnerable version (before fix):
 const path = require('path')  const localeSegmentRegex = /^[A-Z]{2}(-[A-Z]{2})?$/i const localeFolderRegex = /^([a-z]{2}(?:-[a-z]{2})?\/)?(.*)/i  const contentToExt = {   markdown: 'md', module.exports = {     // Clean Path     rawPath = _.trim(qs.unescape(rawPath))     if (_.startsWith(rawPath, '/')) { rawPath = rawPath.substring(1) }     if (rawPath === '') { rawPath = 'home' }      // Extract Info     let pathParts = _.filter(_.split(rawPath, '/'), p => !_.isEmpty(p))     if (pathParts[0].length === 1) {       pathParts.shift()     } module.exports = {       ['date', page.updatedAt],       ['tags', page.tags ? page.tags.map(t => t.tag).join(', ') : ''],       ['editor', page.editorKey],       ['dateCreated', page.createdAt],     ]     switch (page.contentType) {       case 'markdown':

Fixed version (after fix):
 const path = require('path')  const localeSegmentRegex = /^[A-Z]{2}(-[A-Z]{2})?$/i const localeFolderRegex = /^([a-z]{2}(?:-[a-z]{2})?\/)?(.*)/i // eslint-disable-next-line no-control-regex const unsafeCharsRegex = /[\x00-\x1f\x80-\x9f\\"|<>:*?]/  const contentToExt = {   markdown: 'md', module.exports = {     // Clean Path     rawPath = _.trim(qs.unescape(rawPath))     if (_.startsWith(rawPath, '/')) { rawPath = rawPath.substring(1) }     rawPath = rawPath.replace(unsafeCharsRegex, '')     if (rawPath === '') { rawPath = 'home' }      // Extract Info     let pathParts = _.filter(_.split(rawPath, '/'), p => {       p = _.trim(p)       return !_.isEmpty(p) && p !== '..' && p !== '.'     })     if (pathParts[0].length === 1) {       pathParts.shift()     } module.exports = {       ['date', page.updatedAt],       ['tags', page.tags ? page.tags.map(t => t.tag).join(', ') : ''],       ['editor', page.editorKey],       ['dateCreated', page.createdAt]     ]     switch (page.contentType) {       case 'markdown':

Patch:
patch: @@ -5,6 +5,8 @@ const path = require('path')
 
 const localeSegmentRegex = /^[A-Z]{2}(-[A-Z]{2})?$/i
 const localeFolderRegex = /^([a-z]{2}(?:-[a-z]{2})?\/)?(.*)/i
+// eslint-disable-next-line no-control-regex
+const unsafeCharsRegex = /[\x00-\x1f\x80-\x9f\\"|<>:*?]/
 
 const contentToExt = {
   markdown: 'md',
@@ -30,10 +32,14 @@ module.exports = {
     // Clean Path
     rawPath = _.trim(qs.unescape(rawPath))
     if (_.startsWith(rawPath, '/')) { rawPath = rawPath.substring(1) }
+    rawPath = rawPath.replace(unsafeCharsRegex, '')
     if (rawPath === '') { rawPath = 'home' }
 
     // Extract Info
-    let pathParts = _.filter(_.split(rawPath, '/'), p => !_.isEmpty(p))
+    let pathParts = _.filter(_.split(rawPath, '/'), p => {
+      p = _.trim(p)
+      return !_.isEmpty(p) && p !== '..' && p !== '.'
+    })
     if (pathParts[0].length === 1) {
       pathParts.shift()
     }
@@ -73,7 +79,7 @@ module.exports = {
       ['date', page.updatedAt],
       ['tags', page.tags ? page.tags.map(t => t.tag).join(', ') : ''],
       ['editor', page.editorKey],
-      ['dateCreated', page.createdAt],
+      ['dateCreated', page.createdAt]
     ]
     switch (page.contentType) {
       case 'markdown':
path: server/helpers/page.js