What Causes WordPress Plugins to Have Arbitrary File Upload Vulnerabilities and How They Can Be Avoided
When it comes to vulnerabilities in WordPress plugins, one of the most serious types is an arbitrary file upload vulnerability. That type of vulnerability would allow anyone to upload any type of file to the website. Hackers usually exploit that to upload .php files, as they can run arbitrary code on the website through that. That would allow them to add malware or spam to the website, allow them to send spam email or attack other websites, as well as other assorted activity.
To help to better understand what is going wrong, that leads to such a vulnerability and how those issues can be avoided, let’s break down a vulnerability of that type we spotted last month being introduced in to a plugin that comes directly from WordPress.
Unrestricted Access
While plugins sometimes have upload functionality intended to be accessed by even those not logged in to WordPress, many vulnerabilities of this type involve upload functionality that is only intended to be accessed by users with the Administrator role. In that case, if the code is run on a page that only Administrators can access, they are the only ones that can access the upload functionality. But if the code is run somewhere else, it might be accessible to even those not logged in to WordPress.
With this vulnerably that was the case, as the code was run when any admin page was accessed, which makes it accessible to even those not logged in to WordPress if they access the right page. There should have been a capabilities check in the code to restrict access, but there wasn’t. Instead, the only checks in place made sure certain data was being sent with the request:
156 157 158 159 160 161 162 | function save_local_fonts_to_theme () { if ( ! empty( $_FILES['font-file'] ) && ! empty( $_POST['font-name'] ) && ! empty( $_POST['font-style'] ) && ! empty( $_POST['font-weight'] ) ) { |
That was resolved by checking to make sure the request came from someone with the relevant capability, in this case, edit_themes, using the function current_user_can( ):
158 159 160 161 162 163 164 165 166 | function save_local_fonts_to_theme () { if ( current_user_can( 'edit_themes' ) && wp_verify_nonce( $_POST['nonce'], 'create_block_theme' ) && ! empty( $_FILES['font-file'] ) && ! empty( $_POST['font-name'] ) && ! empty( $_POST['font-style'] ) && ! empty( $_POST['font-weight'] ) ) { |
To be on the safe side, plugin code can include that check even if other code should already be restricted access, but it is critical if there isn’t another restriction in place before the upload code is accessed.
Cross-Site Request Forgery (CSRF)
Normally, users with the Administrator role already have the capability to upload arbitrary files, so it wouldn’t be a vulnerability to intentionally upload them. But what if they didn’t intend to do that? That brings in the second issue with the code, there wasn’t a nonce check to prevent cross-site request forgery (CSRF). Without that, an attacker could cause a logged in Administrator that accesses a page the attacker controls to upload arbitrary files.
The nonce check involves two pieces. A nonce needs to be generated and added to legitimate requests to take an action. And a check needs to be done before the action happens to make sure a valid nonce has been included. WordPress handles most of the work to do that for plugin developers.
With this vulnerability, the nonce was added to the form submitted when taking the action:
116 | <input type="hidden" name="nonce" value="<?php echo wp_create_nonce( 'create_block_theme' ); ?>" /> |
There are several functions that can be used for the nonce check and the one used to address this vulnerability was wp_verify_nonce():
161 | wp_verify_nonce( $_POST['nonce'], 'create_block_theme' ) && |
WordPress’ documentation provides much more detail on implementing nonce functionality.
Limiting Files Being Uploaded
With those two things completed, there was no longer a vulnerability there, but the code could still be more secure, as it didn’t restrict what types of files can be uploaded. If the types of files that can be uploaded are restricted, even if someone can access the functionality without them being intended to, the damage can be limited. In other cases, non-Administrators are intended to access the upload functionality, so the restriction is more important.
The best approach, if possible, is to rely on WordPress function for handling file uploads wp_handle_upload(), as that has various built-in security checks. The vulnerable code instead used a PHP function, move_uploaded_file(), which doesn’t do security checks, so additional security checks need to be done. Based on other vulnerabilities in WordPress plugins, it is easy for those checks to be implemented in a way that isn’t effective, so wp_handle_upload() is the best option.