diff options
Diffstat (limited to 'lib/gitlab/utils.rb')
-rw-r--r-- | lib/gitlab/utils.rb | 38 |
1 files changed, 29 insertions, 9 deletions
diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb index 8f5c1eda456..e2d93e7cd29 100644 --- a/lib/gitlab/utils.rb +++ b/lib/gitlab/utils.rb @@ -7,30 +7,50 @@ module Gitlab # Ensure that the relative path will not traverse outside the base directory # We url decode the path to avoid passing invalid paths forward in url encoded format. - # We are ok to pass some double encoded paths to File.open since they won't resolve. # Also see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24223#note_284122580 # It also checks for ALT_SEPARATOR aka '\' (forward slash) - def check_path_traversal!(path, allowed_absolute: false) - path = CGI.unescape(path) - - if path.start_with?("..#{File::SEPARATOR}", "..#{File::ALT_SEPARATOR}") || - path.include?("#{File::SEPARATOR}..#{File::SEPARATOR}") || - path.end_with?("#{File::SEPARATOR}..") || - (!allowed_absolute && Pathname.new(path).absolute?) + def check_path_traversal!(path) + path = decode_path(path) + path_regex = /(\A(\.{1,2})\z|\A\.\.[\/\\]|[\/\\]\.\.\z|[\/\\]\.\.[\/\\]|\n)/ + if path.match?(path_regex) raise PathTraversalAttackError.new('Invalid path') end path end + def allowlisted?(absolute_path, allowlist) + path = absolute_path.downcase + + allowlist.map(&:downcase).any? do |allowed_path| + path.start_with?(allowed_path) + end + end + + def check_allowed_absolute_path!(path, allowlist) + return unless Pathname.new(path).absolute? + return if allowlisted?(path, allowlist) + + raise StandardError, "path #{path} is not allowed" + end + + def decode_path(encoded_path) + decoded = CGI.unescape(encoded_path) + if decoded != CGI.unescape(decoded) + raise StandardError, "path #{encoded_path} is not allowed" + end + + decoded + end + def force_utf8(str) str.dup.force_encoding(Encoding::UTF_8) end def ensure_utf8_size(str, bytes:) raise ArgumentError, 'Empty string provided!' if str.empty? - raise ArgumentError, 'Negative string size provided!' if bytes.negative? + raise ArgumentError, 'Negative string size provided!' if bytes < 0 truncated = str.each_char.each_with_object(+'') do |char, object| if object.bytesize + char.bytesize > bytes |