plugin-update-checker icon indicating copy to clipboard operation
plugin-update-checker copied to clipboard

JSON Security?

Open diamonddev2017 opened this issue 4 years ago • 9 comments

Hello,

Forgive me for probably a basic question, but I'm curious. Is it okay from a security point of view to have the details.json publicly accessible with no authentication? Is that normal and okay to do? Is there a way to maybe password protect it with something like this maybe? https://johnblackbourn.com/wordpress-http-api-basicauth/

I followed the Getting Started section and was able to get it working with no issues, just want to make sure I'm not missing something I'm supposed to do for security (injection, backdoors, etc). Obviously that's very important, and I'm not sure.

Thank you!

diamonddev2017 avatar Jul 06 '21 05:07 diamonddev2017

I guess the answer is "it depends". Does the .json file contain anything that you wouldn't want to be public? For example, if you have a commercial plugin or theme and you want only paying customers to be able to download updates, leaving the update data accessible is probably not safe. Anyone who knows the URL could look at the data, get the download link from the download_url field, and download the plugin or theme without paying. On the other hand, if you're just distributing a free plugin, it's probably fine to leave the JSON publicly accessible (but still use HTTPS).

This update checker doesn't include any built-in support for password-based authentication. It does have some hooks and filters that might help implement custom authentication mechanisms without modifying library code. If you have any questions about that, feel free to ask.

For one of my own plugins, I leave most of the JSON data public, but I only show the download_url field if the request includes a valid license key. It's a custom implementation on top of this update checker and wp-update-server.

YahnisElsts avatar Jul 06 '21 15:07 YahnisElsts

@YahnisElsts First off thank you very much for taking the time to reply. I think I understand now, so other than data in the JSON I wouldn't want seen (like the download URL in the example you gave) it's probably okay to leave the JSON file public. With that said, I would like to lock it down a bit if I can. I would be interested in those hooks and filters you mentioned, might be something I can setup, if not for this project a future one.

I think I found a solution that might work for my specific case. My plugin is something that will only be installed on sites we host (across a few servers). I'll share my idea so maybe it will help somebody else, and I'd be curious to hear your thoughts on it.

  • Upload details.json and plugin.zip to directory.
  • Create .htaccess file with:
order deny,allow
deny from all
allow from 1.2.3.4
  • Set the allow from IP to my server's IP where the website is being hosted, and the plugin updates will be requested from.
  • So nobody can access that directory, including the json and zip except my server checking for updates.
  • It seems to work well, I tested it by first not adding my server ip to allow from. Nobody can directly access either file and when I checked for updates in WP I got this (which is good in this case!):
Could not determine if updates are available for Site Toolkit.
HTTP response code is 403 (expected: 200) puc_unexpected_response_codegot

Then when I added my server IP, the file still couldn't be accessed directly, but it updated within WP perfectly fine.

I know this is a very specific case, and wouldn't work if you're distributing your plugin to tons of people (all with different hosting) but since this plugin will only be used on the sites we build/host it seems like it will work well and keep it all secure!

diamonddev2017 avatar Jul 06 '21 17:07 diamonddev2017

There are two main filters that seem relevant:

  • puc_request_info_query_args-$slug can be used to filter metadata URL query arguments. The filter receives one parameter: an associative array of query arguments. You can use it to dynamically add something to the metadata URL, like the site URL, a license key, customer ID, etc.
  • puc_request_info_options-$slug lets you filter the options that the update checker passes to wp_remote_get(). This is where you would add your Authorization header and anything like that.

YahnisElsts avatar Jul 06 '21 18:07 YahnisElsts

Hello, you write in the instructions to "Self-hosted Plugins and Themes" on point 3. "Upload the JSON file to a publicly accessible location". Is it also possible to use a physical path on the own server and not a an URL?

Within plugin.json I have used a physical path as download_url and this works. But it seems the location of the JSON as argument for Puc_v4_Factory::buildUpdateChecker have to be an URL and a path isn't supported.

We want to use the Plugin Update Checker to roll out our self developed Plugins within our infrastructure (instead of GIT).

Best regards, Ben

B-e-n-G avatar Feb 07 '22 14:02 B-e-n-G

Technically, PUC only supports URLs, but you could probably make it use a file path instead with a few small changes.

I think the main thing that you would have to change is the requestMetadata method in /Puc/v4p11/UpdateChecker.php. Get rid of $queryArgs, $options, and the add_query_arg() call, then replace wp_remote_get with file_get_contents (with one argument). You would also need to either get rid of the HTTP response validation or put the file data in a fake response object that always passes validation.

YahnisElsts avatar Feb 07 '22 17:02 YahnisElsts

Thanks a lot, your answer was very helpful! I'm using the filter in the requestMetadata method and fake the results with following code:

$json = '/var/www/html/plugin.json'
$slug = basename( __FILE__, '.php' );
$slug = ( $slug !== 'functions' ? $slug : get_template() );
$type =  ( $slug !== 'functions' ? 'plugin' : 'theme' );

$puc_request_filter = sprintf(
	'puc_request_metadata_http_result%s-%s',
	( $type === 'theme' ? '_theme' : '' ),
	$slug
);

add_filter( $puc_request_filter, function( $result, $url, $options ) use ( $json ) {
	if ( file_exists( $json ) ) {
		return array(
			'response' => array( 'code' => 200 ),
			'body' => file_get_contents( $json ),
		);
	}

	return $result;
}, 10, 3 );

add_action( 'after_setup_theme', function() {
	require_once( plugin_dir_path( __FILE__ ) . 'vendor/plugin-update-checker/plugin-update-checker.php' );

	$update_checker = Puc_v4_Factory::buildUpdateChecker(
		$json,
		__FILE__,
		$slug
	);
} );

This code is working well for themes and plugins. My original approach is more object orientated, has various checks and use a wrapper class for the Updater Checker, but this snippet is less complex.

B-e-n-G avatar Feb 07 '22 19:02 B-e-n-G

That's a good idea, using the filter is probably simpler. It does have the downside that PUC will still try to make an HTTP request to an invalid URL every time, but that might not matter much in practice.

By the way, you could simplify this a bit more by using the addFilter() utility method. It will automatically add the correct suffix to the filter name. For example:

$updateChecker->addFilter('request_metadata_http_result', function($result, $url, $options) {
    //...
}, 10, 3);

YahnisElsts avatar Feb 07 '22 19:02 YahnisElsts

Thanks for the tip with $updateChecker->addFilter(...);, this works fine!

B-e-n-G avatar Feb 07 '22 20:02 B-e-n-G

This might be too obvious, but just wanted to share a way you can handle this while also checking a license (for those with paid plugins).

Instead of using a path to json like ''https://example.com/path/to/details.json', you can send the request for the JSON to your own website such as:

$myUpdateChecker = PucFactory::buildUpdateChecker(
        'https://yoursite.com.com/wp-json/licensing/v1/request?email='.$email.'&license='.$license.'&requesttype=autoupdates,
	__FILE__,
        'slug'
);

Then on your website you can check the license and either return a valid JSON containing the download URL, or not.

This code (not my blog) is a good starting point for adding an endpoint that can process the request and return a JSON response: https://www.pontikis.net/blog/how-to-create-custom-endpoints-in-wordpress-rest-api

kingplugins avatar Nov 19 '22 05:11 kingplugins