WordPress-Coding-Standards icon indicating copy to clipboard operation
WordPress-Coding-Standards copied to clipboard

[Feature Request] XSS/EscapeOutput: add ability to pass (auto)escaped Methods

Open paulschreiber opened this issue 8 years ago • 34 comments

PHPCS 3.1, WPCS 0.13.0, PHP 5.6.30

A backslash used to indicate the global namespace prevents customAutoEscapedFunctions from being recognized.

I have a ruleset like this:

<?xml version="1.0"?>
<ruleset name="WordPress-Me">
  <rule ref="WordPress.XSS.EscapeOutput">
    <properties>
      <property name="customAutoEscapedFunctions" value="ESPN_AMP, AMP_HTML_Utils" type="array" />
    </properties>
  </rule>
</ruleset>

And some code like this:

public static function amp_additional_css_styles() {
  $css = 'color: black;';
  echo \ESPN_AMP::sanitize_meta_css( $css );
}

which generates this error:

 373 | ERROR | Expected next thing to be an escaping function (see Codex for 'Data Validation'), not '\'
     |       | (WordPress.XSS.EscapeOutput.OutputNotEscaped)

If I drop the backslash (\):

public static function amp_additional_css_styles() {
  $css = 'color: black;';
  echo ESPN_AMP::sanitize_meta_css( $css );
}

Then PHPCS no longer finds the error.

paulschreiber avatar Oct 05 '17 15:10 paulschreiber

@paulschreiber Without looking at the code in detail, the property expects to receive function names, not class or namespace names. Try passing it sanitize_meta_css instead.

jrfnl avatar Oct 05 '17 22:10 jrfnl

@jrfnl I'd tried that. When customAutoEscapedFunctions has value="sanitize_meta_css", PHPCS shows the same error "Expected next thing to be an escaping function" as above.

paulschreiber avatar Oct 05 '17 23:10 paulschreiber

I have come across issues with \ scoped items, but under a slightly different context (and I can't find the relevant issue).

GaryJones avatar Oct 05 '17 23:10 GaryJones

Ok, this will need further investigation. Related to #764

jrfnl avatar Oct 05 '17 23:10 jrfnl

#933 is the one I was thinking of.

GaryJones avatar Oct 05 '17 23:10 GaryJones

This looks just like #933.

paulschreiber avatar Oct 09 '17 02:10 paulschreiber

This doesn't seem to be fixed, or I'm doing something wrong. I have a code like

echo Components::render(
	'modal',
	array_merge(
		$attributes,
		[
			'modalContent' => Components::render('video', $attributes),
			'modalId' => $buttonModalId,
			'modalCarouselHeader' => true
		]
	)
);

echo Components::classnames(['hello']);

And in my ruleset, I tried adding

<rule ref="WordPress.Security.EscapeOutput">
	<properties>
		<property name="customAutoEscapedFunctions" type="array">
			<element value="render" />
			<element value="Components::render" />
		</property>
	</properties>
</rule>

But this doesn't work. What does work (kinda) is:

<rule ref="WordPress.Security.EscapeOutput">
	<properties>
		<property name="customAutoEscapedFunctions" type="array">
			<element value="Components" />
		</property>
	</properties>
</rule>

I say kinda because only the Components::render should be on the auto escaped list, not Components::classnames (and the above will just see Components and mark it as safe).

Also, I think the related issue is #413.

With phpcsutils, how hard would be to add static calls to auto escaped list?

dingo-d avatar Feb 23 '21 09:02 dingo-d

I kinda had to dig through the sniff and I think the issue is that the check for autoEscapedFunctions and escapingFunctions only happens inside the

if ( \T_STRING === $this->tokens[ $i ]['code'] ) {

part of loop through echo'd components.

So the function name that is 'extracted' out inside this conditional only ever evaluates to the first string token found. In my case that is Components.

So a problem is a bit deeper, and could probably touch some other sniffs (as it usually ends up). We'd need to have a utility to recognize the static method calls. I think this could be added in the part of #764.

I could try to modify the existing sniff to handle static methods. Roughly it would be adding this in the loop

if ($this->tokens[$i]['code'] === T_STRING) {
    $colon = $this->phpcsFile->findNext(Tokens::$emptyTokens, ($i + 1), null, true);
    
    if ($this->tokens[$colon]['code'] === T_DOUBLE_COLON) {
        $methodName = $this->phpcsFile->findNext(T_STRING, ($colon + 1));
    
        if ($this->tokens[$methodName]['code'] === T_STRING) {
        	// Static method call. 
        }
    }
}

Then, inside we could check the

if (
	$is_formatting_function
	|| isset( $this->autoEscapedFunctions[ $functionName ] )
	|| isset( $this->escapingFunctions[ $functionName ] )
) {
	continue;
}

Am I on the right track?

dingo-d avatar Apr 14 '21 17:04 dingo-d

@dingo-d Good analysis, however IMO the bigger problem with this is not so much how to detect these methods being used, but how to allow them to be passed via a custom ruleset ?

Currently the property contains no keys and the function names as values.

If we would implement this ability to recognize methods, we would need to be able to distinguish between functions and methods to make sure that a method call using the same name as a global function from the "escaped list "doesn't cause a false negative and visa versa.

To that end, we'd need for methods to either have the class name or object name passed as well. The logical choice would be to add those as the array keys, however, a class could contain multiple escaping methods and if those would all use the same "key", then only the last method would actually be registered.

See the problem ?

jrfnl avatar Apr 14 '21 18:04 jrfnl

Yeah, tricky. Could we add a new public property That would have a name, type (array) and a value that would denote the class? Then for every class the elements would just be all the escaped functions?

Something like

<rule ref="WordPress.Security.EscapeOutput">
	<properties>
		<property name="customAutoEscapedMethods" type="array" value="ClassName">
			<element value="custom_method_1"/>
			<element value="custom_method_2"/>
			<element value="custom_method_3"/>
		</property>
	</properties>
</rule>

Not sure if something like that would work 🤷🏼‍♂️

Then we'd have a public property $customAutoEscapedMethods, that would be

$customAutoEscapedMethods = [
	'ClassName' => [
		'custom_method_1',
		'custom_method_2',
		'custom_method_3',
	]
]

Again, not sure if this is doable at all 🤷🏼‍♂️

dingo-d avatar Apr 15 '21 10:04 dingo-d

@dingo-d 1) That is not an allowed format in PHPCS (the extra value in the property tag) and 2) that would only allow for methods in one (1) class to be added as a second set for a second class would overwrite the first.

