Detecting malware on a WordPress site from the command prompt can be a useful method for webmasters and security professionals who prefer command-line interfaces. In this blog post, we will discuss different terminal commands that can be used to detect malware on a WordPress site.
Table of Contents
Requirements
To make the most of the following article, we expect that you have the following:
- A web hosting account or server with SSH access.
- A SSH client already installed in your computer.
- You have basic PHP knowledge.
In this tutorial we will be using the following services & tools:
Kinsta as our Premium Managed hosting provider for WordPress.
Some features we love from Kinsta:
- Full SSH support.
- GIT included.
- WP-CLI
- Cron Jobs
- Latest PHP versions.
- Free Malware removal service.
iTerm2 for MacOS as our SSH client. iTerm2 is a replacement for MacOS built-in Terminal app. We use due to these amazing features:
- Split pane view. If you usually have several SSH sessions this helps a lot.
- Search – you can search within your session and increase the session to record longer times.
Before you start, make a backup
As we’ve talked about before, Backups are and should be a huge part of your website strategy.
But as a quick reminder, here is a few bullet points:
- Backups should be saved in a different server for disaster recovery purposes. If the server website goes down, you need to have a access to a backup to restore your site on a different server. If you save the backups & website on the same website, you will be out of luck.
- Backups should be scheduled at least daily depending on your business needs. Some eCommerce websites do hourly backups, so please check which frequency makes sense and fit within your budget.
- Backups should include both the filesystem and the database contents. WordPress websites save information in both files and the database so to successfully restore a site you will need both.
Step 1: Connect to the Server
First, you need to connect to the server hosting the WordPress site using an SSH client. You will need to provide your credentials to log in. Once logged in, navigate to the WordPress site directory.
On this tutorial, we will assume you already know:
- How and were to get the SSH credentials or SSH Keys
- Server SSH connection information
- How to successfully connect to a server via SSH.
Step 2: Check for Suspicious Files
You can use the following commands to check for suspicious files on your WordPress site:
- List all files and directories in the current directory:
ls -la
This command will list all files and directories in the current directory, including hidden files.
Please be aware that WordPress by default has 3 different folders: wp-admin, wp-content & wp-includes. This means that any other folder contained there might be suspicious if you did not create it.
Below an example on how a clean installation should look like:
$ ls -la
total 331
drwxrwxr-x 5 wpmechanicsblog wpmechanicsblog 21 Apr 28 21:34 .
drwxr-xr-x 8 wpmechanicsblog wpmechanicsblog 30 Apr 28 21:33 ..
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 405 Apr 28 21:34 index.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 19915 Apr 28 21:34 license.txt
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 7402 Apr 28 21:34 readme.html
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 7205 Apr 28 21:34 wp-activate.php
drwxrwxr-x 9 wpmechanicsblog wpmechanicsblog 101 Apr 28 21:34 wp-admin
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 351 Apr 28 21:34 wp-blog-header.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 2338 Apr 28 21:34 wp-comments-post.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 3013 Apr 28 21:34 wp-config-sample.php
drwxrwxr-x 4 wpmechanicsblog wpmechanicsblog 5 Apr 28 21:34 wp-content
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 5536 Apr 28 21:34 wp-cron.php
drwxrwxr-x 28 wpmechanicsblog wpmechanicsblog 251 Apr 28 21:34 wp-includes
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 2502 Apr 28 21:34 wp-links-opml.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 3792 Apr 28 21:34 wp-load.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 49330 Apr 28 21:34 wp-login.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 8541 Apr 28 21:34 wp-mail.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 24993 Apr 28 21:34 wp-settings.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 34350 Apr 28 21:34 wp-signup.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 4889 Apr 28 21:34 wp-trackback.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 3238 Apr 28 21:34 xmlrpc.php
Sometimes malware is easy to spot on this step, here are some simple Indicators of Compromise to have in mind:
- The main file index.php size should be between 405 and 420 bytes. Any other size on this file, means that the file was intentionally modified. So be sure to check it contents with your favorite text editor such as vim, nano or using other terminal commands such as cat, less or more.
- Folders with random names is another good indication that you have malware. See the example below that contains 3 directories with random names 7kafDso, af6jdh & Dog7ndw:
$ ls -la
total 332
drwxrwxr-x 8 wpmechanicsblog wpmechanicsblog 24 Apr 28 21:40 .
drwxr-xr-x 8 wpmechanicsblog wpmechanicsblog 30 Apr 28 21:33 ..
drwxrwxr-x 2 wpmechanicsblog wpmechanicsblog 2 Apr 28 21:40 7kafDso
drwxrwxr-x 2 wpmechanicsblog wpmechanicsblog 2 Apr 28 21:40 af6jdh
drwxrwxr-x 2 wpmechanicsblog wpmechanicsblog 2 Apr 28 21:40 Dog7ndw
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 405 Apr 28 21:34 index.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 19915 Apr 28 21:34 license.txt
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 7402 Apr 28 21:34 readme.html
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 7205 Apr 28 21:34 wp-activate.php
drwxrwxr-x 9 wpmechanicsblog wpmechanicsblog 101 Apr 28 21:34 wp-admin
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 351 Apr 28 21:34 wp-blog-header.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 2338 Apr 28 21:34 wp-comments-post.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 3013 Apr 28 21:34 wp-config-sample.php
drwxrwxr-x 4 wpmechanicsblog wpmechanicsblog 5 Apr 28 21:34 wp-content
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 5536 Apr 28 21:34 wp-cron.php
drwxrwxr-x 28 wpmechanicsblog wpmechanicsblog 251 Apr 28 21:34 wp-includes
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 2502 Apr 28 21:34 wp-links-opml.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 3792 Apr 28 21:34 wp-load.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 49330 Apr 28 21:34 wp-login.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 8541 Apr 28 21:34 wp-mail.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 24993 Apr 28 21:34 wp-settings.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 34350 Apr 28 21:34 wp-signup.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 4889 Apr 28 21:34 wp-trackback.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 3238 Apr 28 21:34 xmlrpc.php
- There can also be filenames with random names that could be malware, be sure that all WordPress core files that sit on the public directory start with wp-, if you have files with other naming convention, then they should be easy to spot just by doing the directory listing. In the example below there is a file called options.php. This file is not part of WordPress because it does not start with wp- and it’s not WordPress main index.php file.
$ ls -la
total 337
drwxrwxr-x 8 wpmechanicsblog wpmechanicsblog 25 Apr 28 21:45 .
drwxr-xr-x 8 wpmechanicsblog wpmechanicsblog 30 Apr 28 21:33 ..
drwxrwxr-x 2 wpmechanicsblog wpmechanicsblog 2 Apr 28 21:40 7kafDso
drwxrwxr-x 2 wpmechanicsblog wpmechanicsblog 2 Apr 28 21:40 af6jdh
drwxrwxr-x 2 wpmechanicsblog wpmechanicsblog 2 Apr 28 21:40 Dog7ndw
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 405 Apr 28 21:34 index.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 19915 Apr 28 21:34 license.txt
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 1102 Apr 28 21:45 options.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 7402 Apr 28 21:34 readme.html
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 7205 Apr 28 21:34 wp-activate.php
drwxrwxr-x 9 wpmechanicsblog wpmechanicsblog 101 Apr 28 21:34 wp-admin
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 351 Apr 28 21:34 wp-blog-header.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 2338 Apr 28 21:34 wp-comments-post.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 3013 Apr 28 21:34 wp-config-sample.php
drwxrwxr-x 4 wpmechanicsblog wpmechanicsblog 5 Apr 28 21:34 wp-content
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 5536 Apr 28 21:34 wp-cron.php
drwxrwxr-x 28 wpmechanicsblog wpmechanicsblog 251 Apr 28 21:34 wp-includes
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 2502 Apr 28 21:34 wp-links-opml.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 3792 Apr 28 21:34 wp-load.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 49330 Apr 28 21:34 wp-login.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 8541 Apr 28 21:34 wp-mail.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 24993 Apr 28 21:34 wp-settings.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 34350 Apr 28 21:34 wp-signup.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 4889 Apr 28 21:34 wp-trackback.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 3238 Apr 28 21:34 xmlrpc.php
- List all files and directories in the current directory sorted by modification time:
ls -lat
This command will list all files and directories in the current directory, sorted by modification time, with the most recently modified files listed first.
See the example below:
$ ls -lat
total 337
drwxrwxr-x 8 wpmechanicsblog wpmechanicsblog 25 Apr 28 22:13 .
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 4024 Apr 28 22:13 wp-load.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 1102 Apr 28 21:45 options.php
drwxrwxr-x 2 wpmechanicsblog wpmechanicsblog 2 Apr 28 21:40 7kafDso
drwxrwxr-x 2 wpmechanicsblog wpmechanicsblog 2 Apr 28 21:40 Dog7ndw
drwxrwxr-x 2 wpmechanicsblog wpmechanicsblog 2 Apr 28 21:40 af6jdh
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 7402 Apr 28 21:34 readme.html
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 7205 Apr 28 21:34 wp-activate.php
drwxrwxr-x 9 wpmechanicsblog wpmechanicsblog 101 Apr 28 21:34 wp-admin
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 5536 Apr 28 21:34 wp-cron.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 34350 Apr 28 21:34 wp-signup.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 405 Apr 28 21:34 index.php
drwxrwxr-x 28 wpmechanicsblog wpmechanicsblog 251 Apr 28 21:34 wp-includes
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 2502 Apr 28 21:34 wp-links-opml.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 24993 Apr 28 21:34 wp-settings.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 19915 Apr 28 21:34 license.txt
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 351 Apr 28 21:34 wp-blog-header.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 49330 Apr 28 21:34 wp-login.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 8541 Apr 28 21:34 wp-mail.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 4889 Apr 28 21:34 wp-trackback.php
drwxrwxr-x 4 wpmechanicsblog wpmechanicsblog 5 Apr 28 21:34 wp-content
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 2338 Apr 28 21:34 wp-comments-post.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 3013 Apr 28 21:34 wp-config-sample.php
-rw-rw-r-- 1 wpmechanicsblog wpmechanicsblog 3238 Apr 28 21:34 xmlrpc.php
drwxr-xr-x 8 wpmechanicsblog wpmechanicsblog 30 Apr 28 21:33 ..
On this example we can see that on top we have the wp-load.php, which is a core WordPress file and having such file recently modified is never good news.
Below are some Indicators of Compromise to consider when you’re reviewing timestamps:
- All core WordPress files should have the same time-stamp. If any has a different one, it means it was manually modified and it will require our review. The only file that its usual to have a different time-stamp is wp-config.php, this is because this file is edited after WordPress was installed since the last step is to add the MySQL database credentials.
- Review all the most recent edited files.
- Depending on your server or hosting provider, you can use the command stat to gather further information about the file. in the example below you can see further information about changes along with its timestamps.
# stat wp-load.php
File: wp-load.php
Size: 4024 Blocks: 9 IO Block: 4096 regular file
Device: 100078h/1048696d Inode: 547415 Links: 1
Access: (0664/-rw-rw-r--) Uid: ( 1001/wpmechanicsblog) Gid: ( 1003/wpmechanicsblog)
Access: 2023-04-28 22:13:15.047832117 +0000
Modify: 2023-04-28 21:18:40.953886652 +0000
Change: 2023-04-28 21:18:40.953886652 +0000
Birth: -
- List all PHP files in the current directory:
find . -name "*.php"
This command will list all PHP files in the current directory and its subdirectories.
- Check for files that have been modified in the last 24 hours:
find . -type f -mtime -1 -ls
This command will list all files in the current directory and its subdirectories that have been modified in the last 24 hours.
- Check for files that have been modified in the last 24 hours and have the
.php
extension:
find . -type f -name "*.php" -mtime -1 -ls
This command will list all PHP files in the current directory and its subdirectories that have been modified in the last 24 hours.
Step 3: Check for Suspicious Code
Once you have identified suspicious files, you can use the following commands to check for suspicious code:
- Search for a specific string of code in a file:
grep -r "string" /path/to/directory
This command will search for the specified string of code in all files in the specified directory and its subdirectories.
- Search for a specific string of code in all PHP files in a directory:
grep -r "string" --include="*.php" /path/to/directory
This command will search for the specified string of code in all PHP files in the specified directory and its subdirectories.
- Search for a specific string of code in all PHP files in a directory and output the results to a file:
grep -r "string" --include="*.php" /path/to/directory > output.txt
This command will search for the specified string of code in all PHP files in the specified directory and its subdirectories and output the results to a file named output.txt
.
Dangerous PHP functions
Some of the suggested search strings are the following PHP functions which are often used by malicious actors:
- base64_decode – Decodes the given base64 formatted string.
- eval – Evaluate a string as PHP code.
- assert – Identical to eval()
- exec – Returns last line of the given command’s output
- shell_exec – Returns the given command’s output
- system – Passes commands output directly to the browser and returns last line.
- gzinflate – Applies gzip decompression to given string.
- str_replace – Replaces the given string in the specified code block, array, or file.
- preg_replace – Same as str_replace but with RegEX support.
- passthru – Passes commands output directly to the browser.
- str_rot13 – Encodes the given string or content in ROT13 format.
- htmlspecialchars_decode
- gzuncompress
zxczxc
Step 4: Checking for Malware & SEO SPAM in your database
Since WordPress also uses a MySQL database to store settings and website content; malicious actors often place JS redirectors, PHP Code or malicious SEO SPAM blocks of code into the database; so here will provide a few example commands to help you find them.
If you’ve read any of our previous content, you know we LOVE WP-CLI and all its features. But if you have not, pause here and take a quick peek at these articles:
Now, let’s get into the meaty stuff. For checking our database we will use WP-CLI’s command wp db search
. This will allow us to easily scan the database without having to login to MySQL CLI or using a 3rd party desktop app such as MySQL WorkBench, Navicat or Toad.
To have a better understanding of what it makes sense to look for, let’s review some of the most used functions and keywords used in malware (and SEO SPAM) that we can find in WordPress databases:
Dangerous Javascript functions
- eval – just as with PHP, evaluates a string as Javascript code.
- atob – decodes a string of data which has been encoded using base64 encoding.
- btoa – opposite to atob, it encodes a string using base64 encoding.
- String.fromCharCode – returns a string created from the specified sequence of UTF-16 code units.
SEO SPAM Keywords
- Pharma Spam. These are legal drugs such as ones listed below.
Here is the full list.
- Viagra
- Cialis
- Levitra
- Sildenafil
- Xanax
- Nexium
- Allegra
- Lipitor
- Oxycodone
- Omeprazole
- Amoxil
- Ativan
- Ambien
- Claritin
- Lexapro
- Nasonex
- Prozac
- Ritalin
- Rogaine
- Vicodin
- Adult-themed SEO Spam. This includes the links to adult toys stores, adult content websites and similar.
- Sports wear and shoes Spam. Started offering NBA and NFL replica jerseys and then also started included selling replicas of Nike, Jordan, Adidas and other brands.
Let’s now show some examples of commands to use:
wp db search fromCharCode --all-tables
This command will search for the fromCharCode
function in all the tables of the WordPress database due to the --all-tables
parameter. This will match malicious obfuscated JS code like the example below:
eval(String.fromCharCode(118, 97, 114, 32, 95, 112, 97, 113, 32, 61, 32, 95, 112, 97, 113, 32, 124, 124, 32, 91, 93, 59, 10, 32, 32, 95, 112, 97, 113, 46, 112, 117, 115, 104, 40, 91, 39, 116, 114, 97, 99, 107, 80, 97, 103, 101, 86, 105, 101, 119, 39, 93, 41, 59, 10, 32, 32, 95, 112, 97, 113, 46, 112, 117, 115, 104, 40, 91, 39, 101, 110, 97, 98, 108, 101, 76, 105, 110, 107, 84, 114, 97, 99, 107, 105, 110, 103, 39, 93, 41, 59, 10, 32, 32, 32, 32, 118, 97, 114, 32, 117, 61, 34, 104, 116, 116, 112, 115, 58, 47, 47, 118, 111, 105, 112, 110, 101, 119, 115, 119, 105, 114, 101, 46, 105, 110, 110, 111, 99, 114, 97, 102, 116, 46, 99, 108, 111, 117, 100, 47, 34, 59, 10, 32, 32, 32, 32, 95, 112, 97, 113, 46, 112, 117, 115, 104, 40, 91, 39, 115, 101, 116, 84, 114, 97, 99, 107, 101, 114, 85, 114, 108, 39, 44, 32, 117, 43, 39, 112, 105, 119, 105, 107, 46, 112, 104, 112, 39, 93, 41, 59, 10, 32, 32, 32, 32, 95, 112, 97, 113, 46, 112, 117, 115, 104, 40, 91, 39, 115, 101, 116, 83, 105, 116, 101, 73, 100, 111, 100, 101, 46, 105, 110, 115, 101, 114, 116, 66, 101, 102, 111, 114, 101, 40, 103, 44, 115, 41, 59));
Conclusion
Using the command prompt to detect malware on a WordPress site can be an effective method for webmasters and security professionals. By using the above-mentioned commands, you can quickly and easily identify suspicious files and code on your site, which can help you take action to remove any malware and protect your website from further attacks.