If you’re storing your database password + username and other secure information in just any old .php file in your application, you’re doing it so, very, very, very wrong. If you must physically store these keys to the castle, the old method with Apache used to be SetEnv. Of course, not everyone uses Apache these days (I use Nginx on my *nix boxes).
The best place to store these things is in an .ini file. Specifically, for content that rarely, if ever, changes (I.E. database connection strings) it should be php.ini. Every PHP installation should have one and if you don’t have access to this, it’s time to switch web hosts.
In your php.ini, you can add the following or equivalent settings somewhere in the bottom.
[MyCustomApp] myapp.cfg.DB_HOST = 'mysql:host=127.0.0.1;dbname=mydatabase' myapp.cfg.DB_USER = 'dbusername' myapp.cfg.DB_PASS = 'dbpassword'
Note: MyCustomApp is just the configuration label set to that particular group of settings. It’s good practice to give labels to your configuration settings and group them together. Especially if you move on to have a lot more of them later on.
Here is a very simple bit of code to load the above settings into globally defined variables :
// Very simple loader function loadConfig( $vars = array() ) { foreach( $vars as $v ) { define( $v, get_cfg_var( "myapp.cfg.$v" ) ); } } // Then call : $cfg = array( 'DB_HOST', 'DB_USER', 'DB_PASS' ); loadConfig( $cfg );
Doing this is the far more secure method of setting up most other applications (including *cough* WordPress) as opposed to the old-school way, which I’m sure anyone who’s setup any PHP app in the past has dealt with:
// Ordinary config.php or some such file // (I.E. DON'T DO THIS ANY MORE) define( 'DB_HOST', 'mysql:host=127.0.0.1;dbname=mydatabase' ); define( 'DB_USER', 'dbusername' ); define( 'DB_PASS', 'dbpassword' );
The best way to prevent information you have on your hands falling into the wrong hands is to not have it in your hands. If some misconfiguration results in raw PHP files being served as text files (this happens far more often than you might think), the only thing you’ve exposed is just the site code, not your DB credentials AWS passwords, secret salts etc…
Caveats
As mentioned above, not everyone will have access to php.ini from their web host (which, as I also said, is a good hint it’s time to switch hosts). You will also need to reload PHP to ensure the new configuration changes will take effect. It’s possible to gracefully shutdown and restart these days, but that will mean a tinsey bit of down time of a few seconds at least so this will need to be done for configuration settings that are critical and yet will change infrequently. Or, if you’re using PHP-FPM with Nginx, you can start another FastCGI instance and have Nginx fail over to that.
Addendum
The PDO driver for MySQL for some reason demands the username and password separately. I thought this is kinda silly since other drivers (E.G. Postgresql) can function just fine with a connection string such as :
pgsql:host=localhost;port=5432;dbname=testdb;user=bruce;password=mypass
Well, to keep the MySQL driver and many others happy, I’ve written a small helper class that intercepts the connection string and breaks it down so the username and password can be kept separate. It also works with the above php.ini trick in that you can now store a complete connection string as php.dsn.mydb or the like as shown in the PDO docs.
/** * PDO Connector class * Modifies the DSN to parse username and password individually. * Optionally, gets the DSN directly from php.ini. * * @author Eksith Rodrigo <reksith at gmail.com> * @license http://opensource.org/licenses/ISC ISC License * @version 0.1 */ class Cxn { protected $db; public function __construct( $dbh ) { $this->connect( $dbh ); } public function getDb() { if ( is_object( $this->db ) ) { return $this->db; } else { die('There was a database problem'); } } public function __destruct() { $this->db = null; } private function connect( $dbh ) { if ( !empty( $this->db ) && is_object( $this->db ) ) { return; } try { $settings = array( PDO::ATTR_TIMEOUT => "5", //PDO::ATTR_EMULATE_PERPARES => false, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_PERSISTENT => false ); $this->_dsn( $dbh, $username, $password ); $this->db = new PDO( $dbh, $username, $password, $settings ); } catch ( PDOException $e ) { exit( $e->getMessage() ); } } /** * Extract the username and password from the DSN and rebuild */ private function _dsn( &$dsn, &$username = '', &$password = '' ) { /** * No host name with ':' would mean this is a DSN name in php.ini */ if ( false === strrpos( $dsn, ':' ) ) { /** * We need get_cfg_var() here because ini_get doesn't work * https://bugs.php.net/bug.php?id=54276 */ $dsn = get_cfg_var( "php.dsn.$dsn" ); } /** * Some people use spaces to separate parameters in * DSN strings and this is NOT standard */ $d = explode( ';', $dsn ); $m = count( $d ); $s = ''; for( $i = 0; $i < $m; $i++ ) { $n = explode( '=', $d[$i] ); // Empty parameter? Continue if ( count( $n ) <= 1 ) { $s .= implode( '', $n ) . ';'; continue; } switch( trim( $n[0] ) ) { case 'uid': case 'user': case 'username': $username = trim( $n[1] ); break; case 'pwd': case 'pass': case 'password': $password = trim( $n[1] ); break; default: // Some other parameter? Leave as-is $s .= implode( '=', $n ) . ';'; } } $dsn = $s; } }
You can use this class with :
$cxn = new Cxn( DBH );
Where DBH came from (hopefully) php.ini.
xzcxzcxzcxzcxz
Pingback: Defensive web development | This page intentionally left ugly
What happen if somebody put a phpinfo(); script in your public root?
Appear the username and password in text plain.
Disable phpinfo()