Hello!

Being a Toronto based web design and development agency means that we interact with a significant number of WordPress sites. This tends to happen when a project starts (obviously), but often continues after a site is launched. This is something that we offer along the lines of “post launch maintenance”. By no means once a site is launched is our job done, and I’m sure a lot of other people in the industry can relate.

If you follow the WP Vulnerability database notifications (if you don’t, you should), then you will see many notifications per day with various plugin or core vulnerability announcements. For this reason, among many others, we found ourselves struggling to automate and streamline the management of many client websites.

We’re a fan of automation and have published articles on our efforts to integrate WordPress with Jenkins. With that particular plugin, we were able to automate the “push” process from propagating code changes to our client sites from development, staging and production environments. Jenkins handles the code migration and the push process is handled by our Jenkins wordpress plugin.

With code integration taken care of, we wanted an easy way to automate critical (and necessary) plugin / core updates across many sites (hosted in many different places for different clients). Long term the ideal scenario would be a secure web interface that offers an easy way to add / remove WordPress sites. Controlling updates across multiple sites would seamlessly be done in one place.

Developing that web interface is a time consuming process, but that doesn’t mean that we cant implement a framework for interacting with WordPress in this way! We looked at other open source software that offers similar features and decided to put the pieces together to implement a simple framework for passing certain commands and arguments to the plugin in a secure way, with json encoded responses that can be parsed by literally any programming language that has the ability to make web requests.

This means that we could create a simple python script that could submit a core WordPress update request to all our sites in a matter of minutes.

Shift8 Remote Management

We put together a minimal implementation that offers the ability to update,install,activate,deactivate your core,plugins or themes. You download the Shift8 Remote Management plugin if your interested in checking it out yourself.

What I’ll do is run through the basics of how the plugin works and how you interact with it.

How the plugin works

Requests are submitted to your WordPress site in the form of an HTTP POST. There are several key elements that must be contained within the POST request :

API Key

This is generated by the plugin in the settings. The key generation is a random string of 32 characters that must be used when submitting requests. The code to generate the API key is the following :

// Encryption key generation function shift8_remote_generate_api_key() { $encryption_key = bin2hex(openssl_random_pseudo_bytes(32)); return $encryption_key; } 1 2 3 4 5 // Encryption key generation function shift8_remote_generate_api_key ( ) { $ encryption_key = bin2hex ( openssl_random_pseudo_bytes ( 32 ) ) ; return $ encryption_key ; }

There are many many ways to accomplish this. We opted to use a simple solution that is still compatible with PHP versions that are lower than 7.

Command

To see a full list of commands you can view our plugin in the WordPress plugin directory. For example the command to update a plugin is “update_plugin”

Arugment and Arugment Value

For commands like update_plugin, we will need an argument and an argument value. This is because we want you to specify which plugin you want to update specifically.

To specify an argument to accompany the update_plugin command, then you would pass the argument “plugin” with the argument value of the Plugin name and the plugin file. So for “Hello Dolly”, it would be “Hello_Dolly/hello.php”.

Timestamp

To reduce the risk of stale commands being queued or multiple commands being sent, a timestamp field is required with every request. The reasons why a command may be stale are many. For example simple network connectivity issues from where you are sending commands may get them stuck in an outbound queue and not sent until they are received. This means that you may no longer wish that command to be executed for one reason or another.

Security Implications

Of course security is at the forefront of this entire solution. Obviously having an API Key in order to authenticate is important, but being able to properly escape all input processed by the plugin is paramount to avoiding scenarios where workarounds, injections and other security vulnerabilities are not exploited. The plugin has the power to do a lot with your WordPress site, so ensuring that the implementation is secure is paramount.

Sanitize all POST input

We are receiving $_POST input as a multi-dimensional array and processing actions against it. In order to ensure by the time we start processing actions against the input, the sender isn’t attempting to escape or exploit poor sanity checks in order to circumvent the originally intended action.

/** * Return an array of sanitized input (which should be an array as well) * * @return array */ function shift8_remote_sanitize( $arr ){ $result = array(); foreach ($arr as $key => $val){ $result[$key] = (is_array($val) ? shift8_remote_sanitize($val) : sanitize_text_field($val)); } return $result; } 1 2 3 4 5 6 7 8 9 10 11 12 /** * Return an array of sanitized input (which should be an array as well) * * @return array */ function shift8_remote_sanitize ( $ arr ) { $ result = array ( ) ; foreach ( $ arr as $ key =& gt ; $ val ) { $ result [ $ key ] = ( is_array ( $ val ) ? shift8_remote_sanitize ( $ val ) : sanitize_text_field ( $ val ) ) ; } return $ result ; }

