Real Life Security Fails – How a Lost Password Sent Thousands of Magento Credit Cards to Eastern Europe

All of the server security on the planet is useless if you don’t keep track of your passwords.

One of the most effective yet hardest to detect attacks against Magento I’ve seen to date requires nothing but a medium-level admin login and the ability to copy/paste an 8th grade level piece of code. Best (worst?) of all, you won’t even know if you’re infected, because it causes no obvious changes in the site, neither creates nor edits any files, slides under every website malware checker I know of, and doesn’t even show anything shady in webserver access logs.

I have seen this exploit quietly shuttle customer identity and credit card data to attack servers for literally *months* before anyone noticed.

Also, I don’t mean to pick on Eastern Europe, but if we’re being honest, most good malware comes out of Russia/China/Eastern Europe (Ukrainian Magento developers are some of the best). Blame a high cultural importance placed on STEM education and a fluctuating economy. The times I’ve encountered this hack in the wild, it’s usually been shuttling data to that general area.

The hack goes about like this:

1. Attacker gets a hold of an admin login, maybe by brute-force password guessing, but more likely by getting a hold of an email or some other electronic correspondence, probably from a trusted or formerly-trusted 3rd party like an overseas developer.
2. Attacker logs into admin.
3. Attacker goes to System > Configuration > Design > Footer


4. Attacker pastes in a piece of javascript that gets loaded in the footer of every page, that basically simply says

if this is the checkout page
when the submit button is pressed
grab the customer’s data,
create a URL like…
and send a request to that URL.

The most devious part of this, besides the fact that it takes minimal technical skills, is that the script doesn’t actually load anything into the page, so malware scanners won’t notice it. All the attacker has to do is write a script on their own server that grabs the data out of the URL parameters and they’ve got all the customer’s identity information and their credit card number.

Counter Measures

1. AFAIK, the only 100% effective countermeasure against this hack is to do a checkout with your browser running through a proxy, record all network traffic, and then verify the identity of all the servers involved.

2. A better-than-nothing test is to request the page via script, preferably by driving a headless browser like PhantomJS, use some regex magic to parse out any URLs, and then verify the server location via IP-to-country API.

3. Rotate passwords and keep track of who is logging into your Magento.

  • I wrote some shell scripts that perform the second option, but unfortunately I don’t own the code so I can’t release them 🙁
  • I’m working on a Selenium/NodeJS system that performs the first (superior) check, and I promise I’ll either open source the components or provide a free version 🙂
  • I just finished an early-but-functional version of a free extension that logs all of your admin area logins - versions may support IP geolocation. I’ve also got plans in the works for other extensions that add enterprise-level admin user security features like password rotation enforcement, adjustable password difficulty enforcement, etc.

Nginx and php-fpm – ‘File not found’

Ran into this issue briefly when moving a blog over to a Centos 7.2 cloud box.

File not found.

Nginx was running as user ‘nginx’, and that user had full ownership of all web files. Logically, everything should have run fine with 700 permissions, but it wasn’t.

cat /etc/passwd

showed a user named ‘apache’. Why would that user exist on a system that had never had Apache installed?

Ahhhhh… php-fpm. The default php-fpm user is ‘apache’ – I guess just assuming it will be installed alongside apache is easier than trying to figure out how to have the webserver and php-fpm run in the same group or something.

Edited /etc/php-fpm.d/www.conf to set php-fpm’s user and group to ‘nginx’, then did

chown -R nginx.nginx /var/www/webroot
chmod -R 500 /var/www/webroot
chmod -R 700 /var/www/webroot/wp-content/uploads

Now the blog runs fine and permissions are as tight as possible without breaking things (so far lol).

Disabling PHP on a per-directory basis

Any PHP web application that allows uploads, by necessity, is going to need to vary permissions on a per-directory basis (unless you want to just save a hacker some work and make the directories that contain code files writeable by the webserver). Magento needs to be able to write to ./var, ./media, and also ./app/etc if you want to run the installer.

The ./media/ directory is an exceptionally temping gap in the application’s armor, because files there are also web-accessible out of necessity. That means if an attacker can manage to write a code file there, they can then execute it at will by simply requesting the file in a browser or sending an http request another way.

One of the easiest and most effective countermeasures against this method of attack is to simply block php execution in directories that should not contain php files. While it’s tempting to use an .htaccess file (.htaccess files in themselves kind of suck for both security and performance but that’s a diatribe for another day), there is a high risk of an attacker just overwriting it with a new, looser .htaccess file.

Instead, use the following in your Apache virtual host file (test these first, they’re tweaked snippets I found online but they look about right)

<Directory /media>
    <FilesMatch "(?i)\.(php|phtml)$">
        Order Deny,Allow
        Deny from All

… or your nginx config file

location ~* /media/.*\.(php|phtml)$ {
    return 404;

That should block any files with .php or .phtml file extensions in the directory from being parsed. You might want to do the same for ./var/, depending on the installation – the preferable action is to hide ./var/ from the web completely, but some installs with import/export extensions and other 3rd party code require allowing some access.