Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugins need the ability to relax some content security policies in order to work properly #3336

Closed
TheWitness opened this issue Mar 10, 2020 · 4 comments
Labels
bug Undesired behaviour resolved A fixed issue
Milestone

Comments

@TheWitness
Copy link
Member

Describe the bug
Some plugins including mapping plugins need to relax content security policies in Cacti in order to function correctly. These plugins worked in the past but are now blocked.

@TheWitness
Copy link
Member Author

There is a new configuration option called: content_security_policy_script. It's hidden, in the example provided in the forums, the script should check the setting, and then append it as required. Something like:

set_config_option('https://maps.googleapis.com https://maps.gstatic.com');

No trailing semicolon required.

TheWitness added a commit that referenced this issue Mar 10, 2020
Plugins such as mapping plugins need to relax content security policies
@TheWitness TheWitness added bug Undesired behaviour resolved A fixed issue labels Mar 10, 2020
@TheWitness TheWitness added this to the 1.2.11 milestone Mar 10, 2020
@netniV netniV changed the title Plugins such as mapping plugins need to relax content security policies Plugins need the ability to relax some content security policies in order to work properly Apr 5, 2020
@warnesj
Copy link

warnesj commented May 18, 2020

I know this is a closed issue but I wanted to share some troubleshooting I did for an issue with the gpsmaps plugin (Cacti/plugin_gpsmap#11) which is related to this.

I attempted to use the hidden content_security_policy_script option by adding
set_config_option('content_security_policy_script', 'https://maps.googleapis.com https://maps.gstatic.com http://maps.gstatic.com https://fonts.gstatic.com');
in ./plugins/gpsmap/gpsmap.php (the plugin main file) but continued to have problems.

Problem 1

When using domain names as a source, they are not to be enclosed in single quotes (''). At least that's what I could find for the Source List Reference from https://content-security-policy.com/#source_list. It looks like when reading the content_security_policy_script setting in both ./include/global.php and ./lib/html.php that single quotes are forced onto the value.

Problem 2

That plugin relies on a few different Content-Security-Policy elements:

I think your idea of the hidden content_security_policy_script is a good one and perhaps could be expanded to include some other elements like default-src, img-src, etc.? And perhaps during the reading of the settings that a regex search is performed to see if the content is a domain name which would signal the skipping of adding single quotes?

I'm at best a moderate PHP coder but my Git experience is....shallow. I'd love to tinker and expand upon your work but I'm not sure the best way.

@netniV
Copy link
Member

netniV commented May 19, 2020

single quotes are forced onto the value

This is likely because it's using our escaping routines to prevent HTML/Script injection.

@warnesj
Copy link

warnesj commented May 20, 2020

Possibly. The current code in ./include/global.php and ./lib/html.php for the script policy doesn't appear to be related to HTML/Script injection protection. But I'm not nearly as experienced with it as the Cacti Team is.

I was hacking away a bit to see if I could expand upon the initial idea and here is what I came up with. Not sure if it's helpful at all.

In ./include/global.php @ line 420 I changed,

	/* increased web hardening */
	$script_policy = read_config_option('content_security_policy_script');
	if ($script_policy != '0' && $script_policy != '') {
		$script_policy = "'$script_policy'";
	}
	header("Content-Security-Policy: default-src *; img-src 'self' data: blob:; style-src 'self' 'unsafe-inline'; script-src 'self' $script_policy 'unsafe-inline'; frame-ancestors 'self'; worker-src 'self'");

to

	/* increased web hardening */
	/* Content-Policy-Setting script-src */
	$csp_script_policy = " ";	// Set leading whitespace to have something to trim in case the setting is empty
	$csp_script_policy_setting = read_config_option('content_security_policy_script');
	if ($csp_script_policy_setting != '0' && $csp_script_policy_setting != '') {
		$csp_script_policies = explode(" ", $csp_script_policy_setting);
		foreach ($csp_script_policies as $csp_script_policy_value) {
			if (preg_match("/\b(https?:\/\/)?((?=[a-z0-9-]{1,63}\.)[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b/i", $csp_script_policy_value)) {
				$csp_script_policy = $csp_script_policy . "$csp_script_policy_value ";
			} else {
				$csp_script_policy = $csp_script_policy . "'$csp_script_policy_value' ";
			}
		}
		unset($csp_script_policy_value);
	}
	trim($csp_script_policy);	// Remove leading and trailing whitespace

	/* Content-Policy-Setting default-src */
	$csp_default_policy = " ";	// Set leading whitespace to have something to trim in case the setting is empty
	$csp_default_policy_setting = read_config_option('content_security_policy_default');
	if ($csp_default_policy_setting != '0' && $csp_default_policy_setting != '') {
		$csp_default_policies = explode(" ", $csp_default_policy_setting);
		foreach ($csp_default_policies as $csp_default_policy_value) {
			if (preg_match("/\b(https?:\/\/)?((?=[a-z0-9-]{1,63}\.)[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b/i", $csp_default_policy_value)) {
				$csp_default_policy = $csp_default_policy . "$csp_default_policy_value ";
			} else {
				$csp_default_policy = $csp_default_policy . "'$csp_default_policy_value' ";
			}
		}
		unset($csp_default_policy_value);
	}
	trim($csp_default_policy);	// Remove leading and trailing whitespace

	/* Content-Policy-Setting img-src policy setting */
	$csp_image_policy = " ";	// Set leading whitespace to have something to trim in case the setting is empty
	$csp_image_policy_setting = read_config_option('content_security_policy_image');
	if ($csp_image_policy_setting != '0' && $csp_image_policy_setting != '') {
		$csp_image_policies = explode(" ", $csp_image_policy_setting);
		foreach ($csp_image_policies as $csp_image_policy_value) {
			if (preg_match("/\b(https?:\/\/)?((?=[a-z0-9-]{1,63}\.)[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b/i", $csp_image_policy_value)) {
				$csp_image_policy = $csp_image_policy . "$csp_image_policy_value ";
			} else {
				$csp_image_policy = $csp_image_policy . "'$csp_image_policy_value' ";
			}
		}
		unset($csp_image_policy_value);
	}
	trim($csp_image_policy);	// Remove leading and trailing whitespace

	/* Content-Policy-Setting style-src policy setting */
	$csp_style_policy = " ";	// Set leading whitespace to have something to trim in case the setting is empty
	$csp_style_policy_setting = read_config_option('content_security_policy_style');
	if ($csp_style_policy_setting != '0' && $csp_style_policy_setting != '') {
		$csp_style_policies = explode(" ", $csp_style_policy_setting);
		foreach ($csp_style_policies as $csp_style_policy_value) {
			if (preg_match("/\b(https?:\/\/)?((?=[a-z0-9-]{1,63}\.)[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b/i", $csp_style_policy_value)) {
				$csp_style_policy = $csp_style_policy . "$csp_style_policy_value ";
			} else {
				$csp_style_policy = $csp_style_policy . "'$csp_style_policy_value' ";
			}
		}
		unset($csp_style_policy_value);
	}
	trim($csp_style_policy);	// Remove leading and trailing whitespace
	
	header("Content-Security-Policy: default-src 'self' $csp_default_policy; img-src 'self' $csp_image_policy data: blob:; style-src 'self' $csp_style_policy 'unsafe-inline'; script-src 'self' $csp_script_policy 'unsafe-inline'; frame-ancestors 'self'; worker-src 'self'");

And then in ./lib/html.php @ line 2355 I changed,

	$script_policy = read_config_option('content_security_policy_script');
	if ($script_policy != '0' && $script_policy != '') {
		$script_policy = "'$script_policy'";
	}

	?>
	<meta http-equiv='X-UA-Compatible' content='IE=Edge,chrome=1'>
	<meta name='apple-mobile-web-app-capable' content='yes'>
	<meta name='description' content='Monitoring tool of the Internet'>
	<meta name='mobile-web-app-capable' content='yes'>
	<meta http-equiv="Content-Security-Policy" content="default-src *; img-src 'self' data: blob:; style-src 'self' 'unsafe-inline'; script-src 'self' <?php print $script_policy;?> 'unsafe-inline'; worker-src 'self'">

to

	/* Content-Policy-Setting script-src policy setting */
	$csp_script_policy = " ";	// Set leading whitespace to have something to trim in case the setting is empty
	$csp_script_policy_setting = read_config_option('content_security_policy_script');
	if ($csp_script_policy_setting != '0' && $csp_script_policy_setting != '') {
		$csp_script_policies = explode(" ", $csp_script_policy_setting);
		foreach ($csp_script_policies as $csp_script_policy_value) {
			if (preg_match("/\b(https?:\/\/)?((?=[a-z0-9-]{1,63}\.)[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b/i", $csp_script_policy_value)) {
				$csp_script_policy = $csp_script_policy . "$csp_script_policy_value ";
			} else {
				$csp_script_policy = $csp_script_policy . "'$csp_script_policy_value' ";
			}
		}
		unset($csp_script_policy_value);
	}
	trim($csp_script_policy);	// Remove leading and trailing whitespace

	/* Content-Policy-Setting default-src */
	$csp_default_policy = " ";	// Set leading whitespace to have something to trim in case the setting is empty
	$csp_default_policy_setting = read_config_option('content_security_policy_default');
	if ($csp_default_policy_setting != '0' && $csp_default_policy_setting != '') {
		$csp_default_policies = explode(" ", $csp_default_policy_setting);
		foreach ($csp_default_policies as $csp_default_policy_value) {
			if (preg_match("/\b(https?:\/\/)?((?=[a-z0-9-]{1,63}\.)[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b/i", $csp_default_policy_value)) {
				$csp_default_policy = $csp_default_policy . "$csp_default_policy_value ";
			} else {
				$csp_default_policy = $csp_default_policy . "'$csp_default_policy_value' ";
			}
		}
		unset($csp_default_policy_value);
	}
	trim($csp_default_policy);	// Remove leading and trailing whitespace

	/* Content-Policy-Setting img-src policy setting */
	$csp_image_policy = " ";	// Set leading whitespace to have something to trim in case the setting is empty
	$csp_image_policy_setting = read_config_option('content_security_policy_image');
	if ($csp_image_policy_setting != '0' && $csp_image_policy_setting != '') {
		$csp_image_policies = explode(" ", $csp_image_policy_setting);
		foreach ($csp_image_policies as $csp_image_policy_value) {
			if (preg_match("/\b(https?:\/\/)?((?=[a-z0-9-]{1,63}\.)[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b/i", $csp_image_policy_value)) {
				$csp_image_policy = $csp_image_policy . "$csp_image_policy_value ";
			} else {
				$csp_image_policy = $csp_image_policy . "'$csp_image_policy_value' ";
			}
		}
		unset($csp_image_policy_value);
	}
	trim($csp_image_policy);	// Remove leading and trailing whitespace
	
	/* Content-Policy-Setting style-src policy setting */
	$csp_style_policy = " ";	// Set leading whitespace to have something to trim in case the setting is empty
	$csp_style_policy_setting = read_config_option('content_security_policy_style');
	if ($csp_style_policy_setting != '0' && $csp_style_policy_setting != '') {
		$csp_style_policies = explode(" ", $csp_style_policy_setting);
		foreach ($csp_style_policies as $csp_style_policy_value) {
			if (preg_match("/\b(https?:\/\/)?((?=[a-z0-9-]{1,63}\.)[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b/i", $csp_style_policy_value)) {
				$csp_style_policy = $csp_style_policy . "$csp_style_policy_value ";
			} else {
				$csp_style_policy = $csp_style_policy . "'$csp_style_policy_value' ";
			}
		}
		unset($csp_style_policy_value);
	}
	trim($csp_style_policy);	// Remove leading and trailing whitespace

	?>
	<meta http-equiv='X-UA-Compatible' content='IE=Edge,chrome=1'>
	<meta name='apple-mobile-web-app-capable' content='yes'>
	<meta name='description' content='Monitoring tool of the Internet'>
	<meta name='mobile-web-app-capable' content='yes'>
	<meta http-equiv="Content-Security-Policy" content="default-src 'self' <?php print $csp_default_policy;?>; img-src 'self' <?php print $csp_image_policy;?> data: blob:; style-src 'self' <?php print $csp_style_policy;?> 'unsafe-inline'; script-src 'self' <?php print $csp_script_policy;?> 'unsafe-inline'; worker-src 'self'">

What this did was:

  1. Create 3 additional hidden settings: content_security_policy_image, content_security_policy_default, and content_security_policy_style.
  2. Allows for multiple values per setting.
  3. When domain names or URLs are a detected value, they aren't surrounded by single quotes. Other values are.

Then I could add the following lines to the beginning of ./plugins/gpsmap/gpsmap.php,

set_config_option('content_security_policy_default', 'https://fonts.gstatic.com');
set_config_option('content_security_policy_image', 'http://maps.gstatic.com https://maps.gstatic.com https://maps.googleapis.com');
set_config_option('content_security_policy_script', 'https://maps.googleapis.com');
set_config_option('content_security_policy_style', 'https://fonts.googleapis.com');

It seems to work okay, but it doesn't do much to sanitize the values (as you noted in your review of the earlier code submission).

Anyway, thought I'd share. Like I said, I'm not sure if it's helpful or useful.

@github-actions github-actions bot locked and limited conversation to collaborators Aug 19, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Undesired behaviour resolved A fixed issue
Projects
None yet
Development

No branches or pull requests

3 participants