We wanted a simple way to iterate over regular and multi-dimensional arrays. When the array is iterated we run the WordPress function sanitize_text_field against the value. The function above keeps iterating until there are no more dimensions to the array and returns the result.

This means we can use this one function to sanitize all the input in an easy and seamless way.

How to interact with the WordPress plugin to automate updates

The idea behind this plugin is for you to create your own way of interacting and automating your WordPress actions. Literally any programming language that is capable of web requests (i.e. Curl) should be able to work with this plugin.

Included in the plugin’s bin folder is an example Python script that shows you how easy it is to interact with the Shift8 Remote plugin (in 44 lines no less). I’ll break down what the plugin does so that you can better understand how you can either use the script or create your own script or web interface that can interact with the plugin.

#!/usr/bin/python import requests import json import sys from datetime import datetime import calendar ########################### # Check command arguments # ########################### if len(sys.argv) < 3 : print "

Usage Syntax :" print "

shift8-remote.py <command> " print "Argument is optional only if needed" print "

Example : shift8-remote.py http://shift8.local abc1234 update_plugin plugin Hello_Dolly/hello.php" sys.exit(0) </command> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #!/usr/bin/python import requests import json import sys from datetime import datetime import calendar ########################### # Check command arguments # ########################### if len ( sys . argv ) & lt ; 3 : print "

Usage Syntax :" print "

shift8-remote.py <command> " print "Argument is optional only if needed" print "

Example : shift8-remote.py http://shift8.local abc1234 update_plugin plugin Hello_Dolly/hello.php" sys . exit ( 0 ) < / command >

First and foremost we want to ensure that the right number of command arguments are passed. The above snippet shows exactly what is expected. The plugin will fail if the incorrect number of arguments are provided.

# Get unix timestamp to pass as variable d = datetime.utcnow() unixtime = calendar.timegm(d.utctimetuple()) 1 2 3 # Get unix timestamp to pass as variable d = datetime . utcnow ( ) unixtime = calendar . timegm ( d . utctimetuple ( ) )

We want the timestamp variable that is passed via this script to be current every time it is run. This means that the timestamp we send will never be expired and will always be valid, according to our plugin.

try: sys.argv[4] sys.argv[5] except: argument = None argument_value = None else: argument = sys.argv[4] argument_value = sys.argv[5] 1 2 3 4 5 6 7 8 9 try : sys . argv [ 4 ] sys . argv [ 5 ] except : argument = None argument_value = None else : argument = sys . argv [ 4 ] argument_value = sys . argv [ 5 ]

For some commands, an argument and argument value is not required. This means that if you wanted to upgrade_core, you dont need to pass any further arguments. With the above python logic, if nothing is entered for those arguments then they are nullified.

# Define arguments if applicable shift8_url = sys.argv[1] payload = { "shift8_remote_verify_key" : sys.argv[2], "timestamp" : unixtime, "actions[0]" : sys.argv[3], argument : argument_value, } headers = {'content-type': 'application/x-www-form-urlencoded'} response = requests.post(shift8_url, data=payload, headers=headers) print response.text 1 2 3 4 5 6 7 8 9 10 11 12 # Define arguments if applicable shift8_url = sys . argv [ 1 ] payload = { "shift8_remote_verify_key" : sys . argv [ 2 ] , "timestamp" : unixtime , "actions[0]" : sys . argv [ 3 ] , argument : argument_value , } headers = { 'content-type' : 'application/x-www-form-urlencoded' } response = requests . post ( shift8_url , data = payload , headers = headers ) print response . text

The last part of the python script is really where it all happens. We craft a payload with all the variables arranged and submit it using the Python Requests module. The response printed at the end is where you will see the confirmation from the plugin that it has processed the request. If you are listing all plugins, it will provide a JSON formatted list of plugins installed, for example.

I hope this plugin is useful to others out there! Efforts should be focused on developing solutions to interact with WordPress in this way so that automation, security and integration is seamless and easy.