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

VIP Request Block: Add function to allow for blocking by partial User Agent string. #4001

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 19 additions & 4 deletions lib/class-vip-request-block.php
Expand Up @@ -50,15 +50,15 @@ public static function ip( string $value ) {
$hdr = strtolower( $_SERVER['HTTP_TRUE_CLIENT_IP'] );
$bin = inet_pton( $hdr );
if ( $bin === $ip || $hdr === $value ) {
return self::block_and_log( $value, 'true-client-ip' );
return static::block_and_log( $value, 'true-client-ip' );
}
}

if ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
$ips = array_map( fn ( string $s ): string => strtolower( trim( $s ) ), explode( ',', $_SERVER['HTTP_X_FORWARDED_FOR'] ) );
$bin = array_map( 'inet_pton', $ips );
if ( in_array( $value, $ips, true ) || in_array( $ip, $bin, true ) ) {
return self::block_and_log( $value, 'x-forwarded-for' );
return static::block_and_log( $value, 'x-forwarded-for' );
}
}

Expand All @@ -75,7 +75,22 @@ public static function ip( string $value ) {
public static function ua( string $user_agent ) {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPressVIPMinimum.Variables.RestrictedVariables.cache_constraints___SERVER__HTTP_USER_AGENT__
if ( isset( $_SERVER['HTTP_USER_AGENT'] ) && $user_agent === $_SERVER['HTTP_USER_AGENT'] ) {
return self::block_and_log( $user_agent, 'user-agent' );
return static::block_and_log( $user_agent, 'user-agent' );
}

return false;
}

/**
* Block by partial match of the user agent header
*
* @param string $user_agent_substring target user agent to be blocked.
* @return void|bool
*/
public static function ua_partial_match( string $user_agent_substring ) {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated, WordPressVIPMinimum.Variables.RestrictedVariables.cache_constraints___SERVER__HTTP_USER_AGENT__, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
if ( isset( $_SERVER['HTTP_USER_AGENT'] ) && false !== strpos( $_SERVER['HTTP_USER_AGENT'], $user_agent_substring ) ) {
return static::block_and_log( $user_agent_substring, 'user-agent' );
}

return false;
Expand All @@ -95,7 +110,7 @@ public static function header( string $header, string $value ) {
$key = sprintf( 'HTTP_%s', str_ireplace( 'HTTP_', '', $key ) );

if ( isset( $_SERVER[ $key ] ) && $value === $_SERVER[ $key ] ) {
return self::block_and_log( $value, $header );
return static::block_and_log( $value, $header );
}

return false;
Expand Down
15 changes: 14 additions & 1 deletion tests/lib/test-vip-request-block.php
Expand Up @@ -11,7 +11,8 @@ class VIP_Request_Block_Test extends WP_UnitTestCase {
*/

public function tearDown(): void {
unset( $_SERVER['HTTP_TRUE_CLIENT_IP'], $_SERVER['HTTP_X_FORWARDED_FOR'] );
// phpcs:ignore WordPressVIPMinimum.Variables.RestrictedVariables.cache_constraints___SERVER__HTTP_USER_AGENT__
unset( $_SERVER['HTTP_TRUE_CLIENT_IP'], $_SERVER['HTTP_X_FORWARDED_FOR'], $_SERVER['HTTP_USER_AGENT'] );
parent::tearDown();
}

Expand Down Expand Up @@ -78,4 +79,16 @@ public function data_ipv6_corner_cases(): iterable {
[ 'HTTP_X_FORWARDED_FOR', '2001:4860:4860:0:0:0:0:8844', '2001:4860:4860:0000:0000:0000:0000:8844' ],
];
}

public function test_ua_partial_match() {
// Test that a partial match of the user agent string blocks bad site.
$_SERVER['HTTP_USER_AGENT'] = 'WordPress/6.1.1; https://www.BadSite.com'; // phpcs:ignore WordPressVIPMinimum.Variables.RestrictedVariables.cache_constraints___SERVER__HTTP_USER_AGENT__
$actual = VIP_Request_Block::ua_partial_match( 'https://www.BadSite.com' );
self::assertTrue( $actual, 'Expected request to be blocked based on partial User Agent string match.' );

// Test that allowed user agent string is not blocked.
$_SERVER['HTTP_USER_AGENT'] = 'WordPress/6.1.1; https://www.example.com'; // phpcs:ignore WordPressVIPMinimum.Variables.RestrictedVariables.cache_constraints___SERVER__HTTP_USER_AGENT__
$actual = VIP_Request_Block::ua_partial_match( 'https://www.BadSite.com' );
self::assertFalse( $actual, 'Expected request to be allowed.' );
}
}