I've been thinking along the lines of:

<rule ref="WordPress.Security.EscapeOutput">
	<properties>
		<property name="customAutoEscapedMethods" type="array">
			<element key="custom_method_1" value="ClassName"/>
			<element key="custom_method_2 value="ClassName""/>
			<element key="custom_method_3" value="$objectName"/>
		</property>
	</properties>
</rule>

Still fiddly and still wouldn't allow for multiple methods with the same name in different classes, but could become workable.

jrfnl avatar Apr 15 '21 13:04 jrfnl

Yeah, so it's a limitation from the upstream. Are there any chances the configuration can be modified in v4 of phpcs?

dingo-d avatar Apr 15 '21 14:04 dingo-d

Yeah, so it's a limitation from the upstream. Are there any chances the configuration can be modified in v4 of phpcs?

Not really. More than anything, it's a limitation of the PHP array format which (rightfully) can not work with duplicate keys - or rather overwrites those.

jrfnl avatar Apr 15 '21 15:04 jrfnl

What I thought was: is there a way to provide properties that will be parsed in the sniff as

$customAutoEscapedMethods = [
	'ClassName' => [
		'custom_method_1',
		'custom_method_2',
		'custom_method_3',
	],
	'OtherClassName' => [
		'custom_method_1',
		'custom_method_2',
		'custom_method_3',
	]
]

That way, every class is a separate key, and values are methods that are escaped (so nothing gets overwritten).

dingo-d avatar Apr 15 '21 15:04 dingo-d

@dingo-d Well, we could make the value a comma-separated list of method names and then handle the exploding of that within the sniff. Does make it fiddly for ruleset configurators, but then again, it's advanced functionality.

<rule ref="WordPress.Security.EscapeOutput">
	<properties>
		<property name="customAutoEscapedMethods" type="array">
			<element key="ClassName" value="method_1,method2"/>
			<element key="OtherClassName" value="method_1,method_b"/>
		</property>
	</properties>
