Following up on a previous post about Command Injection Vulnerabilities, this post is going to look at File Access Vulnerabilities.

File Access vulnerabilities fall under the category of Insecure Direct Object Reference vulnerabilities in the OWASP top 10 lists. And for 2010 and 2013, Insecure Direct Object vulnerabilities were number 3 for both years. tada confetti_ball fireworks

What is a File Access Vulnerability?

A File Access vulnerability is when an attacker can use various calls to create, modify, or delete files on your server’s file system or a remote file system (eg: S3) that they shouldn’t have permission to modify. Here’s an example of a call that would allow an attacker to link your database file into the public directory of a Rails server:

1 2 3 4 5 6 7 8 9 # http://domain.com?payload=config/database.yml payload = params [ :payload ] path = Rails . root . join ( payload ) id = SecureRandom . uuid File . link ( path , "public/ #{ id } " ) redirect_to "/ #{ id } "

While this example is contrived and code in the wild is not likely to look this obvious, the above is a perfect example of how this type of attack functions. The attacker is able to manipulate your code into linking (and therefore exposing) a file that you wouldn’t want leaked.

Now the difficult thing is that there are an enormous number of methods that are vulnerable to File Access attacks. Pulling from the Brakeman source code, we can create a list of methods where a File Access vulnerability could occur:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 # As of Oct 25, 2015 # From: https://github.com/presidentbeef/brakeman/blob/d2d49bd61f2d77919df17fd8dce6193cf1d1ada2/lib/brakeman/checks/check_file_access.rb#L11-L27 # Dir: Dir [] Dir . chdir Dir . chroot Dir . delete Dir . entries Dir . foreach Dir . glob Dir . new Dir . open Dir . rmdir Dir . unlink # File File . delete File . foreach File . lchmod File . lchown File . link File . new File . open File . read File . readlines File . rename File . symlink File . sysopen File . truncate File . unlink # FileUtils FileUtils . cd FileUtils . chdir FileUtils . chmod FileUtils . chmod_R FileUtils . chown FileUtils . chown_R FileUtils . cmp FileUtils . compare_file FileUtils . compare_stream FileUtils . copy FileUtils . copy_entry FileUtils . copy_file FileUtils . copy_stream FileUtils . cp FileUtils . cp_r FileUtils . getwd FileUtils . install FileUtils . link FileUtils . ln FileUtils . ln_s FileUtils . ln_sf FileUtils . makedirs FileUtils . mkdir FileUtils . mkdir_p FileUtils . mkpath FileUtils . move FileUtils . mv FileUtils . pwd FileUtils . remove FileUtils . remove_dir FileUtils . remove_entry FileUtils . remove_entry_secure FileUtils . remove_file FileUtils . rm FileUtils . rm_f FileUtils . rm_r FileUtils . rm_rf FileUtils . rmdir FileUtils . rmtree FileUtils . safe_unlink FileUtils . symlink FileUtils . touch # IO IO . foreach IO . new IO . open IO . read IO . readlines IO . sysopen # Kernel Kernel . load Kernel . open Kernel . readlines # Net::FTP Net : :FTP . new Net : :FTP . open # Net::HTTP Net : :HTTP . new # PStore PStore . new # Pathname Pathname . glob Pathname . new # Shell Shell . new # YAML YAML . load_file YAML . parse_file

That’s a nasty long list. And what it means is when you make one of these calls, if you’re using input a user controls then they can attack your system!

One of the things to note about the above list is that you’re never going to find methods like .foreach or .sysopen in the File docs. This is because File inherits from IO . What you need to recognize, is that if you’ve created any special classes that inherit from File , IO , YAML , etc. in your app, those won’t get caught by Brakeman!

To top it all off, there are numerous different types of attacks that could performed. They’re all dangerous and slightly different:

1 2 3 4 5 6 7 8 9 Filling up disk space: FileUtils.copy, FileUtils.cp, File.new, IO.new, PStore.new Move a file to a downloadable location: File.rename, FileUtils.move Linking a file to a downloadable location: File.link, File.symlink, FileUtils.link, FileUtils.ln Bricking your server (DoS): Dir.delete, FileUtils.rm Changing permissions to directories (DoS): File.chmod, File.chown, FileUtils.chmod, FileUtils.chown Renaming key files: File.rename, FileUtils.move, FileUtils.mv Leaking paths: FileUtils.pwd Downloading malicious files onto your server: Net::FTP.new, Net::HTTP.new Launch an attack against another website: Net::FTP.new, Net::HTTP.new

Some are more harmful than others and typically an attacker is going to leverage one or more of these vulnerabilities to escalate their privileges to own your system. From wikipedia:

Privilege escalation is the act of exploiting a bug, design flaw or configuration oversight in an operating system or software application to gain elevated access to resources that are normally protected from an application or user.

How do you Fix File Access Vulnerabilities?

