overleaf/services/web/app/coffee/Features/Project/SafePath.coffee
Simon Detheridge 56dcbefb5b Check for safe paths in all ProjectEntityHandler methods
Some import mechanisms (for example, Github project import) call methods such as 'upsert*' directly, bypassing existing filename checks.

Added checks to all methods in ProjectEntityHandler that can create or rename a file.

bug: overleaf/sharelatex#908
Signed-off-by: Simon Detheridge <s@sd.ai>
2018-10-08 15:31:04 +01:00

92 lines
2.8 KiB
CoffeeScript

# This file is shared between the frontend and server code of web, so that
# filename validation is the same in both implementations.
# Both copies must be kept in sync:
# app/coffee/Features/Project/SafePath.coffee
# public/coffee/ide/directives/SafePath.coffee
load = () ->
BADCHAR_RX = ///
[
\/ # no forward slashes
\\ # no back slashes
\* # no asterisk
\u0000-\u001F # no control characters (0-31)
\u007F # no delete
\u0080-\u009F # no unicode control characters (C1)
\uD800-\uDFFF # no unicode surrogate characters
]
///g
BADFILE_RX = ///
(^\.$) # reject . as a filename
| (^\.\.$) # reject .. as a filename
| (^\s+) # reject leading space
| (\s+$) # reject trailing space
///g
# Put a block on filenames which match javascript property names, as they
# can cause exceptions where the code puts filenames into a hash. This is a
# temporary workaround until the code in other places is made safe against
# property names.
#
# The list of property names is taken from
# ['prototype'].concat(Object.getOwnPropertyNames(Object.prototype))
BLOCKEDFILE_RX = ///
^(
prototype
|constructor
|toString
|toLocaleString
|valueOf
|hasOwnProperty
|isPrototypeOf
|propertyIsEnumerable
|__defineGetter__
|__lookupGetter__
|__defineSetter__
|__lookupSetter__
|__proto__
)$
///
MAX_PATH = 1024 # Maximum path length, in characters. This is fairly arbitrary.
SafePath =
# convert any invalid characters to underscores in the given filename
clean: (filename) ->
filename = filename.replace BADCHAR_RX, '_'
# for BADFILE_RX replace any matches with an equal number of underscores
filename = filename.replace BADFILE_RX, (match) ->
return new Array(match.length + 1).join("_")
# replace blocked filenames 'prototype' with '@prototype'
filename = filename.replace BLOCKEDFILE_RX, "@$1"
return filename
# returns whether the filename is 'clean' (does not contain any invalid
# characters or reserved words)
isCleanFilename: (filename) ->
return SafePath.isAllowedLength(filename) &&
!BADCHAR_RX.test(filename) &&
!BADFILE_RX.test(filename) &&
!BLOCKEDFILE_RX.test(filename)
# returns whether a full path is 'clean' - e.g. is a full or relative path
# that points to a file, and each element passes the rules in 'isCleanFilename'
isCleanPath: (path) ->
elements = path.split('/')
lastElementIsEmpty = elements[elements.length - 1].length == 0
return false if lastElementIsEmpty
for element in elements
return false if element.length > 0 && !SafePath.isCleanFilename element
return true
isAllowedLength: (pathname) ->
return pathname.length > 0 && pathname.length <= MAX_PATH
if define?
define [], load
else
module.exports = load()