Hacker News new | past | comments | ask | show | jobs | submit login
1% of CMS-Powered Sites Expose Their Database Passwords (2011) (feross.org)
134 points by feross on Feb 4, 2013 | hide | past | favorite | 90 comments



0. Put local config files outside of webroot or other publicly accessible directories, include programatically.

1. Disable swap/backup files in your editor (or write them to a different location)[1]

2. Git ignore or SVN ignore swap files and backup files

3. Configure your web server to not serve such files.

Some combination of these should keep you safe. :)

[1]: in vim: noswap nobackup nowritebackup or http://vim.wikia.com/wiki/Remove_swap_and_backup_files_from_...


Step 0 is the only way to do it. I'm constantly stunned to see how many PHP apps violate this rule.


You've obviously never had an amateur developer run smack-into open_basedir issues before. The application basically gets a 404 error when including dependencies, until they look for open_basedir configuration. A developer really needs to know when open_basedir has restricted an include, because the error is really vague.

And shared hosts don't generally allow files outside the webroot of a site. When they do, they might already have open_basedir set to only allow inclusion WITHIN the webroot.

Most packages like Wordpress are developed to run on the maximum number of servers. It isn't built to run on the best, or smartest configured ones—but the average, out-of-the-box Apache/PHP install.


The PHP ecosystem has some really awful defaults which is why it's developed such a bad name for itself. It may be that many of these are there to accommodate misconfigured web hosts, but this behavior has a way of self-perpetuating.

Example: PHP 5.5 will be running for the next billion years because when mysql_query is finally put to rest in newer versions all those applications that depend on it will fall over. Thus, a legacy version of PHP will be supported by hosts, bugs, security holes and all.

"You either die a hero, or live long enough to see yourself become the villain..."

PHP crossed the line from hero long ago.


Actually, it comes back to the user, not so much the coder. After I left a job a while back they hired some random as a replacement. The replacement had no Linux experience, so the next best thing for them was to tell the owner to move all software to a shared host (which they had had experience with before).

Everything was written below a public root, but the shared host didn't allow this, so the next thing you saw was: domain.com/publicroot/index.php^ when the new developer took over. The funniest thing was that they had internal subdomains which ended up public as well, one with full customer listings.

"Never attribute to malice that which is adequately explained by stupidity." - Hanlon's Razor

^ fake path


4. Don't make live edits on your production server.


5. FTP is not a deployment tool.


Lol. That's just called statistics.

I bet you 1% of every web site or server in existence does something so blatantly and unthinkably wrong that it would make any sysadmins eyes pop out of his head.

1% is in fact extremely low in my opinion—my guess would be closer to 10-15% of sites have some glaring security hole.

The only important thing is to not let it be your site.


> I bet you 1% of every web site or server in existence does something so blatantly and unthinkably wrong that it would make any sysadmins eyes pop out of his head.

I wonder what the percentages are for cops, doctors, judges, and pilots are?


Or a site holding data of yours, which is harder.


Exactly. This is why you use different passwords for every site.


Prevent nginx from serving files that start with a period or tilde will help.

    # prevent hidden files from being served and logged
    location ~ /\. {
      access_log off;
      log_not_found off;
      deny all;
    }

    # prevent tilde files from being served and logged
    location ~ ~$ {
      access_log off;
      log_not_found off;
      deny all;
    }


This is a bit of a hack, since you don't really know how the "backup" file will look like. It might end with tilde, but it might end with .bkp. It's impossible to iterate through all the possibilities, just like it's impossible to separate them from valid files.

A much cleaner solution would be to separate "callable" entry-point files (like index.php) from "library" files into separate directories and point nginx/Apache only to the directory with callable files.


How utterly stupid do you have to be to engineer software that requires you to have configuration files in a publicly accessible folder? Even most shared hosts now will have a public_html folder and the ability to put sensitive stuff not inside it.

The .htaccess hacks are great but they are just patching the symptom, and one slip up and you're back to square one.

The best way to do configuration is to have it in environment variables that are populated in a completely separate config file for the specific instance, so for instance a uWSGI ini file.

This is very easy with Django and uWSGI.

In your uWSGI.ini:

    env = DJANGO_SETTINGS_MODULE=yoursite.settings.production
    env = DJANGO_SECRET_KEY=herp
    env = DJANGO_DB_PASSWORD=derp