</rule>

jrfnl avatar Apr 15 '21 16:04 jrfnl

Well, we could make the value a comma-separated list of method names and then handle the exploding of that within the sniff. Does make it fiddly for ruleset configurators, but then again, it's advanced functionality.

Considering the limitations you've mentioned, I feel like this solution is still good in spite of its drawbacks. It is better than what we currently have.

I would vote in favor of something like this.

JPry avatar Apr 15 '21 18:04 JPry

In version V2 it silenced the error when I used, in V3 it shows the error again.

	<rule ref="WordPress.Security.EscapeOutput">
		<properties>
			<property name="customEscapingFunctions" type="array">
				<element value="Helpers"/>
			</property>
		</properties>
	</rule>

I've added the ignore to each. Or is there a better workaround? //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

janw-me avatar Sep 13 '23 20:09 janw-me

@janw-me IIRC nothing changed regarding that property in v3, so to get a feel for what you are seeing we'd need more information. Also not sure if this is the right issue for your issue.

jrfnl avatar Sep 13 '23 20:09 jrfnl

I'll double check tomorrow. But I'm pretty sure All I did was change the composer version, composer update and ran phpcs.

janw-me avatar Sep 14 '23 07:09 janw-me

@jrfnl I've recreated the bug in a test repo: https://github.com/janw-me/wpcs

I've tried to keep everything the same while strapping down the the unnecessary parts. The problem might be in the .dist.

janw-me avatar Sep 15 '23 10:09 janw-me

@janw-me can you check with the dev-hotifx/escape-output-sniff branch and see if it will work?

I think it should be fixed with the #2370

dingo-d avatar Sep 15 '23 10:09 dingo-d

@dingo-d It changes the output of the error to:

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 14 | ERROR | All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found
    |       | 'Helpers::wpcs_esc_str_rev'. (WordPress.Security.EscapeOutput.OutputNotEscaped)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Now it includes both the class and method, which is great :) So from found 'Helpers'. to 'found Helpers::wpcs_esc_str_rev'.

I've tried the following extra xml:

    <rule ref="WordPress.Security.EscapeOutput">
        <properties>
            <property name="customEscapingFunctions" type="array">
                <element value="wpcs_esc_str_rev"/>
                <element value="Helpers"/>
                <element value="Helpers::wpcs_esc_str_rev"/>
                <element value="Helpers\:\:wpcs_esc_str_rev"/>
                <element key="Helpers" value="wpcs_esc_str_rev"/>
            </property>
        </properties>
    </rule>

I still get the error.

PS I'm available all afternoon (on slack) for testing.

janw-me avatar Sep 15 '23 11:09 janw-me

I'll probably take a look tomorrow. Maybe there's something in the sniff I need to modify to take the static methods into account.

dingo-d avatar Sep 15 '23 16:09 dingo-d

Please no pressure, this can wait, there is no rush. There are more important things.

If I can help with testing to relieve some pressure. I'm glad to. I know how busy you guys are.

janw-me avatar Sep 15 '23 18:09 janw-me

I think I found the issue and will try to see how to solve it in my PR, I just need to get feedback from the rest of the maintainers on the best way forward (I have a quick solution in mind, just need to see if this is an ok approach).

dingo-d avatar Sep 16 '23 11:09 dingo-d

@janw-me I've updated the branch with some modifications, can you test it?

You should be able to pass the static methods with their respective classes now in the customEscapingFunctions property.

For instance, putting \QualifiedExample\Namespaced\ClassName::statMethod won't flag it as unsafe.

dingo-d avatar Sep 16 '23 14:09 dingo-d

@dingo-d Yes, great this works :tada: I've already updated the example in the wiki, I was a bit surprised I was just allowed to directly save and edit it...

janw-me avatar Sep 17 '23 10:09 janw-me

@janw-me please wait on updating the wiki, as the PR is not merged yet.

dingo-d avatar Sep 17 '23 13:09 dingo-d

@dingo-d The fix for static customEscapingFunctions is not in 3.0.1, it's still unreleased, right?

Luc45 avatar Jan 22 '24 15:01 Luc45

@Luc45 nope, the PR is still open, waiting for a review.

dingo-d avatar Jan 22 '24 15:01 dingo-d