Fixing a hacked website

head file infected

When our website is hacked, due to a lack of proper maintenance of the installation, it can be due to many causes.

The solution is not always equally easy, if our website is a static website and we have a copy that we know is clean, the easiest thing to do is to restore, but be careful with the type of restoration. If you simply dump the new files over the old ones, you will not delete files that may have been created by the hack and continue with the problem. It can also be that the problem is in a file outside WordPress and even if we restore, the problem continues.

If the website is more dynamic, such as a WooCommerce, we will not be able to restore a copy from a week ago and delete the latest orders. Trying to combine a copy with the latest records will, in 99% of the cases, be one problem after another.

There are many types of hacks and different ways to fix it, there is no one way. If you are not completely sure how to do it, the best thing to do is to hire a professional to help you with the installation recovery, but it will be much better to prevent it with good security and proper maintenance, which will be much cheaper in the long run.

Having said all this, here I am going to show some possible tools to help us in the work of restoration and search for problems.

WP Cli to the rescue: check the core

WP CLI is a must-have tool in every WordPress administrator’s toolbox. To use it we must have SSH access to the server.

Remember that if we access as root, we must either change to another user or add at the end of our commands --allow-root

The first thing we do is to enter the root of our WordPress installation and the first command will be to check that the files of our WordPress installation are correct and that none is changed, missing or there are any more, for that we run the following command:

wp core verify-checksums
WP Cli verify core

If it tells us that everything is correct we continue to the next section. If any files are missing or “modified” we download a clean copy of the files using the following command and force it to overwrite the old ones, and ignore the default themes and plugins (such as Hello Dolly):

wp core download --skip-content --force

If our WordPress is a version different from the latest one, we can force to download that version, for example 6.1:

wp core download --version=6.1 --force

If there are added files we will have to remove them(Tip: download it first to look at the added code and see possible consequences of the hack). A quite frequent case in some servers is that they add a php.ini file inside each directory, so we will find a lot of these “suspicious” files. If we want to delete them all at once, we first search for them:

 find . -name "php.ini" -type f

If the result we see is the one we want to eliminate, we execute the following:

 find . -name "php.ini" -type f -delete

Check plugins

The next step is to check the plugins with the following command:

wp plugin verify-checksums --all

Instead of the parameter --all we can also write the plugin name and perform the check only for that plugin.

The bad news here, is that it will only check plugins from the official repository, so all other plugins will have to do a “more manual” work.

As with the core, if we have added files, we delete them. If they are missing or modified, we download a clean copy (as with the core, we can also indicate the version):

wp plugin install bbpress --force

Plugins from outside the official repository

With plugins that are not in the official repository, the best technique is to delete the directory to make sure there are “no files added” and download a clean copy from their official website.

I think it is not necessary to remind you that the websites that offer these plugins for “free” or through subscriptions for all plugins (free bar of plugins for all), in many cases are the source of these hacks, either premeditated or due to lack of updates and minimum security measures. You will end up seeing how those 100 euros you saved on that plugin you needed for your online store, will end up costing you thousands of euros in losses and repairs to your store.

WordPress Themes

For WordPress themes we don’t have a command to check their integrity, but we do have one to download a clean copy if they are themes from the official WordPress repository.

If your theme is not in the official repository, delete the directory and download a clean copy of it.

If it is in the official repository, delete the directory and download the copy directly from WP-CLI:

wp theme install twentysixteen --force

Other useful WP-CLI commands

From https://developer.wordpress.org/cli/commands/ we can see the available WP-CLI commands. Among those that cannot be useful in the event of a hack and to obtain information from the installation, are:

# Ver todos los usuarios administradores.
wp user list --role=administrator

# Crear un usuario administrador para entrar en el panel de control de WordPress.
wp user create carlos [email protected] --role=administrator

# Borrar un usuario y reasignar sus entradas al usuario con ID 69.
wp user delete fakeuser --reassign=69

