Is storing sensitive data in files instead of a database safe?

The best measure to do this is to store such file on a directory outside the web root.

Basically, all of the solutions 'solve' the problem, in making the password file not available. Their differences stem on the kind of configuration error that needs to happen for it to break.

Surprisingly, such misconfigurations do happen from time to time, (e.g. when updating or reinstalling a system) and thee solutions simply vary on how robust they are to them. Also, sometimes these configurations are set in .htaccess files, but then a config changed at the server and they were not applied.

  1. Breaks if the web server generated an Index list.
  2. Fails if the piece configuring the web server not to serve .secret files was not added/applied, or if an editor left a backup file with an unprotected extension
  3. Would fail if the PHP files were not being interpreted (mod_php not loaded?) or if the password was not changed from the default.
  4. Would also fail if the PHP files were not being interpreted

Placing the file outside the web root (eg. /etc/php-app/hashed_passwords.txt) makes it harder that it gets downloaded by a malicious actor.

Obviously, nothing stops you from combining several of these methods (although at some point that would win you little).

Note: if using the .php route, you shall make sure that a malicious user cannot insert code into the generated files, leading to a code execution vulnerability. Generally this means you will die at the beginning (e.g. starting the file with <?php die(1);). You may also use __halt_compiler to ensure that no part of the rest of the file is interpreted by PHP.

The main difference I see on using files vs connecting to a database server (note you would face the same dilemma of how to name the file if you used SQlite) would be on its robustness for race conditions. Suppose that a user is performing a change password action at the same time that another one is signing up. A naive implementation using files would be vulnerable to a race condition where the recently created user would be lost when replacing the file with the new password hash, whereas a database would automatically care take of that (a naive implementation would be vulnerable to SQL injection, though).

Other differences would be at permissions, backups and scalability:

  • If the app connects to a database, the code itself could be read-only at the filesystem, whereas the files with information stored need write access there (and if you are using .php files, they need to be able to write into a file where code could be executed if written into, so a file-writing vulnerability would automatically become an execution vulnerability).
  • If there is already a system in place to backup the database server, that is easiest, rather than having to do a custom backup of a weirdly named file. On the other hand, using a file means that simply copying the directory results in a full backup, no need to dump the db separately.
  • If you may end up scaling horizontally, it is better to go with a normal database. Often simply making several nodes point to the same database make them all work (you should share the sessions, too). If you used a file database, that means you would have to share that file (by nfs, perhaps?) and have it writable by the multiple nodes (with all the added complication of race conditions, just with more nodes and a less capable filesystem layer).

And obviously, running a database server imposes certain overhead. If there isn't already a need to be using one, and the server resources are constrained, it may be preferable to use files simply to be lighter.

As you see, mostly the reasons that would lead your decision of using a file vs a database server are not about security, since you could have an equivalently secure app in both cases.

In an arbitrary sense a database is a file - in Unix systems it definitely is. I'm not sure why however, if you want to store anything in a file that you don't want accessible, you aren't storing it outside of web root. Doing that immediately renders bullet point one and two useless (plus there is no such thing as security through obscurity).

Bullet point 4 fails if an attacker can get the file to dump without executing. Number 3 gives an aspect of a solution.

One advantage of a database is full control of users that can read and write the database. These exist in a sense with file permissions but not nearly with the same degree of granularity.

This is all ignoring other advantages with a database, such as the speed of a lookup in a database; storing 1000 users in a file makes look ups a lot harder...backups would also become messy, and all the tools you need to secirely interact with a database are already there. Let's say you separate users and passwords with a colon; what if a user submits a name with a colon in?

A file containing one or two keys, outside web root, encrypted (without the key being hard-coded in the application) and readable only by root and PHP, where a database isn't available, might be justifiable and ok from a security point of view, but your listed solutions aren't useful. I've done this for systems where I am demonstrating an early PoC of how such s thing might work although never for any production service.

The problems

Security through obscurity: giving the text file a nonsense name that would be hard to guess (hashed_password_wefhbweifvbewuivgbwueigfvu4gf.txt)

This is only safe as long as nobody turns on the directory listing. If you are working with Apache + PHP it is a good rule of thumb that your PHP should be secure even for bad configurations of the Apache. Because in the end, god knows how the Apache will end up configured...

Also, there is the risk of accidental source code disclosure (see the third point).

Giving the text file a certain file extension (hashed_password.secret), and instructing the web server to return a 403 when it is requested

Same problem as above. I would say this is an improvement, though.

Encrypting the text file with a key hard-coded into the PHP application

Slightly better, but still not good. You still have the risk of accidental source code disclosure, which can easily happend with a misconfiguration or just a commit to a public git repo.

Encrypting the text file and storing the encryption key elsewhere (see this question for alternatives) would be much better.

Storing the information in a commented-out part of a PHP file, so it can only be read from the back end (and will return a blank file in a web browser)

Same issues as above, plus the very serious code execution issue Ángel writes about. I would absolutely avoid this.

The solution

Put the file outside of the web root! There it is less likely to be served accidentally. Hopefully it will also exclude it from all your git repos (it shouldn't be in them).

You could combine this with some combination of your first three suggestions. Do not use the forth!