Create directories securely and prohibit illegal access

Currently, we use the Dir.mkdir or FileUtils.mkdir methods to create directories. They are convenient but not secure.

For example, I create a new directory under the /home directory, and the directory name is entered based on a username:

def create_user_home(username)
   Dir.mkdir("/home/#{username}")
end

When the username contains .., the parent directory restrictions can be bypassed:

create_user_home("../user1")

This will cause the directory to be created in /user1 instead of /home/user1. Although I can restrict the username, but it did not eliminate the risk fundamentally.

I think you can use File.basename(path) like this:

def create_user_home(username)
   path = File.basename(username)
   Dir.mkdir("/home/#{path}")
end

If basename does its job, this should guarantee no escaping into higher directories.

Looking at the Crystal source, it looks like path is passed along to the C standard library mkdir:

I would just add a sanitizer to your method? Something like this:

def create_user_home(username)
  # Pre-conditions
  raise ArgumentError.new "username is nil" unless username
  raise ArgumentError.new "username is blank" if username.blank?
  
  # Sanitize username
  sanizited_username = username.gsub(/^\.\.\//, "")
  
  # Make user home directory
  Dir.mkdir("/home/#{sanitized_username}")
end
./../../home/whatever

this will still escape to higher directories and “.hello” is a legal filename.

I think using basename is safer than rolling your own protection.

2 Likes

Seems to me that if you’re worried about the value of “username” (because it is typed in by some untrusted user), then you’d use File.expand_path before doing the mkdir, check the result of that, and only do the mkdir if the full pathname is where you expect it to be.

This means this application is running as root, which is indeed dangerous.
Sometimes it’s necessary for very specific application modifying the system, but if you can avoid it - do it for this kind of security issue.

Is it possible in your case to have a dedicated user, which has only access to a specified root directory?