Monday, October 19, 2009

Passwords in php scripts

Putting passwords in your php scripts is dangerous for a number of reasons, as for example:
  1. the php files are usually world-readable, meaning that anyone with shell access to the server has full access to your db passwords, even if they only have an unprivileged account
  2. if you are using version control, the passwords are needlessly replicated to all the tree checkouts. If someone's home machine gets compromised, you will have to change all the database passwords as well. Changing database passwords always involves a blip in uptime because changing the password in your db software is not simultaneous with changing it in your web scripts.
  3. the same applies if an employee in your organization leaves.
  4. if for some reason the apache configuration becomes screwed up and your php handler disappears, then the server will output the php code as text, exposing all passwords contained within.
Or, to illustrate, let's imagine the following scenario. You have two applications, one you trust (example.com/trusted), and the other not so much (example.com/untrusted). One day there is a remote exploit in the untrusted app that allows remote code execution. This would let an attacker read any files readable by the apache process, and therefore any passwords used by the trusted application would be accessible to the attacker, if they are stored in the application source. As a consequence, the data used by the trusted application is compromised because the attacker has access to the mysql database used by /trusted.

The solution is to store these passwords separately from your main app tree and only readable by root. Doing a simple php include from somewhere outside the web tree will problem #4 above, but it still won't address the other concerns. If you have full admin control of your apache server, you can go further than that.

Apache reads config files as root, so we'll take advantage of that.
  1. Create a /etc/httpd/conf.d/passwords.conf
  2. Put your password information in the form:

    <Directory "/var/www/path/to/your/app">
        # disable phpinfo, to prevent accidental leaks
        php_admin_value disable_functions phpinfo

        SetEnv mysql_user_password SecretPassXX
        SetEnv some_other_password AnotherPassXX
    </Directory>


  3. Set permissions on passwords.conf by doing:
    chown root:root passwords.conf
    and
    chmod 0600 passwords.conf
    Now this file is only readable by root.
  4. You can now access these passwords from your scripts using:
    $_SERVER['mysql_user_password'].
The benefits of this solution are:
  1. The passwords are not accessible outside your application's immediate execution environment
  2. The only way an attacker can get these passwords is to execute arbitrary code in the context of your application, which is quite a bit harder than simply being able to output arbitrary files (e.g. via directory traversal vulnerabilities)
  3. Underprivileged accounts, including php scripts that are executed in another directory can't get to the passwords
In terms of our example with /trusted and /untrusted, even if /untrusted gets compromised, the attacker would not be able to obtain the passwords used by /trusted (the config file containing the passwords is only readable by root, and SetEnv directives with password values are only set in /trusted location -- unavailable in /untrusted even if the attacker has full access to $_SERVER).

There are caveats with this solution as well. E.g. you have to be careful about doing things like phpinfo() or var_dump($_SERVER) (which is why I include the code to disable the phpinfo() using the php_admin_value disable_functions phpinfo parameter).

I think overall this helps significantly tighten the security of web applications, especially in shared environments.

1 comment:

Eric Lamb said...

Good idea but my head starts to hurt when I start to think about adding yet another requirement to a system.

Still, for in-house, stable, projects I can see the appeal.