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

XSS sniff: require escaping for die(), exit(), printf() #312

Merged
merged 4 commits into from
Feb 16, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion WordPress/Sniffs/XSS/EscapeOutputSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,8 @@ class WordPress_Sniffs_XSS_EscapeOutputSniff implements PHP_CodeSniffer_Sniff
'_ngettext',
'_nx',
'_x',
'printf',
'vprintf',
);

public static $addedCustomFunctions = false;
Expand All @@ -247,6 +249,7 @@ public function register()
return array(
T_ECHO,
T_PRINT,
T_EXIT,
T_STRING,
);

Expand Down Expand Up @@ -275,13 +278,25 @@ public function process( PHP_CodeSniffer_File $phpcsFile, $stackPtr )

$tokens = $phpcsFile->getTokens();

$needs_sanitizing_function = false;

$function = $tokens[ $stackPtr ]['content'];

// If function, not T_ECHO nor T_PRINT
if ( $tokens[$stackPtr]['code'] == T_STRING ) {
// Skip if it is a function but is not of the printing functions ( self::needSanitizingFunctions )
if ( ! in_array( $tokens[$stackPtr]['content'], $this->needSanitizingFunctions ) ) {
return;
}

$needs_sanitizing_function = true;

$stackPtr++; // Ignore the starting bracket

$end_of_statement = $tokens[ $stackPtr ]['parenthesis_closer'];
}

if ( $tokens[ $stackPtr ]['code'] === T_EXIT ) {
$stackPtr++; // Ignore the starting bracket
}

Expand Down Expand Up @@ -310,7 +325,10 @@ public function process( PHP_CodeSniffer_File $phpcsFile, $stackPtr )
}
}

$end_of_statement = $phpcsFile->findNext( T_SEMICOLON, $stackPtr );
// This is already determined if $needs_sanitizing_function.
if ( ! isset( $end_of_statement ) ) {
$end_of_statement = $phpcsFile->findNext( T_SEMICOLON, $stackPtr );
}

// Check for the ternary operator.
$ternary = $phpcsFile->findNext( T_INLINE_THEN, $stackPtr, $end_of_statement );
Expand All @@ -326,6 +344,11 @@ public function process( PHP_CodeSniffer_File $phpcsFile, $stackPtr )
if ( $tokens[$i]['code'] == T_WHITESPACE )
continue;

if ( 'vprintf' === $function && $tokens[ $i ]['code'] === T_ARRAY ) {
$i++; // Skip the opening parenthesis.
continue;
}

// Wake up on concatenation characters, another part to check
if ( in_array( $tokens[$i]['code'], array( T_STRING_CONCAT ) ) ) {
$watch = true;
Expand All @@ -338,6 +361,12 @@ public function process( PHP_CodeSniffer_File $phpcsFile, $stackPtr )
continue;
}

// Wake up for commas.
if ( $needs_sanitizing_function && $tokens[$i]['code'] === T_COMMA ) {
$watch = true;
continue;
}

if ( $watch === false )
continue;

Expand Down
24 changes: 23 additions & 1 deletion WordPress/Tests/XSS/EscapeOutputUnitTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,26 @@ echo $foo ? $foo : 'no foo'; // Bad
echo empty( $foo ) ? 'no foo' : $foo; // Bad
echo $foo ? esc_html( $foo ) : 'no foo'; // OK

echo 4; // OK
echo 4; // OK

exit( $foo ); // Bad
exit( esc_html( $foo ) ); // OK

die( $foo ); // Bad
die( esc_html( $foo ) ); // OK

printf( 'Hello %s', $foo ); // Bad
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JDGrimes I think printf here is a tricky one, and it is discussed in #212. We basically need recognize that what you have here is indeed bad, but whitelist the following as OK:

printf( 'Hello %s', esc_html( $foo ) ); // OK

That will require some sophistication, so I think this that printf and vprintf should be removed from the scope of this PR, unless you want to tackle #212 as well 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just pushed some improved tests in 39881e8 that demonstrate that this works for printf(). It also addresses vprintf(). sprintf() is still another story, which I/someone will tackle at a later date. :-)

printf( 'Hello %s', esc_html( $foo ) ); // OK
printf( 'Hello %s! Hi %s!', esc_html( $foo ), $bar ); // Bad

vprintf( 'Hello %s', array( $foo ) ); // Bad
vprintf( 'Hello %s', array( esc_html( $foo ) ) ); // OK

// The below checks that functions which are marked as needed further sanitization
// don't spill over into later arguments when nested in a function call. There was
// a bug which would cause line 84 to be marked as needing sanitization because _x()
// is marked as needing sanitization.
do_something(
_x( 'Some string', 'context', 'domain' )
, array( $foo ) // OK
);
5 changes: 5 additions & 0 deletions WordPress/Tests/XSS/EscapeOutputUnitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ public function getErrorList()
46 => 1,
59 => 1,
60 => 1,
65 => 1,
68 => 1,
71 => 1,
73 => 1,
75 => 1,
);

}//end getErrorList()
Expand Down