Fortify user data path checks
| @@ -16,7 +16,7 @@ import { sync as writeFileAtomicSync } from 'write-file-atomic'; | |||
| 16 | import sanitize from 'sanitize-filename'; | 16 | import sanitize from 'sanitize-filename'; |
| 17 | 17 | ||
| 18 | import { USER_DIRECTORY_TEMPLATE, DEFAULT_USER, PUBLIC_DIRECTORIES, SETTINGS_FILE, UPLOADS_DIRECTORY } from './constants.js'; | 18 | import { USER_DIRECTORY_TEMPLATE, DEFAULT_USER, PUBLIC_DIRECTORIES, SETTINGS_FILE, UPLOADS_DIRECTORY } from './constants.js'; |
| 19 | import { getConfigValue, color, delay, generateTimestamp, invalidateFirefoxCache } from './util.js'; | 19 | import { getConfigValue, color, delay, generateTimestamp, invalidateFirefoxCache, isPathUnderParent } from './util.js'; |
| 20 | import { readSecret, writeSecret } from './endpoints/secrets.js'; | 20 | import { readSecret, writeSecret } from './endpoints/secrets.js'; |
| 21 | import { getContentOfType } from './endpoints/content-manager.js'; | 21 | import { getContentOfType } from './endpoints/content-manager.js'; |
| 22 | import { serverDirectory } from './server-directory.js'; | 22 | import { serverDirectory } from './server-directory.js'; |
| @@ -948,7 +948,11 @@ function createRouteHandler(directoryFn) { | |||
| 948 | try { | 948 | try { |
| 949 | const directory = directoryFn(req); | 949 | const directory = directoryFn(req); |
| 950 | const filePath = decodeURIComponent(req.params[0]); | 950 | const filePath = decodeURIComponent(req.params[0]); |
| 951 | const exists = fs.existsSync(path.join(directory, filePath)); | 951 | const fullPath = path.join(directory, filePath); |
| 952 | if (!isPathUnderParent(directory, path.resolve(fullPath))) { | ||
| 953 | return res.sendStatus(403); | ||
| 954 | } | ||
| 955 | const exists = fs.existsSync(fullPath); | ||
| 952 | if (!exists) { | 956 | if (!exists) { |
| 953 | return res.sendStatus(404); | 957 | return res.sendStatus(404); |
| 954 | } | 958 | } |
| @@ -971,13 +975,20 @@ function createExtensionsRouteHandler(directoryFn) { | |||
| 971 | try { | 975 | try { |
| 972 | const directory = directoryFn(req); | 976 | const directory = directoryFn(req); |
| 973 | const filePath = decodeURIComponent(req.params[0]); | 977 | const filePath = decodeURIComponent(req.params[0]); |
| 974 | 978 | const localPath = path.join(directory, filePath); | |
| 975 | const existsLocal = fs.existsSync(path.join(directory, filePath)); | 979 | if (!isPathUnderParent(directory, path.resolve(localPath))) { |
| 980 | return res.sendStatus(403); | ||
| 981 | } | ||
| 982 | const existsLocal = fs.existsSync(localPath); | ||
| 976 | if (existsLocal) { | 983 | if (existsLocal) { |
| 977 | return res.sendFile(filePath, { root: directory }); | 984 | return res.sendFile(filePath, { root: directory }); |
| 978 | } | 985 | } |
| 979 | 986 | ||
| 980 | const existsGlobal = fs.existsSync(path.join(PUBLIC_DIRECTORIES.globalExtensions, filePath)); | 987 | const globalPath = path.join(PUBLIC_DIRECTORIES.globalExtensions, filePath); |
| 988 | if (!isPathUnderParent(PUBLIC_DIRECTORIES.globalExtensions, path.resolve(globalPath))) { | ||
| 989 | return res.sendStatus(403); | ||
| 990 | } | ||
| 991 | const existsGlobal = fs.existsSync(globalPath); | ||
| 981 | if (existsGlobal) { | 992 | if (existsGlobal) { |
| 982 | return res.sendFile(filePath, { root: PUBLIC_DIRECTORIES.globalExtensions }); | 993 | return res.sendFile(filePath, { root: PUBLIC_DIRECTORIES.globalExtensions }); |
| 983 | } | 994 | } |