SoftwareSecurity2014/Group 8/Level 2B Code Evaluation

Uit Werkplaats
Ga naar: navigatie, zoeken

Inhoud

wp-db.php

wp-db.php is the only file that handles a connection to the database and the execution of SQL queries. This means it does not handle user input directly, but all user input that arrives here should be checked before it is sent to the database. The execution of SQL queries are executed via several get functions and an insert, an update and a delete function. This happens in a consistent way for the insert, update and delete functions. These functions first prepare the SQL queries using the prepare()-function, after which the query()-function is used to execute them. The get functions (get_var(), get_row(), get_col(), get_results()) pass the SQL queries directly to the query() function.

V5.1 Verify that the runtime environment is not susceptible to buffer overflows, or that security controls prevent buffer overflows.

As PHP is a higher level language we expect the runtime environment to be responsible for protecting against buffer overflows.

V5.2 Verify that a positive validation pattern is defined and applied to all input.

Some simple functions like set_prefix() do this. The main functions however, like insert(), update(), delete(), are not checked using a positive validation pattern in wp-db.php, so this should happen before calling these functions. Every query passed into the query function should first be prepared using the prepare() function. This does happen in the functions that call query() in wp-db. This prepare() function can be called a form of positive validation, because only parameters of the specified types can be placed in the statement. This however does not positively verify strings. For strings the mysql_real_escape() is used inside the prepare() function, but this is negative input validation. Queries passed into the get functions are not checked in wp-db.php itself, so these are supposed to be checked in the calling methods.

Searching through all files there are 228 calls to those get functions. In 131 of those calls, the argument is the direct result of the prepare statement, like in the following example (post.php, line 4081):