In yoursite/settings/production.py

    from .base import *
In yoursite/settings/base.py

    import os
    
    DATABASES = {
        'default': {
            ...
            'PASSWORD': os.environ.get('DJANGO_DB_PASSWORD'),
            ...
        }
    }

    SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
I'm not sure if this is possible with WordPress or the whole shitty PHP way of doing things - perhaps if you had a FPM pool for each site and site specific configuration in there?


How utterly stupid do you have to be to engineer software that requires you to have configuration files in a publicly accessible folder? Even most shared hosts now will have a public_html folder and the ability to put sensitive stuff not inside it.

Complete untrue. You can (and are advised to) move your wp-config.php above public_html. The reason its not done by default is because you just can't do this on many of the crappy hosting services, and it creates a barrier for entry for people making their first-ever site.


Except you can on 99% of crappy hosts. I remember even Tripod allowed you to put things outside of public_html.


But things inside public_html can't read files outside of it. I believe cPanel is configured this way be default.


    The best way to do configuration is to have it in
    environment variables
Isn't it the only sensible way, actually?

Django doesn't expose static files the same way, so you still get away with a secret.txt stored as a file.


Because your average graphics designer or blogger has no idea what Apache is, what public directories or file permissions are. They just want to run their own blog. That's why Wordpress (or PHP for that matter) is successful; it's easy to set up and get started.


In that case why not just host it at wordpress.com where it's managed by people who are (presumably) more experienced?


There could be many reasons, not the least of which is that the hosting provider would be providing the installation and updates for the user.

So, they are paying for it by people who are presumably more experienced.

Edit: Just to be clear, I'm not excusing any of the mistakes. Just pointing out how normal people could get screwed over, even if they are using a "professional service" and paying for it.


You are correct, you could do almost exactly the same thing in "the whole shitty PHP way of doing things" using FPM pools.


Excellent! Part of me wants to have a play and see how closely I can get a PHP/FPM/nginx to resemble my Python/uWSGI Emperor/nginx stack.

