checksum-command icon indicating copy to clipboard operation
checksum-command copied to clipboard

Fix plugin verification to detect directories without main PHP files

Open Copilot opened this issue 2 months ago • 3 comments

Summary

Fixes security issue where wp plugin verify-checksums skipped plugin directories without valid main plugin files, allowing malware to hide in these "shadow" directories.

Changes

1. Enhanced Plugin Detection (get_all_plugin_names())

  • Scans wp-content/plugins/ filesystem for all plugin directories
  • Includes both plugins with valid headers AND directories without headers
  • Security measures:
    • Directory readability validation
    • Symlink exclusion to prevent traversal attacks
    • Proper error handling

2. Enhanced Plugin Fetcher (UnfilteredPlugin::get())

  • Detects plugin directories even when main files are missing
  • Constructs conventional plugin file path for verification

3. Version Detection Fallback (detect_version_from_directory())

  • Attempts to extract version from readme.txt (Stable tag)
  • Falls back to scanning PHP files for Version header
  • Security hardening:
    • File size limit constant (1MB max)
    • Readability checks before operations
    • Error suppression to prevent path disclosure
    • Proper glob() failure handling

4. User Warnings

  • Clear warning when main plugin file is missing during verification
  • Helps administrators identify potential security issues

5. Test Coverage

  • Comprehensive test scenario for missing main file case
  • Validates renamed files are detected
  • Validates warnings are shown
  • Tests both single plugin and --all flag scenarios

Security Impact

Before: Attackers could hide malicious files in plugin directories by removing/renaming the main plugin file. These directories were completely ignored during checksum verification.

After: All plugin directories are verified against WordPress.org checksums, regardless of whether they have valid plugin headers. Malware cannot hide using this technique.

Backward Compatibility

✅ Fully backward compatible - existing plugins with valid headers work exactly as before ✅ Only affects detection of plugin directories without valid main files (previously ignored) ✅ No breaking changes to API or command syntax

Files Changed

  • src/Checksum_Plugin_Command.php: +96 lines
  • src/WP_CLI/Fetchers/UnfilteredPlugin.php: +11 lines
  • features/checksum-plugin.feature: +34 lines (test)

Total: 141 lines added, 2 lines removed

Testing Status

  • [x] PHP syntax validation passed
  • [x] Code review completed and all issues addressed
  • [x] Security hardening implemented
  • [x] Code style issues fixed
  • [ ] Behat tests (requires full test environment)
Original prompt

This section details on the original issue you should resolve

<issue_title>Verification checks skipped when plugin missing main PHP file</issue_title> <issue_description>Recently ran into a situation where a website had malware reinfection issues which required a bit of a deep dive to resolve. During the process I discovered that wp plugin verify-checksums --all will only check plugins which have their main plugin.php file. For example, let's install a plugin then break the main file by renaming:

wp plugin install wordfence
mv wp-content/plugins/wordfence/wordfence.php wp-content/plugins/wordfence/wordfence.php.bad

Now if we try and run wp plugin verify-checksums wordfence we'll get the following:

Warning: The 'wordfence' plugin could not be found.
Error: You need to specify either one or more plugin slugs to check or use the --all flag to check all plugins.

Also if we run wp plugin verify-checksums --all it will say success and not even attempt to run any checks on the /wordfence/ directory. This is a problem as bad actors can use this method to hide files in these shadow plugin folders. Also there is no indication that these PHP files exist from /wp-admin/plugins.php.

I think the solution should be to run checksums verifications based solely on the directory names. If a plugin directory matches a wordpress.org plugin then maybe run the verification checks?</issue_description>

Comments on the Issue (you are @copilot in this section)

@danielbachhuber Interesting report!

I agree, this is worth putting together an enhancement for.</comment_new>

  • Fixes wp-cli/checksum-command#121

💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot avatar Nov 01 '25 19:11 Copilot

Codecov Report

:x: Patch coverage is 75.60976% with 10 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/Checksum_Plugin_Command.php 72.97% 10 Missing :warning:

:loudspeaker: Thoughts on this report? Let us know!

codecov[bot] avatar Nov 01 '25 20:11 codecov[bot]

@copilot Code style issues:

  1. Empty ELSEIF statement detected
  2. Call to function is_array() with array<int, string> will always evaluate to true. in src/Checksum_Plugin_Command.php:318

swissspidy avatar Nov 01 '25 20:11 swissspidy

@copilot Code style issues:

  1. Empty ELSEIF statement detected ...

Fixed in commit 9a4be37:

  1. Removed empty elseif block - converted to standalone comment
  2. Removed redundant is_array() check - scandir() returns array|false, so checking false !== $dirs is sufficient

Copilot avatar Nov 01 '25 21:11 Copilot