Extending WP-CLI Plugin Verify Checksums

WP-CLI’s plugin verify checksums is an awesome builtin security feature. With a single command wp plugin verify-checksums --all you can make sure all of your WordPress.org plugins are valid and haven’t been tampered with. It’s one of the first commands I’ll reach to when malware is suspected. However many popular plugins are not on WordPress.org which means they are not included with the verify checks. Let’s explore how we might extend WP-CLI’s plugin verify-checksums to cover all plugins, not just WordPress.org plugins.

Uncovering how the current checksums are generated

The source code that powers WordPress.org is open source. After a bit of digging I found the internal code that is responsible for generating the plugin checksums on WordPress.org. That code produces a checksums for every version of every plugin uploaded to WordPress.org. Here is an example of what a WooCommerce checksum file looks like.

Example of WooCommerce v8.6.0 checksums

Planning out plugin checksums for plugins not on WordPress.org

Ideally I’d just like to run the same wp plugin verify-checksums --all command to check all plugins. There are couple problems. First, we’d need to build out own library of checksums or pull from a centralized 3rd party. And second, we’ll need to fallback to our own checks when a plugin is not found on WordPress.org. To do any of this we’d need to make changes to the existing WP-CLI command or create our own WP-CLI command.

Possible solutions for this have been discussed within WP-CLI here: https://github.com/wp-cli/ideas/issues/167. My first idea was to hook into it the when plugins are updated and generate local checksums. Considering anyone or any code can update plugins at anytime this method seems unlikely to be reliable. However maybe better then nothing? My next idea was to just build my own central repository of checksums. I figured I already own a bunch of paid WordPress plugins so I could probably source quite a few plugins myself. Also I might be able to extract checksums from CaptainCore, my WordPress management tool which is powering Anchor Hosting.

So begins WP Beacon, checksums for the WordPress community

The name WP Beacon comes from a few years back when I needed a place to fork a few plugins for PHP 8 compatibility. I always thought that name would be great for something security or health related in the WordPress space. So after a day stuck with the idea of extending plugins checksums I created a landing page for WP Beacon which I’m describing as checksums for the WordPress community.

After adapting the code which powers WordPress.org checksum creation I now have a WP-CLI command which can generate checksums for any locally installed WordPress plugin wp beacon plugin generate-checksums. This is useful for generating checksums for an individual plugin or in bulk for many. For example the following script will generate checksums for most recent versions of ACF Pro.

versions=(6.2.7 6.2.6 6.2.5 6.2.4 6.2.3 6.2.2 6.2.1.1 6.2.1 6.2.0)
license_key=MY_ACF_LICENSE_KEY
cd wp-content/plugins/

for version in ${versions[@]}; do
	echo "Downloading ACF $version"
	url="https://connect.advancedcustomfields.com/v2/plugins/download?p=pro&k=${license_key}&t=$version"
	if [ -f "advanced-custom-fields-pro.zip" ]; then
		rm advanced-custom-fields-pro.zip
	fi
	if [ -d "advanced-custom-fields-pro" ]; then
		rm -rf advanced-custom-fields-pro/
	fi
	wget -q "$url" -O advanced-custom-fields-pro.zip
	unzip -qq advanced-custom-fields-pro.zip
	wp beacon plugin generate-checksums advanced-custom-fields-pro --disable-remote-check
done

All was going good until I discovered that many plugins have dirty releases which make checksums impractical

The whole idea of checksums is that for any released version of a plugin there is manifest that says these files should contain this content. This falls apart if you or the developer is making changes to files in between versions. So after some initial testing of wp beacon plugin verify-checksums --all I discovered many plugins, not on WordPress.org, have dirty changes. An example of that comes from WPMU DEV Dashboard plugin where many files on the same version number had single line changed.

Git diff of WPMU DEV Dashboard changelog.txt

I’m sure that arbitrary line serves some purpose for WPMU DEV Dashboard however it exposes the reality with WordPress that many of us already know. With WordPress you the developer have the power to do anything you want. There is nothing in WordPress that says you can’t make file changes without updating the plugin version number. This limits the usefulness of checksums to only plugins that follow WordPress.org workflow, where a new version is required when files are changed.

Conclusion and alternative ideas

While my initial idea of solving WordPress plugins for the community might be a bit out of reach, this weekend project was not all for nothing. Here are my takeaways.

  • WP Beacon is a great way to extend checksums for plugins not currently checked.
  • A community sourced checksums might be possible for a selected plugins which follow strict version changes and/or can issue updated checksums when changes happen between releases.
  • Checksums can only take you so far. Just because they fail doesn’t mean your plugins are comprised. Being able to see file level changes is far more valuable and should be coupled with checksums.

I think my next step will be to revisit a git based approach for tracking file level changes. That’s something I’m already doing here at Anchor Hosting and I think could be more valuable then simply verifying checksums.