if ( ! $wpdb->get_var( $wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE ID = %d", $import_id) ) ) {

The remaining 97 occurrences either do not handle user input, are validated before by the wp-db prepare() function or are validated in an alternative way. One other way statements are validated is via the functions esc_sql() and like_escape(). An example of this is in bookmark.php on line 207:

$search = esc_sql( like_escape( $search ) );

In user.php the user input is passed to the get_posts_by_author_sql() function, which produces queries by also calling the wp-db prepare() function.

Finally, many filters are used for validating user input, mostly when comments are involved.

V5.3 Verify that all input validation failures result in input rejection or input sanitization.

Input is sanitized by the prepare function in wp-db (using escaping).

V5.4 Verify that a character set, such as UTF-8, is specified for all sources of input.

The charset can be defined in wp-db, but the further checking whether all variables follow this charset should have happened before calling the functions in wp-db.

V5.5 Verify that all input validation is performed on the server side.

wp-db does not provide any client side code, so everything happens at server side in this class

V5.6 Verify that a single input validation control is used by the application for each type of data that is accepted.

All accepted queries should go through the prepare function.

V5.7 Verify that all input validation failures are logged.

Errors are only logged by wp-db in case the execution of a query leads to a failure.

wp-login.php

This Wordpress page handles a lot of user input. It is responsible for registering and authenticating users, handling forgotten passwords and resetting passwords and more. That is why wp-login.php is an important page to review regarding input validation.

V5.1 Verify that the runtime environment is not susceptible to buffer overflows, or that security controls prevent buffer overflows.

Since Wordpress is written in php, we expect the runtime environment to protect against buffer overflows.

V5.2 Verify that a positive validation pattern is defined and applied to all input.

In wp-login.php, a lot of user input is handled. Most of these occur in $_POST and $_GET variables. Below is an analysis of the most interesting occurrences of user input, which may be sent to the database, for example.

1. First we look at the function retrieve_password(). This function is responsible for retrieving a password when a user has forgotten it. It gets the user's login and checks whether it is not empty. If it's not, it checks whether it's a valid email address (whether the string contains an @ symbol), in which case the string is trimmed (whitespaces, tabs, etc are removed). This trimmed string is then passed along to the function get_user_by(), so the the corresponding user to this email address is fetched from the database. When the string is not an email address, it is assumed that it's a user name and the corresponding user is then fetched from the database with get_user_by(). The function get_user_by() is located in another file, wp-includes/pluggable.php, which in turn redirects to get_data_by() which is located in wp-includes/capabilities.php. In get_data_by(), the user is retrieved from the database according to the email address or user name. A prepared statement is used for this. The function retrieve_password() does not necessarily seem to apply a positive validation pattern to the user input, which contains the user's login. It is checked whether the string contains an @ character and prepared statements are used, but no further input validation is applied here. The user's login is sanitized in the database query once it is used in the get_data_by call().

In the main part of wp-login.php we find a lot of $_POST variables. These are often not validated, as can be seen in the next two code fragments. However, positive input validation may not really be necessary in these examples.

2. The post password is set in the user's cookie; this password is unslashed before being hashed. No input validation is performed.

setcookie( 'wp-postpass_' . COOKIEHASH, $hasher->HashPassword( wp_unslash( $_POST['post_password'] ) ), $expire, COOKIEPATH );


3. Two passwords are checked against each other. No input validation is performed, but it is also not really necessary because they are only compared to each other.

if ( isset($_POST['pass1']) && $_POST['pass1'] != $_POST['pass2'] )
		$errors->add( 'password_reset_mismatch', __( 'The passwords do not match.' ) );

More interesting things happen when a user tries to reset his/her password:

4. When a user tries to reset their password, it is checked whether it is set and non-empty, which is positive validation. Then it is passed along to the reset_password() function.

if ( ( ! $errors->get_error_code() ) && isset( $_POST['pass1'] ) && !empty( $_POST['pass1'] ) ) {
		reset_password($user, $_POST['pass1']);
		login_header( __( 'Password Reset' ), '<p class="message reset-pass">' . __( 'Your password has been reset.' ) . ' <a href="' . esc_url( wp_login_url() ) . '">' . __( 'Log in' ) . '</a></p>' );
		login_footer();
		exit;
	}

The reset_password() function contains the following code (and is located in includes/user.php):

function reset_password( $user, $new_pass ) {
	do_action( 'password_reset', $user, $new_pass );

	wp_set_password( $new_pass, $user->ID );
	update_user_option( $user->ID, 'default_password_nag', false, true );

	wp_password_change_notification( $user );
}

This function is the only occurrence of the hook password_reset, so nothing happens to the $new_pass variable in the do_action(). (Of course, if someone was to add a function to this hook, then the $new_pass variable would be passed along unsanitized and input validation would have to be performed there. ) The wp_set_password() call also passes along $new_pass. This function is located in includes/pluggable.php and contains the following code:

$hash = wp_hash_password( $password );
	$wpdb->update($wpdb->users, array('user_pass' => $hash, 'user_activation_key' => ''), array('ID' => $user_id) );

The function update() applies a positive validation pattern to the input, refer to wp-db.php for this. This is where the trace of the user input for the password ends. So in conclusion, a positive validation pattern is applied for this user input.

5.

$user_login = $_POST['user_login'];
		$user_email = $_POST['user_email'];
		$errors = register_new_user($user_login, $user_email); 

The user email and user login are sanitized in the function register_new_users() (located in includes/user.php) which contains the following code:

 $sanitized_user_login = sanitize_user( $user_login );
	$user_email = apply_filters( 'user_registration_email', $user_email ); 

The sanitize_user() function sanitizes the user login, it strips unsafe characters such as tags, octets, entities. Also, the sanitized user login is then checked to be non-empty; the function validate_username() is applied to the user login to make sure that no illegal characters are used and username_exists( $sanitized_user_login) is applied to check whether the user name already exists. So here, a positive validation pattern is applied, but is mixed with sanitizing. The email address is also checked to be non-empty. More importantly, the functions is_email( $user_email ) and email_exists( $user_email ) are applied to check whether the email address is a valid email and whether it already exists. is_email is one of the clearest functions that apply positive input validation. We conclude that the user login and user email are both validated in a positive manner.

6.

 $user = check_password_reset_key($_GET['key'], $_GET['login']); 

The function check_password_reset_key() sanitizes the key and login input, which is shown by some selected lines of code below:

1. $key = preg_replace('/[^a-z0-9]/i', '', $key); 
2. if ( empty( $key ) || !is_string( $key ) )
3. if ( empty($login) || !is_string($login) )
4. $row = $wpdb->get_row( $wpdb->prepare( "SELECT ID, user_activation_key FROM $wpdb->users WHERE user_login = %s", $login ) );

The key is filtered for certain characters. Also, in lines 2 and 3, it is checked that the key as well as the login are non-empty and are strings, so at these places there is a positive input validation for the key as well as the login. Once these checks have been performed, a database query is executed using a prepared statement (another form of positive input validation).

7.

 action="<?php echo esc_url( site_url( 'wp-login.php?action=resetpass&key=' . urlencode( $_GET['key'] ) . '&login=' . urlencode( $_GET['login'] ), 'login_post' ) ); ?>" 

In this case, a positive input validation pattern is not applied, the user's key is encoded in the url, it is not checked whether it is really a key or at least whether it is a string, as in example 6.

As we see, a positive validation input is not applied as a rule to all user input. Sometimes it is applied, such as in the case of an email address, where the is_email() function is used, but wp-login.php fails to validate user input consistently.

V5.3 Verify that all input validation failures result in input rejection or input sanitization.

As far as we could tell, at every point where input validation fails, that input is either rejected or sanitized. Lots of functions that use user input return an error when the input validation fails. For example:

$user = check_password_reset_key($_GET['key'], $_GET['login']);

	if ( is_wp_error($user) ) {
		if ( $user->get_error_code() === 'expired_key' )
			wp_redirect( site_url( 'wp-login.php?action=lostpassword&error=expiredkey' ) );
		else
			wp_redirect( site_url( 'wp-login.php?action=lostpassword&error=invalidkey' ) );
		exit;
	}

This check_password_reset_key() function was already mentioned in the previous requirement. Here we see what happens when input validation in this function fails. If an error has occurred, checks are performed to see whether the error code was an expired key or something else. The input is then rejected, the user is redirected to another site.

In the following examplethe same thing happens;

$user_login = $_POST['user_login'];
		$user_email = $_POST['user_email'];
		$errors = register_new_user($user_login, $user_email);
		if ( !is_wp_error($errors) ) {
			$redirect_to = !empty( $_POST['redirect_to'] ) ? $_POST['redirect_to'] : 'wp-login.php?checkemail=registered';
			wp_safe_redirect( $redirect_to );
			exit();
		}

Overall, the user input is more likely to be rejected than sanitized on failed input validation in wp-login.php.

V5.4 Verify that a character set, such as UTF-8, is specified for all sources of input.

No character set is specified for all sources of input in wp-login.php.

V5.5 Verify that all input validation is performed on the server side.

All code in wp-login.php is executed on the server, so input validation, if any, is indeed executed on the server.

V5.6 Verify that a single input validation control is used by the application for each type of data that is accepted.

The user login is always sanitized when it is passed along to register_new_user(), which uses sanitize_user() on this data. This function is also called from wp-login.php sometimes, e.g.: sanitize_user($_POST['log']). The user email is also passed along to register_new_user(). Here, a filter is applied to the user email, but this is not a default filter, so no functions are specified yet. The user email is checked to be a legitimate email with the is_email() function, this control is always used for e-mail adresses within wp-login.php. The variable $pass1, which is a new password that is provided by a user, is not necessarily validated to be safe, but the update function on the database is executed safely. The log is sometimes sanitized with sanitize_user(), as shown above, and sometimes sanitized by unslashing the string and escaping certain characters before assigning it to the $user_name variable. The key variable is sanitized by a pregreplace and is checked to be non-empty and a string.

There isn't a single input validation control used for eacht type of data that is accepted. This is partly because some types of user input are not validated, but directly sanitized. For the user input that is validated, however, there seems to be a single input validation control per data type. Most common are the database functions and the is_email function.

V5.7 Verify that all input validation failures are logged.

Input validation failures are not logged. When input validation fails, the user input is mostly rejected and the user may get redirected to another page that tells them there was some illegal input.

xmlrpc.php

We will first introduce what the xmlrpc.php file is used for in Wordpress to get a better understanding of what should actually be checked. xmlrpc.php is used for remotely calling functions (remote procedure calls) on a Wordpress installation. It's like an API to your Wordpress site. You can use certain client software other than your ordinary web browser to create new posts, get all posts, or get all posts from a certain writer. This list is far from complete; class-wp-xmlrpc-server.php contains a full list of all available functions. Procedures can be called by constructing XML formatted messages that look like the following:

<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
   <methodName>wp.getPosts</methodName>
   <params>
       <param>
            <value><int>1</int></value>
       </param>
       <param>
	    <value><string>username</string></value>
       </param>
       <param>
	    <value><string>password</string></value>
       </param>
   </params>
</methodCall>

The above XML-RPC message asks for all the posts for the blog with id 1. Authentication is done by providing a username and password as the second and third argument. In XMLRPC, function call parameters are derived from their position and cannot be named.

xmlrpc.php is only a small wrapper. The main functionality is in class-wp-xmlrpc-server.php and class-IXR.php. As describe before, class-wp-xmlrpc-server.php contains a list of all the functions that can be called remotely. It does not only list these, but the procedures are also defined in this file.

Wordpress does some checking if the request is actually a POST; if not, execution is broken, and the user is presented with (code is in class-IXR.php):

die('XML-RPC server accepts POST requests only.');

The actual function call, if a POST request is made, is read by Wordpress like this:

global $HTTP_RAW_POST_DATA;
if (empty($HTTP_RAW_POST_DATA)) {
   // workaround for a bug in PHP 5.2.2 - http://bugs.php.net/bug.php?id=41293
   $data = file_get_contents('php://input');
} else {
   $data =& $HTTP_RAW_POST_DATA;
}

The $data is then used to create an IXR_Message, which is then parsed. This may return an error if the message is not well-formed or if the message is not a method call. If parsing went OK, the actual method is called with its parameters, which have, by then, been parsed. Of course the result of the method call will also be constructed. This is again a message formatted in XML. Such a result may contain all blog posts, for example.

V5.1 Verify that the runtime environment is not susceptible to buffer overflows, or that security controls prevent buffer overflows.

xmlrpc.php is executed as a PHP script on a web server. This web server is out of scope for our current assessment, so this requirement will not be verified at the moment.

V5.2 Verify that a positive validation pattern is defined and applied to all input.

xmlrpc.php does not apply a positive validation pattern to all inputs. Input is checked in a negative way, instead. Below is an explanation of how Wordpress accomplishes this. The below explanation also serves as an explanation to the security requirements V5.3 and V5.5

Input for the xmlrpc.php file will usually be in XML formatted messages. If the message is not properly formatted, which is checked by the class-IXR.php function parse(), the user will be presented with an error message. When the message is actually well-formatted XML, but is not a method call, the execution will also fail, and the user will again be presented by an error in an XML result message.

If parsing went correct, then the message now has a method name and parameters. The corresponding function is then executed by the function call(). The call() function is passed the $methodName and $args parameters, the first of which is used to find out if the method is actually defined. If it's not defined, execution is aborted and the user is again presented with an error in XML. When the method is defined, execution of the function is passed via call_user_func() with parameters $method and $args. The latter may still contain user tainted input, because this has not been validated or sanitized yet.

So, in the end, when everything went OK, the function that was defined in the XML message at the beginning will be executed together with its arguments. The checking of the argument(s) type(s) is actually up to the method that is called, which are defined in class-wp-xmlrpc-server.php. Some functions don't use arguments, so they don't have to validate the data (sayHello($args) and addTwoNumbers($args); these are usually used to test the connection). For functions that actually do use their arguments, in most cases the number of parameters passed is checked. When the minimum amount of parameters is not met, there will be an error message. After this check the next step is to call $this->escape($args), for which the implementation is shown below. It takes care of adding slashes in locations of characters that need escaping; it uses PHP's addslashes ( string $str ) to do this.

function escape( &$data ) {
   if ( ! is_array( $data ) )
	return wp_slash( $data );

   foreach ( $data as &$v ) {
	if ( is_array( $v ) )
		$this->escape( $v );
	elseif ( ! is_object( $v ) )
		$v = wp_slash( $v );
   }
}

Every single method that uses the arguments passed to it uses the same pattern, except for wp_newPage() and wp_editPage. However, in the comments it is pointed out that escaping the $args that are not escaped initially will be passed in another function, which are wp_newPost() and wp_newPost(). Looking at those function declaration this indeed seems to be the case.

After adding slashes, the actual parameters have to be checked. I've checked the types of these; these are all integer, strings or arrays.

$post_ID  = intval($args); //does not correspond with the $args below

$blog_id  = (int) $args[0];
$username = $args[1];
$password = $args[2];
$filter   = isset( $args[3] ) ? $args[3] : array();  //optional arguments

We see that integers are cast via (int) or intval(), which is in the guidelines for Data Validation in Wordpress. $username and $password are pushed through the login(), which passes them on to wp_authenticate(), which is defined in pluggable.php:

function wp_authenticate($username, $password) {
   $username = sanitize_user($username);
   $password = trim($password);
   ...

So, also these parameters are sanitized and validated before they are used. We can conclude that xmlrpc.php seems to indeed apply a positive input validation on every input.

V5.3 Verify that all input validation failures result in input rejection or input sanitization.

As far as I could check by just looking over the code manually, I found that all input validations that resulted in failures will actually result in a rejection of the input. This has also been explained to some length in 5.2 already. In most cases the user calling the remote function will be presented by an XML formatted message containing at least some information about why the function call did not work. No information that was present in the procedure call is reflected to the user, as far as I can tell.

V5.4 Verify that a character set, such as UTF-8, is specified for all sources of input.

No character set is specified for all sources of input in xmlrpc.php, as the input comes from file_get_contents('php://input') or $HTTP_RAW_POST_DATA. The result messages sent by the XML-RPC server are all in the UTF-8 (standard, but this can be changed on a blog wide basis) format however. In a sense, Wordpress fails on this verification requirement, but the presence of the parse() function, which was already explained in some detail before, actually takes care of the correct formatting of a message. If it cannot be read, which might be due to some illegal character in the XML message, the message will actually not be parsed successfully, and as a consequence the user will be presented with an error message.

V5.5 Verify that all input validation is performed on the server side.

The xmlrpc.php file is of course executed on the server. All input validation that is performed is thus actually performed on the server, which is the safest method to do this. I did not find traces of Ajax requests being made to xmlrpc.php, so I think it is reasonable to say that there's no input validation going on in Javascript for this part of the application (which would of course hinder most of the functionality of an XML-RPC server).

V5.6 Verify that a single input validation control is used by the application for each type of data that is accepted.

The entire set of $args is passed through escape() every time a remote procedure is called. Integers are checked via (int) i or intval(i) every time they are used. $username is always passed to wp_authenticate() which will call sanitize_user() on $username. The same holds for $password, which is thrown in trim() by the wp_authenticate() function. There may be other data left in the $args, but the fact that these functions have to be set up in a very generic way has as a consequence that most of these cannot be checked deeper. At least, $args is always properly escaped as far as I can tell.

V5.7 Verify that all input validation failures are logged.

Input validation failures don't seem to be logged. Users will receive an error message in XML format when their request is illegal in some way, however. Digging through many levels of the code, I didn't find any functions that would actually write errors in the input validation to some file (such as sanitize_user()). In xmlrpc.php a function called logIO() is available, but it is denoted deprecated as of Wordpress version 3.4.0. This function passed on an error message to error_log().

wp-signup.php

This part of Wordpress handles the registration of new sites and blogs. Since it has user names, email addresses, and blog titles as input variables, we are highlighting this file in our validation of the ASVS security requirement V5 - Input Validation.

V5.1 Verify that the runtime environment is not susceptible to buffer overflows, or that security controls prevent buffer overflows.

Since Wordpress is written in PHP, it can be assumed that the lower level runtime environment will take care of buffer overflows.

V5.2 Verify that a positive validation pattern is defined and applied to all input.

It is also known as 'accept known good': accept all input that is within a set of constraints. Wordpress uses a combination of blacklisting and whitelisting. An example of whitelisting is the input of email addresses. Email addresses are only accepted if they are given in a certain format. An example of blacklisting used by Wordpress is the rejection of underscores (_) in blognames. The procedure wpmu_validate_blog_signup() is used to validate the inserted blog name and blog title. The blog name is validated for the following criteria:

  • Not already in use
  • At least 4 characters long
  • Not empty
  • Lowercase
  • Alphanumeric
    • Not numeric only
    • Does not contain underscores (_)
  • No illegal names like www, web, admin, root, main, etc. (Other illegal names can be added to the list by the site admin)

On the contrary, the blog title is only checked to be non-empty. With a similar function the user name is checked for the same criteria as the blog name. The email address validation checks for three criteria:

  • Not already in use
  • Valid email address (x@x.xx)
  • Is not listed as unsafe (by using is_email_address_unsafe())

The user input parameters that are required by this part of Wordpress are a blog name, blog title, user name and email address. All these input fields seem to be properly validated.

V5.3 Verify that all input validation failures result in input rejection or input sanitization.

Input validation failures will most of the time result in input rejection. The user is told that his input is not allowed. A new blog name appears to be sanitized before Wordpress continues using it.

V5.4 Verify that a character set, such as UTF-8, is specified for all sources of input.

We suppose that this is the case, but it is not specified in this specific file.

V5.5 Verify that all input validation is performed on the server side.

The wp-signup.php file is ran at the server side. The input validation for the sign-up functionality of Wordpress is therefore also performed at the server side.

V5.6 Verify that a single input validation control is used by the application for each type of data that is accepted.

For blog names and blog titles the function wpmu_validate_blog_signup() is used, for user names and email addresses the function wpmu_validate_user_signup() is used. These functions can be found in the file ms-functions.php. Both functions contain SQL queries, but all of them are properly escaped by using the wpdb->prepare() function. In an earlier stage it was concluded that input via this function is considered safe.

V5.7 Verify that all input validation failures are logged.

Input validation failures do not appear to be logged. Almost all functions catch errors that could occur by using the function. This results sometimes in the ending of function execution, sometimes in an error message being given to the user. Errors are registered in a kind of error array, but this is not meant to log input validation failures. So input validation failures are not logged.

wp-comments-post.php

The comment system uses wp-comment-post.php to post comments. In this file the input is read and trimmed but not validated.

$comment_author       = ( isset($_POST['author']) )  ? trim(strip_tags($_POST['author'])) : null;
$comment_author_email = ( isset($_POST['email']) )   ? trim($_POST['email']) : null;
$comment_author_url   = ( isset($_POST['url']) )     ? trim($_POST['url']) : null;
$comment_content      = ( isset($_POST['comment']) ) ? trim($_POST['comment']) : null;

The saving and update functions are implemented in comment.php. For both updating and saving, the function wp_filter_comment() is used to filter the input. This function uses the Wordpress hook system to filter the different inputs. Those hooks are added in default-filters.php().

foreach ( array( 'pre_term_name', 'pre_comment_author_name', 'pre_link_name', 'pre_link_target', 'pre_link_rel', 'pre_user_display_name', 'pre_user_first_name', 'pre_user_last_name', 'pre_user_nickname' ) as $filter ) {
	add_filter( $filter, 'sanitize_text_field'  );
	add_filter( $filter, 'wp_filter_kses'       );
	add_filter( $filter, '_wp_specialchars', 30 );
}

// Save URL
foreach ( array( 'pre_comment_author_url', 'pre_user_url', 'pre_link_url', 'pre_link_image',
	'pre_link_rss', 'pre_post_guid' ) as $filter ) {
	add_filter( $filter, 'wp_strip_all_tags' );
	add_filter( $filter, 'esc_url_raw'       );
	add_filter( $filter, 'wp_filter_kses'    );
}

// Email saves
foreach ( array( 'pre_comment_author_email', 'pre_user_email' ) as $filter ) {
	add_filter( $filter, 'trim'           );
	add_filter( $filter, 'sanitize_email' );
	add_filter( $filter, 'wp_filter_kses' );
}

Name, URLs and mail are escaped by different functions as _wp_specialchars(), esc_url_raw() and sanitize_email(). The content is handled differently. The filters are added by kses.php. Depending on the rights of the user, the HTML is filtered or removed. This is based on a whitelist with allowed tags and attributes.

function kses_init_filters() {
	// Normal filtering
	add_filter('title_save_pre', 'wp_filter_kses');

	// Comment filtering
	if ( current_user_can( 'unfiltered_html' ) )
		add_filter( 'pre_comment_content', 'wp_filter_post_kses' );
	else
		add_filter( 'pre_comment_content', 'wp_filter_kses' );

	// Post filtering
	add_filter('content_save_pre', 'wp_filter_post_kses');
	add_filter('excerpt_save_pre', 'wp_filter_post_kses');
	add_filter('content_filtered_save_pre', 'wp_filter_post_kses');
}

V5.2 Verify that a positive validation pattern is defined and applied to all input.

The escape functions used by the hooks use a positive validation pattern.

V5.3 Verify that all input validation failures result in input rejection or input sanitization.

If input can be sanitized the input is sanitized. It's only rejected when input is not an email or URL.

V5.4 Verify that a character set, such as UTF-8, is specified for all sources of input.

Only sanitize_text_field() checks for invalid UTF-8 characters and is only used for the author name. For email and URL a whitelist is used. The comment itself uses encoded HTML entities for every character not in their whitelist. Invalid Unicode characters can't be encoded and are removed.

V5.5 Verify that all input validation is performed on the server side.

The code in wp-comments-post.php runs on the server so the input is validated on the server side.

V5.6 Verify that a single input validation control is used by the application for each type of data that is accepted.

Although they used a lot off different validation functions they use one function per data type.

V5.7 Verify that all input validation failures are logged.

Failures result in rejection or sanitation but never in logging.