99 lines
3.1 KiB
PHP
99 lines
3.1 KiB
PHP
<?php
|
|||
|
|
/**
|
||
|
|
* Verify that every shipped PHP file really guards against direct access.
|
||
|
|
*
|
||
|
|
* 🔴 Tokenised, not grepped, and that is the whole point of the file.
|
||
|
|
*
|
||
|
|
* The grep version asked whether "defined( 'ABSPATH' )" appeared ANYWHERE in
|
||
|
|
* the file. A comment saying the guard is handled elsewhere therefore passed,
|
||
|
|
* and so did a file with the words and no guard at all. Measured: a planted
|
||
|
|
* file whose only mention was in a comment produced
|
||
|
|
* "15 shipped PHP files examined, every one guarded" — green over a file that
|
||
|
|
* runs for anyone who fetches it directly, outside WordPress and outside every
|
||
|
|
* capability check the code assumes has already happened.
|
||
|
|
*
|
||
|
|
* A check that must RECOGNISE its own code has one blind spot per recognition
|
||
|
|
* method; one that reads what the source actually says has none of that
|
||
|
|
* particular kind. PHP's own tokeniser cannot be fooled by a comment, because
|
||
|
|
* it is the thing that decides what a comment is.
|
||
|
|
*/
|
||
|
|
|
||
|
|
$min = 0;
|
||
|
|
$files = array();
|
||
|
|
foreach ( array_slice( $argv, 1 ) as $arg ) {
|
||
|
|
if ( 0 === strpos( $arg, '--min=' ) ) {
|
||
|
|
$min = (int) substr( $arg, 6 );
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
$files[] = $arg;
|
||
|
|
}
|
||
|
|
|
||
|
|
$guards = array( 'ABSPATH', 'WP_UNINSTALL_PLUGIN' );
|
||
|
|
$problems = 0;
|
||
|
|
$checked = 0;
|
||
|
|
|
||
|
|
foreach ( $files as $path ) {
|
||
|
|
if ( ! is_readable( $path ) ) {
|
||
|
|
printf( "::error file=%s::Listed as shipped but not readable.\n", $path );
|
||
|
|
++$problems;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
++$checked;
|
||
|
|
$tokens = token_get_all( file_get_contents( $path ) );
|
||
|
|
$count = count( $tokens );
|
||
|
|
$found = false;
|
||
|
|
|
||
|
|
for ( $i = 0; $i < $count; $i++ ) {
|
||
|
|
$token = $tokens[ $i ];
|
||
|
|
|
||
|
|
// Comments and doc blocks are tokens of their own, so they are simply
|
||
|
|
// never examined. That is the property the grep version lacked.
|
||
|
|
if ( ! is_array( $token ) || T_STRING !== $token[0] || 'defined' !== strtolower( $token[1] ) ) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// defined ( 'ABSPATH' ) — whitespace anywhere, quotes either kind.
|
||
|
|
$j = $i + 1;
|
||
|
|
while ( $j < $count && is_array( $tokens[ $j ] ) && T_WHITESPACE === $tokens[ $j ][0] ) {
|
||
|
|
++$j;
|
||
|
|
}
|
||
|
|
if ( $j >= $count || '(' !== $tokens[ $j ] ) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
++$j;
|
||
|
|
while ( $j < $count && is_array( $tokens[ $j ] ) && T_WHITESPACE === $tokens[ $j ][0] ) {
|
||
|
|
++$j;
|
||
|
|
}
|
||
|
|
if ( $j < $count && is_array( $tokens[ $j ] ) && T_CONSTANT_ENCAPSED_STRING === $tokens[ $j ][0] ) {
|
||
|
|
if ( in_array( trim( $tokens[ $j ][1], "'\"" ), $guards, true ) ) {
|
||
|
|
$found = true;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( ! $found ) {
|
||
|
|
printf(
|
||
|
|
"::error file=%s,line=1::No guard against direct access. Add: if ( ! defined( 'ABSPATH' ) ) { exit; }\n",
|
||
|
|
$path
|
||
|
|
);
|
||
|
|
++$problems;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// The floor, for the same reason every other check in this kit has one: "every
|
||
|
|
// shipped file is guarded" is also what zero shipped files prints.
|
||
|
|
if ( $checked < $min ) {
|
||
|
|
fwrite( STDERR, sprintf( "Only %d shipped PHP file(s) were examined, expected at least %d. The file list is wrong, so this check proved nothing.\n", $checked, $min ) );
|
||
|
|
exit( 1 );
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $problems > 0 ) {
|
||
|
|
printf( "\n%d of %d shipped PHP files have no guard against direct access.\n", $problems, $checked );
|
||
|
|
exit( 1 );
|
||
|
|
}
|
||
|
|
|
||
|
|
printf( " %d shipped PHP files parsed, every one guards against direct access\n", $checked );
|
||
|
|
exit( 0 );
|