The best technique for preventing File Access vulnerabilities is not allowing them happen in the first place and avoiding unnecessary system level operations.

Thanks Captain Obvious! facepalm

While that advice is correct, it’s not necessarily good or helpful, so let’s look at the techniques you can use to keep File Access attacks from happening when you do need to work with your system.

Restriction via Identifier

The first way to do that is by using an identifier to refer to files on disk. This identifier will take the form of an id, hash, or GUID.

1 2 3 4 5 6 7 8 9 10 # HTML < select name = "file_guid" > < option value = "690e1597-de8d-4912-ac04-d0e626f806f4" > file1 . log < /option> <option value="2e157fa3-ea1e-4b46-931e-c0f8b10bfcb2">file2.log</o ption > < option value = "fffb938b-07bc-472c-a48f-383123a9f04d" > file3 . log < /option> </se lect > # Controller download = FileDownload . find_by ( file_guid : params [ :file_guid ] ) send_file ( download . path , filename : download . name , type : "text/plain" )

Notice in the above code that a GUID is used as the value that gets submitted to the server, and not the actual file name. This makes it impossible for an attacker to download a file they’re not allowed to, and also keeps you safe from any manipulation of the file name or path. This technique will work for moving, deleting, renaming, and sending files as long as you know files names and paths ahead of time. It is the best way to secure your app.

Partial Restriction

Ideally you wouldn’t have to resort to any other techniques for protection, however the real world is a bit messier. And sometimes you don’t have all the information you need in order to use an identifier. In cases like this you want to “sandbox” your users as much as possible by limiting access within the file system:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # HTML < select name = "file_name" > < option value = "file1.log" > file1 . log < /option> <option value="file2.log">file2.log</o ption > < option value = "file3.log" > file3 . log < /option> </se lect > # Controller file_name = sanitize ( params [ :file_name ] ) # if possible current_user.download_directory should be an identifier # and controlled 100% by the server. download_path = "downloads/ #{ current_user . download_directory } / #{ file_name } " if File . exists? ( download_path ) send_file download_path , filename : file_name , type : "text/plain" else # return an error message end

Here you can use a sanitize function to clear params[:file_name] of any dangerous characters. In this way you’re accessing the file system in a controlled manner.

Filtered Restriction

The next technique to limit file access trouble is by restricting to specific file types. Here you want to whitelist the types of files that a user can access, such as only .pdf files on the server:

1 2 3 4 5 6 payload = sanitize ( params [ :filename ] ) if payload =~ /.pdf$/ send_file ( "downloads/ #{ payload } " , filename : 'report.pdf' , type : "application/pdf" ) else raise "Unknown file format requested" end

This is a line of defense that makes sure that you’re not leaking any sensitive information like a database.yml file. And again make sure to use a sanitize function!

The place you have to be careful here is that whitelisted file extensions can be exploited if an attacker is able to move or rename files. Specifically if they are able to add a .pdf extension to database.yml then they’re able to download the database.yml.pdf file. That’s where multiple vulnerabilities come in as mentioned before. An attacker uses one File Access vulnerability to rename the file, and another to download it.

Store User Files on a Different Server

These days disk space is cheap. One great way to avoid opening your web server up to compromise is to limit data stored on the system. This means leveraging tools like Amazon S3, or DreamHost’s Dream Objects to store user files, generated reports, etc. on a server that is loosely coupled to your app.

As I mentioned in the opening paragraph of this post, you can still shoot yourself in the foot and have an attacker gain access to files they shouldn’t with external storage. Storing your files externally simply separates systems (called a boundary) so that a compromise of your data storage system, doesn’t also compromise your web server.

The added benefit of storing data on other servers is that it will help you scale the load your servers can handle and can reduce processing cycles for those files.

Use an Intermediary

One of the great tools to come out of the “dev ops revolution” is Chef. I use Chef on a regular basis and within the apps that I develop our team uses Chef to manage server configurations. With Chef you can create a boundary between your Ruby/Rails app and your configuration code. Then when you’re passing information between the web app and chef you can ask yourself: “Is this data dangerous?” It’s a subtle distinction and if you’re doing enough system calls it’s worth the investment.

But before you jump on the Chef bandwagon, having an intermediary isn’t going to solve the File Access problem. At the end of the day you’re going to need to pay attention to what you’re doing. The nice bit about Chef is that you can come up with ground rules on your team like:

No system calls in main app, only in Chef

Heavily sanitized user input, used sparingly in Chef

Code Review by two or more people for Chef changes

Quarterly review of chef code for vulnerabilities

You get the idea, create a separation of concerns between safe code and hazardous code!

Use Dangerous Methods Sparingly

There’s a good chance that a lot of the methods listed above won’t be useful for you. And really that’s the best case scenario. At the end of the day, not using a dangerous method is the #1 technique for keeping your app safe.

When you’re being asked to implement the amazing new feature that involves file access, you can provide constructive feedback on potential harms that these types of features can bring to the table.