Security researcher Rafie Muhammad discovered a high-severity reflected XSS vulnerability on ‘Advanced Custom Fields’ and ‘Advanced Custom Fields Pro’ WordPress plugins, with over two million installed.

This plugin allows us to add extra content fields to our WordPress edit screens. These extra content fields are more commonly referred to as Custom Fields and can allow us to build websites faster with more available fields.

The security vulnerability in Advanced Custom Fields

This plugin suffers from reflected XSS vulnerability. This vulnerability allows any unauthenticated user from stealing sensitive information to, in this case, privilege escalation on the WordPress site by tricking privileged users to visit the crafted URL path.

The underlying vulnerability is located on admin_body_class function handler :

includes/admin/admin-internal-post-type-list.php
public function admin_body_class( $classes ) {
    $classes .= " acf-admin-page acf-internal-post-type {$this->admin_body_class}";

    if ( $this->view ) {
        $classes .= " view-{$this->view}";
    }

    return $classes;
}

The admin_body_class configured to be an extra handler of WordPress’s own hook that is also named admin_body_class. This hook controls and filters the CSS classes for the main body tag in the admin area.

includes/admin/admin-internal-post-type-list.php
// Add hooks.
add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
add_action( 'admin_body_class', array( $this, 'admin_body_class' ) );

Further looking deeper at the implementation of the admin_body_class on WordPress core, we could see that the outputted value of the hook is not properly sanitized and directly constructed on the HTML page :

https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-admin/admin-header.php#L245
$admin_body_classes = apply_filters( 'admin_body_class', '' );
$admin_body_classes = ltrim( $admin_body_classes . ' ' . $admin_body_class );
?>
<body class="wp-admin wp-core-ui no-js <?php echo $admin_body_classes; ?>">
<script type="text/javascript">
	document.body.className = document.body.className.replace('no-js','js');
</script>

Because of that condition, there is a potential XSS vulnerability if the admin_body_class hook function handler doesn’t properly sanitize the returned class string.

Let’s get back to the hook function handler, the code will directly concatenate $this->view variable to the $classes variable that will be returned as a class string. We actually could fully control the $this->view variable from current_screen function :

includes/admin/admin-internal-post-type-list.php
public function current_screen() {
    // Bail early if not the list admin page.
    if ( ! acf_is_screen( "edit-{$this->post_type}" ) ) {
        return;
    }

    // Get the current view.
    $this->view = isset( $_GET['post_status'] ) ? sanitize_text_field( $_GET['post_status'] ) : ''; //
-------------------------------------

Sanitization using sanitize_text_field function is not enough to prevent XSS, since we could still use a DOM XSS payload. Combining all of that, the XSS could be achieved using this example payload :

http://<WORDPRESS_SITE>/wp-admin/edit.php?post_type=acf-field-group&post_status=xxxxxxx" onload=alert(document.domain) xxx="

The reflected HTML structure that is being shown on the front end could be something like this :

<body class="wp-admin wp-core-ui no-js acf-admin-5-3 acf-browser-chrome acf-admin-page acf-internal-post-type acf-admin-field-groups view-xxxxxxx\" onload=alert(document.domain) xxx=\" edit-php auto-fold admin-bar post-type-acf-field-group branch-6-2 version-6-2 admin-color-fresh locale-en-us no-customize-support no-svg">

Note that this vulnerability could be triggered on a default installation or configuration of the Advanced Custom Fields plugin. The XSS also could only be triggered by logged-in users that have access to the Advanced Custom Fields plugin.

The patch in Advanced Custom Fields

Since this issue is mainly because the code directly constructed a variable to the HTML without proper sanitization, implementing esc_attr a function should be enough to patch the issue. The patch can be found below :

Conclusion

There is many WordPress hook function that used across plugin and theme codes. One of them is admin_body_class hook. Make sure to sanitize the classes string that outputted when using the hook using sanitize function like esc_attr.