# Borrar los datos transitorios caducados.
wp transient delete --expired

# Borrar todos los datos transitorios.
wp transient delete --all

# Cambiar una cadena por otra en la Base de Datos (prueba sin ejecución).
wp search-replace 'http://tabernawp.com' 'https://nueva-tabernawp.com' --dry-run

# Cambiar una cadena por otra en la Base de Datos.
wp search-replace 'http://tabernawp.com' 'https://nueva-tabernawp.com'

# Mostrar la lista de reglas de re-escritura.
wp rewrite list

# Mostrar la lista de reglas de re-escritura en formato csv 
# para copiar y pegar en una hoja de cálculo.
wp rewrite list --format=csv

# Recargar las reglas de re-escritura.
wp rewrite flush

# Borrar la caché.
wp cache flush

# Listar todos los comentarios SPAM.
wp comment list --status=spam

# Borrar los comentarios SPAM.
wp comment delete $(wp comment list --status=spam --format=ids)

# Generar nuevos Salts.
wp config shuffle-salts

# Ejecuta la actualización de BD.
wp core update-db

# Comprobar que las tareas cron se ejecutan correctamente.
wp cron test

# Mostrar las programaciones cron disponibles.
wp cron schedule list

# Mostrar las tareas cron programadas.
wp cron event list

# Comprueba la Base de Datos.
wp db check

# Optimiza la Base de datos.
wp db optimize

# Reparar la Base de Datos.
wp db repair

# Muestra el prefijo de la Base de Datos.
wp db prefix

# Mostrar las tablas
wp db tables

# Exportar la Base de Datos con la sentencia Drop Table.
wp db export --add-drop-table

# Importa un volcado de Base de Datos desde archivo.
wp db import wordpress_dbase.sql

# Buscar una cadena en la Base de Datos, en todas las tablas, aunque no tengan el prefijo.
wp db search "Carlos Longarela" --all-tables

# Buscar una cadena en la Base de Datos, en todas las tablas con el prefijo de WordPress.
wp db search "Carlos Longarela" --all-tables-with-prefix

# Buscar en la Base de Datos utilizando expresiones regulares.
wp db search 'https?://' --regex

# Mostrar el tamaño de las tablas de la Base de Datos.
wp db size --tables --human-readable

# Mostrar el tamaño total de la Base de Datos con dos deciamles.
wp db size --human-readable --decimals=2

# Ejecutar código PHP, por ejemplo, para mostrar el directorio de contenidos.
wp eval 'echo WP_CONTENT_DIR . "\r\n";'

# O mostrar la URL del sitio web.
wp eval 'echo home_url() . "\r\n";'

# Mostrar los tamaños de imagen registrados.
wp media image-size

# Regenerar todas las miniaturas sin confirmación.
wp media regenerate --yes

# Regenerar las miniaturas del tamaño de imagen large.
wp media regenerate --image_size=large

# Regenerar las miniaturas para los tres IDs de adjunto dados.
wp media regenerate 68 69 70

# Importa todos los jpgs del directorio "Carlos", que no estén adjuntos a ningún post.
wp media import ./Carlos/**\/*.jpg

# Obtener la URL del sitio web actual.
wp option get siteurl

# Obtener el tamaño total de todas las opciones de autocarga.
wp option list --autoload=on --format=total_bytes

# Mostrar las opciones que comienzan con woo
wp option list --search="woo_*"

# Actualizar el email de administración de WordPress.
wp option update admin_email [email protected]

# Comprobar si está activado el modo mantenimiento.
wp maintenance-mode status

# Activar el modo mantenimiento.
wp maintenance-mode activate

# Desactivar el modo mantenimiento.
wp maintenance-mode deactivate

WP Cli Doctor

But in addition to WP-CLI commands, we can also install extensions that perform various tasks, or some of the plugins that we have installed, can add their own commands.

A plugin that performs several checking tasks is WP Doctor that we can install with the following command:

wp package install wp-cli/doctor-command:@stable

And then we can run all the checking tasks with:

wp doctor check --all

Or individually run each of the checks that we can see with wp doctor list.

We can check for conflicting files containing `eval\(.*base64_decode\(.*.` with the following command:

wp doctor file-eval

But in addition to the default checks, we can create our own yaml file by modifying these checks or adding new ones and run some or all of them from our file.

If we have the following YAML configuration file and name it doctor.yml:

core-verify-checksums:
	check: Core_Verify_Checksums
autoload-options-size:
	check: Autoload_Options_Size
	options:
		threshold_kb: 1024
constant-savequeries-falsy:
	check: Constant_Definition
	options:
		constant: SAVEQUERIES
		falsy: true
constant-disallow-file-edit-true:
	check: Constant_Definition
	options:
		constant: DISALLOW_FILE_EDIT
		value: true
plugin-file-manager-uninstalled:
	check: Plugin_Status
	options:
		name: file-manager
		status: uninstalled
plugin-fileorganizer-uninstalled:
	check: Plugin_Status
	options:
		name: fileorganizer
		status: uninstalled
plugin-filebird-uninstalled:
	check: Plugin_Status
	options:
		name: filebird
		status: uninstalled

We can run all the checks with:

wp doctor check --all --config=doctor.yml

With the following result:

WP Doctor

Or execute just one of the custom commands:

wp doctor check autoload-options-size --config=doctor.yml

Or we can create a configuration file that searches our installation for a series of patterns of possible hacked files:

file-remote-contents:
  check: File_Contents
  options:
    regex: .*(file_put_contents|file_get_contents|curl_exec).*
    only_wp_content: false
file-rot13:
  check: File_Contents
  options:
    regex: .*(str_rot13|ignore_user_abort).*
    only_wp_content: false
 file-eval:
  check: File_Contents
  options:
    regex: .*(base64|eval).*
    only_wp_content: false

Console commands

In addition, we have a multitude of commands in the Linux console that can help us to recover an infected website or find the cause of the infection.

All the Linux commands will be able to help us in our work, to give a couple of examples. If we want to find all the files that have been modified between 3 am and 4:15 am on April 17 from the directory we are in, we should run:

find -type f -newermt "2023-04-16 03:00:00" -not -newermt "2023-04-16 04:15:00"

In the above command, if we only want to search within the contents directory, we can either enter it and execute the same command or modify it to:

find wp-content/ -type f -newermt "2023-04-16 03:00:00" -not -newermt "2023-04-16 04:15:00"

Check that there are no PHP executable files in the uploads directory of your installation:

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

We can search for a text inside the files, for example, if we know that the hack inserts in the files the string $goweb = str_rot13(urldecode($xmlname)); we can search for it in all the files with the following command:

grep -rni "$goweb = str_rot13(urldecode($xmlname));"

We will show the files in the current directory, including hidden files with their date, permissions, owner:

ls -la --color=auto

Display the content of a certain file (or modify it with nano or vim):

cat wp-includes/css/wp-in-auth-check.css

Or delete a file because it turns out that it should not exist because it is a hacked file:

rm wp-includes/css/wp-in-auth-check.css

Show the size of the current directory:

du -h --max-depth=1 | sort -h

And many more Linux console commands that will help us in the search and repair of our WordPress installation.

But remember that the main thing is to avoid infection. The most important thing is to keep the installation up to date, for which a good maintenance plan is essential, whether we do it ourselves or have it done by a professional. In addition, we must take into account the security of WordPress.

But, above all, when faced with a hacking problem, keep calm and take the appropriate steps to avoid erasing the traces of the infection and be very careful about the steps to be taken. If you do not fully understand the commands and utilities shown above, DO NOT DO ANYTHING and contact a professional as soon as possible or you may make the situation worse.

Leave a Comment