summaryrefslogtreecommitdiff
path: root/lib/gitlab/utils.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gitlab/utils.rb')
-rw-r--r--lib/gitlab/utils.rb38
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