You can do stuff like include /srv/*/pool.ini, right?


Yes you can. Dotdeb's php-fpm.ini even includes this line by default:

    ; To configure the pools it is recommended to have one .conf file per
    ; pool in the following directory:
    include=/etc/php5/fpm/pool.d/*.conf
Then you could configure ENV inside FPM pool using the env directive:

    env[DB_HOST] = localhost
    env[DB_USER] = foobar
    env[DB_PASSWORD] = foobar
...and use something like this in e.g. Wordpress config:

    define('DB_NAME', getenv('DB_NAME'));
    define('DB_USER', getenv('DB_USER'));
    define('DB_PASSWORD', getenv('DB_PASSWORD'));
The biggest caveat is you MUST disable phpinfo()

    php_value[disable_functions] = phpinfo
Otherwise these ENV will shown up in any page that calls phpinfo();


That's pretty handy. Didn't think about the phpinfo thing, actually, though in all fairness if they managed to upload any PHP files you're rather screwed. I think the way to be secure would be to not actually serve PHP files in the document root and instead have nginx proxy / directly to the FPM pool, and just alias your static files over, much as one would with Django.


If you wanna see php done right, forget about all the crap you have seen and take a look at symfony.com. It has great docs, that focus on teaching web development (not just a framework) and all these sysadmin best practices like how to set the permissions, etc. Trust me, I have used every popular framework (including Rails and Django), and Symfony2 has nothing to envy.


uWSGI is capable of serving PHP, mind.


The fundamental problem with many PHP applications is that the php files are located in document root, i.e. can be potentially served as static files. More correct approach would be to put them outside document root (e.g. APP_DIR/lib), and using index.php which requires them (e.g. APP_DIR/static/index.php).


I'm not sure this is fair to pin on PHP in a broad way. Many PHP frameworks do not use the application file-structure root as the httpd document root.

For example with a Symfony app:

. - App root

./web/ - httpd doc root

./app/ - app files

./app/config/ - config files

Barring an exploit that lets you break out of the httpd doc root (not saying this is impossible), there is no way to request the config or app files directly.


That's not restricted to PHP, since it does not even depend on the language.


True, but other languages are not typically processed in the traditional mod_php way where urls are directly mapped to files in your static root, thus it's extremely rare to see e.g. python or ruby files served as static files.


I don't know what you mean. You just tell the web server what you want to do, and it will do it. Eg: "Process only files in this dir, and only accept requests to this kind of URL. And any request to that kind URL, pass it to this single php file".

Do software like XAMP have bad defaults and shouldn't be used for production out of the box? Maybe.

Has that helped a lot of people to get started, and make PHP the most popular server language for the web? Definitely.

Should we mock all php devs including experts, because of designers or ninja CEOs who tried/succeeded in editiing or making a website but made some mistakes in the process? The answer is still no.


The title of the linked post is 1% of CMS-Powered Sites Expose Their Database Passwords - has it changed since submission?


It looks like this article was submitted over a year ago with that original title, it's since changed it's name and was recently resubmitted


It originally said 1% of Wordpress sites.


Could be HN mod editorialising at work


Don't start assuming without evidence. I don't support the changing of titles, but I'm not going to blame the mods for something they didn't do.


Perhaps "it could be..." was merely a suggestion rather than an accusation? (It's within the realm of possibility based on empirical evidence.)


To protect the wp-config.php file using htaccess is a well known security precaution for any WP pro.

The issue however is if you have a copy of said file, in which case protecting the main version would be useless.

Also I'd say the blame if you can assign any is on Vim, Emacs, Gedit, and Nano which would have had to crash in order to set these chain of events in motion.


vim lets you choose where to put swp/backup files. Mine are always in ~/.vim/tmp. Avoids awkward issues with scripts that operate on the current directory recursively. And no, you cannot blame shitty security on a text editor.


> Also I'd say the blame if you can assign any is on Vim, Emacs, Gedit, and Nano

... Or the guy who is just editing files directly on the server (or shovelling everything up with FTP), instead of sane version control.

(I'm not sure anyone doing the former can be counted as "Pro", WP or otherwise.)


Please don't put your database passwords into version control. https://github.com/search?q=path%3Awp-config.php&ref=sim...

(A) Database passwords are a security risk. You don't want them escaping to other servers when you clone or share code.

(B) Wordpress is commodity software. You are unlikely to need custom modifications to the source. "I'm on version 3.4.2" is sane enough version control.

(C) Database passwords are ephemeral. You don't need to keep a durable record of them. If your hard drive fails or you otherwise lose your record of them, you can just change the password. What is needed is a good backup strategy, not a durable password.

I would say live editing wp-config.php outside of version control is the best strategy, until you start needing to synchronize multiple webservers. At that point you should switch to some deployment and configuration manager like Chef or Puppet, not put passwords into version control.


You're right; on reflection, version control has nothing to do with it (besides a .gitignore with wp-config-anything ignore entries). I used to use a wp-config.php file in a shared directory on the server that gets symlinked in by Capistrano during deployments; there's no way to accidentally create new .swo/.bkp/.whatever files in production without both screwing up the Capistrano recipe and wp-config.php file creation.

(It seems I skipped actually writing something useful and went straight to durr version control, sorry.)

Points A) and C) are dead on. Addressing your point B), though:

Yes, it is commodity software. But most seem to use at least some plugins and themes, and making changes to those without a rollback is a nightmare. You can make backups before changing things, sure, but version control has that already built in.

(Sibling posts to yours point out that Wordpress looks in both the docroot and one level up for wp-config.php - if I ever manage a Wordpress site again I'll be sure to move it out.)


Our tutor in my first year uni course taught us to put sensitive information into a folder outside the web path, and use relative paths to include the files where needed. There is absolutely zero reason these sensitive files should be accessible via URL.


WordPress looks for wp-config.php in the current directory and 1 directory up, so there is no need for any use of htaccess.


wp-config.php~ and wp-config.php# and other variations may slip through a simple htaccess, even for a "wp pro" :-)


thanks for the summary, I had to read the whole article to pick that out. Also, if there is a connection error (such as FTP session ending) that could also leave the copy on the server.

Take away is to not use an editor that auto-saves or to save it on your local computer and put the finished file onto the server.


Why would they not have a config folder which uses .htaccess to protect everything inside? That would close this vulnerability without affecting convenience.


Yii places all config information (and any files that shouldn't be accessed by a visitor) in a folder called "protected" that is behind a "deny from all" .htaccess file. WordPress should consider something like that.


While this is bad of course, you also would have to allow network access to your mysql from remote ips.

Which if allowed is even more stupid.

If you run mysql only locally then do skip-networking and if you have to have networking restrict the ips it's allowed from.

If they can use the mysql password locally, well then you have far bigger problems with security than mysql and exposed php configuration files.


You can always try to append "/phpMyAdmin" to the root url, you'd be surprised how often that works.


Wonder how many use the same password for ssh and mysql though


The first time I read a vulnerability report about text editor backup or swap files being served as pain text was probably in the mid-to-late-90s

Some things never change.


For the majority of users that use PHP: A lot of hosts now allow editing of php.ini which isn't accessible to the web (or else it's time to switch hosts).

You can store the login credentials there : http://www.php.net/manual/en/pdo.construct.php

This is another reason why you should move away from mysql(i) to PDO.

Or if that doesn't work, try storing in the .htaccess or Apache conf environment variables : http://stackoverflow.com/a/2583857


This is another great reason to "keep config files out of web root." WordPress supports this, although it's not by default, and poorly documented, and even suggested it's not beneficial.

http://codex.wordpress.org/Editing_wp-config.php#Configure_D... http://codex.wordpress.org/Hardening_WordPress#Securing_wp-c...


My small-ish personal website gets hammered everyday by bots trying to access obscure config files and installer scripts from various CMS I have never even heard about.

I think the chance of successfully obtaining DB credentials using this method is fairly high even without those "backup files" mentioned in the link.

I'd assume there are many people out there who can set up a simple CMS themself but ignore/don't understand how to properly remove the various "installer" and "configurator" scripts once they're done...


  "If the text editor crashes or the SSH connection drops during editing"
What about the time of editing? During that time, the temp files exist. Are they readably? I would guess so. So even if you just edit them and have to crash, you are vurnurable.


This is why everything except a font accessor (index.php) should be stored in a directory behind the publicly accessible web root directory.

It's standard practice for most web frameworks, and yet it seems the most popular CMSs don't bother.


It would make the CMSs harder to deploy, security gets traded off for convenience.


Apache could make this very easy. The default htaccess file could set this up.


As a side note a lot of Wordpress sites do expose the wp-config-sample.php file. See for example the official Yahoo! Blog, The Wall Street Journal, Sony Blog, etc...:

http://ycorpblog.com/wp-config-sample.php

http://blogs.wsj.com/law/wp-config-sample.php

http://blog.us.playstation.com/wp-config-sample.php

...


> Thus, 230 / 216391 = 0.11% of all websites are vulnerable.

> Latest stats say that about 13.8% of the top 10,000 websites run CMSs. If we just focus on CMS-powered websites, then the percentage of vulnerable sites is much higher:

> Thus, 230 / (216391 * 0.138) = 0.77% of websites running a CMS are vulnerable.

I don't think those numbers mean what you think they mean.

13.8% out of 10,000 doesn't say much about the top 216,391. And perhaps 0 out of 230 of the vulnerable websites use CMS, however unlikely that is.


When I'm working with CodeIgniter (which ExpressionEngine is based off), I always rewrite access to subfolders of the project to the index.php file, because that's the only file that there needs to be access to for the site to function properly. This keeps anyone from accessing configuration (or any other) files.

  RewriteCond %{REQUEST_URI} ^somefolder.*
  RewriteRule ^(.*)$ /index.php?/$1 [L]


Quite an old issue (2011), did anybody run the script to see if as many sites still publicly expose these files?


It would also be interesting to know if any of the big CMS vendors (Automattic, Drupal etc) has done anything to prevent this in the newer releases.


<Files ~ "(^#.*#|~|\.sw[op])$"> Order allow,deny Deny from all </Files>

found this in comment, it's useful to me


This is not anything new and has been known for ages.

Assuming one follows the "defense-in-depth" approach, however, this is easily mitigated and becomes a non-issue.

FWIW, I could give you the username and password that my instances of WordPress use to connect to MySQL and you wouldn't be able to do anything with them.


Interesting security concern. It might be added that if one has several backup files, and has MultiviewsMatch enabled, the files aren't actually readable but get run instead. This is because multiple backups get numbered after a dot, while single backups and crashed files do not.


Wow.

I have never thought about this, as I'm super paranoid about database credentials stored in any sort of config file (where it's wp-config.php, settings.php or any other language with frontfacing credentials).

Great writeup ferross!


To find VIM swp files you can run this command on your public_html directory:

    find . -name "*.swp"
You can find and remove them with:

    find . -name "*.swp" -exec rm -f \{\} \;


A solution using xargs instead of exec:

find . -name "*.swp" | xargs rm

I believe find -exec forks and execs once for every file found, xargs may only fork and exec once, provided that all of the files find finds fit in a single command line. Hopefully not an issue in this case, but it can greatly speed up a deletion when you have a lot of files to find and delete.

[EDIT] Doesn't properly handle spaces in filenames, see the comments below for other slick solutions.


Technically, your solution does not work with files whose name contain a space or newline. A working solution is either to do '-print0' in find and '-0' in xargs, or to just use '-delete' instead of the -exec. It is also possible to use '+' instead of ';' with '-exec' to say "fork as few times as possible", ie pack the largest list of arguments to rm you can. But '-delete', when supported, won't even fork/exec.


Are these GNU find extensions? I spend a lot of time on solaris, and I don't remember seeing a delete option. Thanks for the tips though!


You're right, -delete is not standard, but I think that + and -print0 are.


Please don't run the command above, it doesn't handle spaces. You might delete something you didn't intend to delete.

You need to add -print0 to find, and -0 to xargs to make it work properly: find . -name "*.swp" -print0 | xargs -0 rm

But better would be to use -delete as I wrote above.


I'm a little bummed to find that this doesn't work on Solaris either (just checked). I don't generally use spaces in paths, but it's probably about half luck that I haven't been bitten by this yet. And sadly, I can't always just put GNU findutils on.


For GNU: find . -name "*.swp" -delete


Would also note that if php for some reason is turned off the server will send the actual config file wp-config.php as well as well as all those other files that are mentioned by the OP.


Just do a google search for "filetype:sql @gmail.com" and you will find thousands of `users` table dumps.

I dont know how useful it is for passwords but very useful for email spamming!


The author helps with an Apache rule:

<Files ~ "(^#.*#|~|\.sw[op])$"> Order allow,deny Deny from all </Files>

What would be the equivalent of this for Nginx?


    location ~ "(^#.*#|~|\.sw[op])$" {
        return 401;
    }
Or something along those lines.

Nodesocket's answer is good as well: http://news.ycombinator.com/item?id=5164017


Not sure if it's actually that helpful but might be nicer to serve up a 404, in the interest of opacity.

Simply giving the hacker less information (though not just depending on this) is a useful form of security. If you give them a 401, then they at least know that the file exists.


It should be noted that the author states that this htaccess rule "block[s] access to any file containing the string wp-config.php", but the rule itself is designed to block any temporary editor file matching the pattern he describes in the article regardless if it is named wp-config.php or not. Your nginx rule does the same.


Thx, I'll try that.


OP please fix your title, the article states "1% of CMS-Powered Sites" NOT "1% of WordPress Sites".

Also you can move wp-config.php outside the root.


how much % of these 1% websites have their database server allowing remote connections ?

does the DB password mean anything without allowing remote connections ? (let's forget shared hosts for a moment)


Assuming there are no other bugs in the CMS in question, and no other apps are installed with access to the same server, and the admins implement good authentication policies, there is no danger from this.

But...

It is rare there is only one app on a server, even a non-shared one, so this information could be used in conjunction with this information in order to cause bother. It is not uncommon to see something like phpMyAdmin installed in a standard location on a given domain and not locked down well - for sites where this is the case this bug is very serious.

It is scary how many people out there calling themselves sysadmins use the same password for everything to do with a given service, or even for everything - for them this is a bigger problem as revealing the DB password also reveals the credentials needed to access other things (perhaps an SSH account with privileged access either directly or via sudo).


> It is scary how many people out there calling themselves sysadmins use the same password for everything to do with a given service, or even for everything

That should immediately mark them as a Dunning-Krueger victim/hack and you should get someone else. If you're not using something equivalent to a password vault or something equivalent, you're not as security savvy as you think you are.


> Assuming there are no other bugs in the CMS in question, and no other apps are installed with access to the same server, and the admins implement good authentication policies, there is no danger from this.

That depends on the contents of the file in question. Example: An attacker can forge hmacs if the config file contains the signing key.


[deleted]


Example public config file:

I think you should delete your comment. It's not helpful to hand out other peoples' passwords like that, regardless of how easy it might be to find them.




Consider applying for YC's W25 batch! Applications are open till Nov 12.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: