How to Check a WordPress Plugin for Malware Before Installing It

Step-by-step methods and free tools for scanning WordPress plugin ZIP files, spotting obfuscated PHP, and keeping malicious code off your website.

A WordPress plugin is executable PHP code that runs with the same privileges as WordPress itself. Once activated, it can read the database, create administrator accounts, write files anywhere the web server has permission, and send data to remote servers. That is why the moment before you click "Install Now" is the cheapest point to stop an infection. Buying from a store that distributes original, unmodified GPL files, such as https://wplend.net/, removes most of the risk at the source, but a responsible site owner still verifies every ZIP archive before it reaches a live server. This guide walks through the checks in order, from the fastest to the most thorough, and names the free tools that handle each stage.

Why Plugin ZIP Files Are a Common Infection Route

Attackers rarely need to break into a WordPress site when the owner will install the backdoor voluntarily. The usual carrier is a "nulled" plugin: a commercial product with its license check removed and redistributed for free on forums, file-sharing sites, and lookalike download portals. The person who removed the license check often adds something extra.

The added code tends to follow a few business models. Some injects spam links or redirects visitors from search results to gambling and pharmacy pages, but only for visitors who are not logged in, so the owner never sees it. Some creates a hidden administrator account and phones home with the site URL, turning the site into an asset that is later sold or used for phishing pages. Some loads a cryptocurrency miner in visitors' browsers. A well-known family called WP-VCD spread for years through pirated themes and plugins; it planted files in the WordPress core directories, re-infected every theme on the server, and survived the removal of the original plugin.

Legitimate sources can also deliver bad code. A plugin author's account can be compromised, a plugin can be sold to a new owner who adds tracking, or an abandoned plugin can contain an exploitable flaw. These cases are rarer, but they explain why verification is a habit rather than a reaction to a suspicious download.

What Malicious Plugin Code Looks Like

Before running any tool, it helps to know what the tools are searching for. Malicious PHP rarely announces itself. It is usually short, buried in a long file, and disguised as encoded data. The table below lists the patterns that appear most often and how they differ from legitimate use of the same functions.

PatternWhy attackers use itLegitimate useHow to tell the difference
eval() combined with base64_decode(), gzinflate(), or str_rot13()Executes hidden code that cannot be read in the sourceAlmost none in modern pluginsNested decoding inside eval is a near-certain sign of malware
assert() or create_function() with a variable argumentOlder ways to execute strings as codeRare; create_function was removed in PHP 8.0Any variable from $_GET, $_POST, or $_COOKIE reaching these functions is hostile
wp_create_user() or wp_insert_user() with administrator roleCreates a backdoor accountMembership and registration pluginsCheck whether the call runs on a form submission or silently on init
pre_user_query filter modifying the user listHides a specific account from the Users screenVery rareA filter that excludes one hardcoded username is a hiding mechanism
file_put_contents() or fopen() writing .php filesDrops additional backdoorsCache and backup pluginsLook at the target path; writes into wp-includes or theme folders are suspicious
wp_remote_get() or curl_exec() to an unknown domainDownloads payloads or reports the site to a controllerUpdate checks, license servers, APIsCompare the domain with the vendor's real domain
Long strings of hex (\x65\x76\x61\x6c) or chr() concatenationSpells out function names to avoid scannersNoneDecoding the string usually reveals eval, system, or a URL
JavaScript that writes <script src=...> with a computed URLInjects redirects or miners on the front endAnalytics and ad pluginsObfuscated or minified code that builds a URL character by character is suspect

No single match proves infection. A backup plugin will legitimately write files, and a page builder will legitimately call remote APIs. Context decides the verdict, which is why the steps below combine automated scanning with manual reading.

Step 1: Verify the Source and the File Itself

Confirm where the ZIP came from

Free plugins should come from the WordPress.org directory or the developer's own site. Commercial plugins should come from the vendor or from a GPL distributor that states it supplies original, unmodified files. Download links in YouTube descriptions, Telegram channels, and "free premium plugins" blogs are the main distribution channel for nulled archives. If a download requires disabling antivirus software or completing a survey, delete it.

Check the file name as well. A WordPress.org archive is named after the plugin slug, sometimes with the version number, for example contact-form-7.5.9.8.zip. Names such as elementor-pro-nulled-activated.zip describe exactly what you should not install.

Compare checksums when the vendor publishes them

Some vendors list a SHA-256 hash next to each release. Computing the hash of your copy takes seconds:

# Linux
sha256sum plugin-name.zip

# macOS
shasum -a 256 plugin-name.zip

# Windows PowerShell
Get-FileHash .\plugin-name.zip -Algorithm SHA256

If the output matches the published value character for character, the file has not been altered since the vendor packaged it. A mismatch means the archive was modified, corrupted, or belongs to a different version. Do not install it until the cause is clear.

Step 2: Upload the ZIP to a Multi-Engine Scanner

VirusTotal is the standard first pass. It accepts files up to 650 MB through the web interface, unpacks ZIP archives, and runs the contents through roughly 70 antivirus engines. The report shows which engines flagged which inner file, so a detection on includes/class-loader.php tells you exactly where to look next.

Read the results with two limits in mind. First, antivirus engines are tuned for Windows executables and catch PHP backdoors less reliably. A result of 0 out of 70 lowers the risk but does not clear the file. Second, one or two detections on a well-known plugin are often false positives caused by legitimate obfuscation, such as licensing code encoded with ionCube. Several detections naming a webshell or backdoor family, for example "PHP.Backdoor" or "Trojan.PHP.Agent", deserve full attention.

Files uploaded to VirusTotal become available to its security partners. If an archive contains a personal license key or private customer data, scan only the extracted PHP files or use the local tools described in Step 6.

Step 3: Unpack the Archive and Inspect Its Structure

Extract the ZIP into an empty folder on your computer, never into a web-accessible directory. Then look at the file tree before reading any code. Structural anomalies are often faster to spot than malicious lines. Warning signs include:

  • PHP files with misleading names such as wp-tmp.php, class.wp.php, wp-feed.php, or license.php in a plugin that has no licensing system.
  • PHP code inside files with non-PHP extensions, such as .ico, .png, .txt, or .log; a real image does not begin with <?php.
  • Files placed outside the plugin's own folder, such as a wp-includes or themes directory bundled inside the ZIP.
  • A single PHP file far larger than its neighbours, for example a 400 KB functions.php in a plugin whose other files are 5–20 KB.
  • Modification dates that differ from the rest of the release by months or years.
  • Hidden files starting with a dot, such as .htaccess rules that execute PHP in an uploads folder.

On Linux and macOS, two commands surface most of these issues at once:

# List PHP code hidden in non-PHP files
grep -rl "<?php" plugin-folder/ --include="*.ico" --include="*.png" --include="*.jpg" --include="*.txt"

# Show the 15 largest files in the plugin
find plugin-folder/ -type f -exec ls -s {} + | sort -rn | head -15

Step 4: Search the Code for Dangerous Patterns

Text search is crude but fast, and it catches the majority of nulled-plugin payloads because those payloads are usually pasted in by hand rather than carefully engineered. Run a pattern search across the extracted folder:

grep -rnE "eval\s*\(|base64_decode\s*\(|gzinflate\s*\(|str_rot13\s*\(|assert\s*\(|create_function|shell_exec|passthru|system\s*\(" plugin-folder/

On Windows, the same search works in PowerShell with Select-String -Path .\plugin-folder\* -Pattern "eval\(|base64_decode" -Recurse or inside a code editor such as Visual Studio Code with regular-expression search enabled.

Expect some hits. base64_decode appears in plenty of clean plugins for encoding images, API tokens, or serialized settings. What matters is the surrounding code. A line like $icon = base64_decode($svg_data); that feeds an image tag is harmless. A line like eval(gzinflate(base64_decode('7X1rc9...'))); with a string thousands of characters long is not, because no legitimate developer hides readable PHP from their own users that way.

Also search for remote addresses:

grep -rnoE "https?://[a-zA-Z0-9./_-]+" plugin-folder/ | sort -u

The output lists every URL in the code. Expected entries include the vendor's domain, WordPress.org, and documented third-party APIs. Unknown domains, raw IP addresses, and URL shorteners require an explanation.

Step 5: Compare Against a Known-Clean Copy

This is the most reliable check available to non-specialists, because it does not depend on recognizing malware. It only shows what is different.

For a plugin hosted on WordPress.org, download the same version directly from the directory. The archive URL follows the format https://downloads.wordpress.org/plugin/plugin-slug.1.2.3.zip. Extract both copies and compare them:

diff -rq original-folder/ downloaded-folder/

The -q flag lists only which files differ or exist in one copy but not the other. Remove it to see the changed lines. Graphical tools such as WinMerge on Windows or Meld on Linux show the same comparison side by side. In an untouched archive, the output is empty. In a tampered one, you will typically see one or two added files and a few lines inserted at the top of the main plugin file.

For commercial plugins, the clean reference is a copy downloaded directly from the vendor's account area. If you hold a license for one site and received the plugin elsewhere for another, comparing the two copies settles the question in a minute.

Step 6: Run Static Scanners Locally

Several free tools analyze PHP without executing it. They differ in what they look for, and none covers everything.

ToolWhat it checksHow to run itMain limitation
VirusTotalKnown malware signatures across about 70 enginesWeb upload or free APIWeak on custom PHP backdoors; uploads are shared
php-malware-finderObfuscation and webshell patterns using YARA rulesCommand line on Linux or macOSProduces false positives on minified or encoded code
ClamAVGeneral malware signatures, including some PHP familiesclamscan -r plugin-folder/Limited PHP coverage without extra signature sets
Plugin Check (PCP)WordPress coding standards and security practices such as unescaped outputInstalled as a plugin on a test siteNot a malware scanner; flags weak code, not intent
Wordfence (free)Known malware signatures and changes against WordPress.org originalsInstalled on a test site, then a full scanScans only after installation, so use it on staging
WPScan vulnerability databasePublished security flaws by plugin and versionSearch by plugin name on its websiteCovers known vulnerabilities, not added malware

A practical combination for most site owners is VirusTotal for the archive, a grep pass for the obvious patterns, a diff against the original where one exists, and a Wordfence scan on a staging copy. Developers who review many plugins benefit from adding php-malware-finder, since its rules target exactly the obfuscation techniques found in nulled packages.

Step 7: Test the Plugin in an Isolated Staging Environment

Static analysis cannot see code that downloads its payload after activation. Some malicious plugins contain nothing suspicious in the archive and fetch the real backdoor from a remote server on first run. Behavioral testing closes that gap.

Create a disposable WordPress installation with LocalWP, DevKinsta, the wp-env tool, or a Docker container. It should have no real content, no real user accounts, and no credentials shared with production. Take a snapshot of the file system and the user list before activation:

find . -type f -exec sha256sum {} + > before.txt
wp user list --role=administrator

Activate the plugin, load several front-end and admin pages, and wait a few minutes so scheduled tasks can fire. Then repeat the snapshot and compare:

find . -type f -exec sha256sum {} + > after.txt
diff before.txt after.txt
wp user list --role=administrator
wp cron event list

A clean plugin may create cache files in wp-content/uploads or its own folder. It should not modify files in wp-includes, wp-admin, or other plugins, add administrator accounts, or register cron events with random names. The Query Monitor plugin shows outgoing HTTP requests made during each page load; requests to unknown domains on activation deserve investigation. Opening the site in a private browser window while logged out also matters, since injected redirects often target only anonymous visitors.

A Worked Example: Checking a Downloaded Slider Plugin

A small business owner receives a ZIP named premium-slider-pro-7.2.zip from a freelancer who says it came "from a friend's license". The owner wants to use it on a live store with customer accounts. The check proceeds like this:

  1. The vendor publishes no checksums, so the owner uploads the archive to VirusTotal. Three engines flag includes/wp-cache-helper.php as a PHP backdoor; the remaining files show no detections.
  2. Extracting the archive shows that wp-cache-helper.php is 38 KB, while every other file in the includes folder is under 12 KB. The vendor's public changelog never mentions a cache helper.
  3. A grep for eval returns one hit in that file: a single line containing eval(gzinflate(base64_decode( followed by a long encoded string.
  4. A search for remote URLs returns the vendor's update server and one unfamiliar domain registered under a free top-level domain.
  5. The main plugin file contains an extra require_once at line 3 that loads wp-cache-helper.php before the plugin's own code.
  6. On a LocalWP staging copy, activating the plugin creates a new administrator named wpsupport, which does not appear on the Users screen but shows up through wp user list. A new cron event named with eight random characters is also registered.

The verdict is clear after step 3, and steps 4 to 6 confirm the purpose: a hidden admin account plus a remote control channel. The owner deletes the archive, buys a legitimate copy, and diffs it against the tampered one. The diff shows exactly two differences, the added file and the added require_once line. The entire check took about twenty minutes, which is far less than cleaning a compromised store and notifying customers.

Keeping Malicious Code Off Your Site After Installation

Pre-installation checks cover one moment in time. A plugin that is clean today can receive a compromised update, and a vulnerability in any plugin can let an attacker write files later. A few ongoing measures keep the verification meaningful.

For plugins from WordPress.org, WP-CLI can confirm that installed files still match the official release: wp plugin verify-checksums --all reports any file that has been added or changed. Commercial plugins are not covered by this command, so a file-integrity monitor such as the one built into Wordfence or Sucuri's free plugin fills that gap by alerting on unexpected changes.

Update plugins from the same trusted source you originally used, and keep the number of installed plugins small. Every deactivated plugin still sitting in wp-content/plugins is code an attacker can call directly by URL if it contains a flaw, so delete what you do not use. Subscribe to a vulnerability feed such as WPScan or Patchstack for the plugins you run, since a published flaw is usually exploited within days. Finally, keep off-site backups with enough history to restore a version from before a suspected infection, not just last night's copy.

FAQs

Can a WordPress plugin contain malware even if it comes from the official directory?

Yes, although it is uncommon. The WordPress.org review team inspects new plugins, and malicious updates are usually removed quickly, but compromised developer accounts and plugins sold to new owners have caused incidents. Checking recent reviews, the support forum, and vulnerability databases before installing or updating reduces the risk further.

Is a clean VirusTotal result enough to trust a plugin?

No. VirusTotal engines detect known signatures and miss many custom or freshly obfuscated PHP backdoors. Treat a clean result as one passed check, then search the code for dangerous patterns and compare it with an original copy when possible.

Do I need programming skills to check a plugin?

Basic checks require none: verifying the source, uploading to VirusTotal, and looking for oddly named or oversized files. Grep searches and diffs need only copied commands. Reading and judging individual code matches is easier with some PHP knowledge, and when in doubt, a developer can review the flagged lines in minutes.

Why are nulled plugins so often infected?

Removing a license check requires editing the code, and the person doing it has no obligation to stop there. Distributing free copies of paid software also has no legitimate business model, so many distributors earn money by bundling backdoors, spam injectors, or ad scripts. Nulled copies also never receive security updates.

What should I do if I already installed a suspicious plugin?

Put the site in maintenance mode, deactivate and delete the plugin, then check for new administrator accounts, unknown cron events, and modified files in wp-includes, wp-admin, and theme folders. Change all passwords, including database and hosting credentials, and regenerate the security keys in wp-config.php. If changes appear in core files, restoring from a backup taken before the installation is usually faster and safer than manual cleanup.

Does a security plugin replace pre-installation checks?

No. Security plugins scan code that is already on the server, and by then a malicious plugin may have already run once, created accounts, or downloaded further payloads. Checking the ZIP first and testing on staging keeps the first execution away from the live site.

Conclusion

Checking a WordPress plugin for malware is a sequence of filters, each catching what the previous one missed. Source and checksum verification rule out tampered downloads. VirusTotal catches known malware. Inspecting the file structure and searching for patterns such as eval(base64_decode( expose the hand-made payloads common in nulled packages. A diff against an original copy shows every change regardless of how well it is disguised, and a staging test reveals behavior that static analysis cannot see, such as hidden admin accounts and remote downloads. Most of these steps take minutes and use free tools. Combined with buying plugins from trustworthy sources and monitoring file integrity after installation, they keep malicious code from ever reaching a production site.