fix: admin/fs: file/folder names weren't validated

Massimo Melina committed Feb 3, 2024 at 14:43 UTC 5c471ef55b2b8ca4ddf95fa566037ad7632a947e
4 files changed +11 -6
src/api.vfs.ts
+7 -2
@@ -6,7 +6,8 @@ import _ from 'lodash'
6 import { mkdir, stat } from 'fs/promises'
7 import { ApiError, ApiHandlers } from './apiMiddleware'
8 import { dirname, extname, join, resolve } from 'path'
9 -import { dirStream, enforceFinal, isDirectory, isWindowsDrive, makeMatcher, PERM_KEYS, VfsNodeAdminSend } from './misc'
9 +import { dirStream, enforceFinal, isDirectory, isValidFileName, isWindowsDrive, makeMatcher, PERM_KEYS,
10 + VfsNodeAdminSend } from './misc'
11 import { IS_WINDOWS, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR, HTTP_CONFLICT, HTTP_NOT_ACCEPTABLE } from './const'
12 import { getDrives } from './util-os'
13 import { Stats } from 'fs'
@@ -88,6 +89,8 @@ const apis: ApiHandlers = {
89 return new ApiError(HTTP_NOT_FOUND, 'path not found')
90 props = pickProps(props, ALLOWED_KEYS) // sanitize
91 if (props.name && props.name !== getNodeName(n)) {
92 + if (!isValidFileName(props.name))
93 + return new ApiError(HTTP_BAD_REQUEST, 'bad name')
94 const parent = await urlToNodeOriginal(dirname(uri))
95 if (parent?.children?.find(x => getNodeName(x) === props.name))
96 return new ApiError(HTTP_CONFLICT, 'name already present')
@@ -103,6 +106,8 @@ const apis: ApiHandlers = {
106 async add_vfs({ parent, source, name, ...rest }) {
107 if (!source && !name)
108 return new ApiError(HTTP_BAD_REQUEST, 'name or source required')
109 + if (!isValidFileName(name))
110 + return new ApiError(HTTP_BAD_REQUEST, 'bad name')
111 const parentNode = parent ? await urlToNodeOriginal(parent) : vfs
112 if (!parentNode)
113 return new ApiError(HTTP_NOT_FOUND, 'parent not found')
@@ -133,7 +138,7 @@ const apis: ApiHandlers = {
138
139 async del_vfs({ uris }) {
140 if (!uris || !Array.isArray(uris))
136 - return new ApiError(HTTP_BAD_REQUEST, 'invalid uris')
141 + return new ApiError(HTTP_BAD_REQUEST, 'bad uris')
142 return {
143 errors: await Promise.all(uris.map(async uri => {
144 if (typeof uri !== 'string')
src/frontEndApis.ts
+1 -1
@@ -51,7 +51,7 @@ export const frontEndApis: ApiHandlers = {
51
52 async create_folder({ uri, name }, ctx) {
53 apiAssertTypes({ string: { uri, name } })
54 - if (!isValidFileName(name) || dirTraversal(name))
54 + if (!isValidFileName(name))
55 return new ApiError(HTTP_BAD_REQUEST, 'bad name')
56 const parentNode = await urlToNode(uri, ctx)
57 if (!parentNode)
src/util-files.ts
+1 -1
@@ -153,7 +153,7 @@ export function createFileWithPath(path: string, options?: Parameters<typeof cre
153 }
154
155 export function isValidFileName(name: string) {
156 - return !/^\.\.?$|[/:*?"<>|\\]/.test(name)
156 + return !/^\.\.?$|[/:*?"<>|\\]/.test(name) && !dirTraversal(name)
157 }
158
159 const FILE_ATTR_PREFIX = 'user.hfs.' // user. prefix to be linux compatible
src/vfs.ts
+2 -2
@@ -2,7 +2,7 @@
2
3 import fs from 'fs/promises'
4 import { basename, dirname, join, resolve } from 'path'
5 -import { dirStream, dirTraversal, enforceFinal, getOrSet, isDirectory, makeMatcher, setHidden, onlyTruthy,
5 +import { dirStream, enforceFinal, getOrSet, isDirectory, makeMatcher, setHidden, onlyTruthy, isValidFileName,
6 throw_, VfsPerms, Who, isWhoObject, WHO_ANY_ACCOUNT, defaultPerms, PERM_KEYS, removeStarting } from './misc'
7 import Koa from 'koa'
8 import _ from 'lodash'
@@ -117,7 +117,7 @@ export async function urlToNode(url: string, ctx?: Koa.Context, parent: VfsNode=
117 }
118
119 export async function getNodeByName(name: string, parent: VfsNode) {
120 - if (dirTraversal(name) || /[\\/]/.test(name)) return
120 + if (!isValidFileName(name)) return
121 // does the tree node have a child that goes by this name, otherwise attempt disk
122 const child = parent.children?.find(isSameFilenameAs(name)) || childFromDisk()
123 return child && applyParentToChild(child, parent, name)