Very Low
CVE-2024-31077
CVE ID
AttackerKB requires a CVE ID in order to pull vulnerability data and references from the CVE list and the National Vulnerability Database. If available, please supply below:
Add References:
Very Low
(1 user assessed)Very High
(1 user assessed)Unknown
Unknown
Unknown
MITRE ATT&CK
Collection
Command and Control
Credential Access
Defense Evasion
Discovery
Execution
Exfiltration
Impact
Initial Access
Lateral Movement
Persistence
Privilege Escalation
Topic Tags
Description
Forminator prior to 1.29.3 contains a SQL injection vulnerability. If this vulnerability is exploited, a remote authenticated attacker with an administrative privilege may obtain and alter any information in the database and cause a denial-of-service (DoS) condition.
Add Assessment
Ratings
-
Attacker ValueVery Low
-
ExploitabilityVery High
Technical Analysis
Forminator Wordpress plugin versions prior to 1.29.3 are vulnerable to SQL injection. After investigating the changes made in version 1.29.3, it is easy to see that no input sanitization was done on the order_by
and order
parameters before being concatenated to the SQL statement. This code lies in the get_filter_entries()
function in library/model/class-form-entry-model.php
:
$order_by = 'ORDER BY entries.entry_id'; if ( isset( $filters['order_by'] ) ) { $order_by = 'ORDER BY ' . esc_sql( $filters['order_by'] ); // unesacaped. } $order = 'DESC'; if ( isset( $filters['order'] ) ) { $order = esc_sql( $filters['order'] ); } // group. $group_by = 'GROUP BY entries.entry_id'; $sql = "SELECT entries.`entry_id` FROM {$table_name} entries INNER JOIN {$entries_meta_table_name} AS metas ON (entries.entry_id = metas.entry_id) WHERE {$where} {$group_by} {$order_by} {$order}"; $results = $wpdb->get_results( $wpdb->prepare( $sql, $form_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
This function is called by _prepare_export_data()
in library/class-export.php
when exporting Quiz or Form data as a CSV or sent as an attachment via email. This code path is reached when filters like order
or order_by
are used in the query.
To demonstrate this, authenticate as an admin to access the Wordpress Admin Dashboard, go to /wp-admin/admin.php?page=forminator-entries
, click on the filter button on the right hand side of the submission list, add some filters and click APPLY
. The ones that interest us are Sort By
and Sort Order
. Then, click on EXPORT
, select Apply Submission Filters
in the Manual Exports
section and click DOWNLOAD CSV
.
This will send the following POST request:
POST /wp-admin/admin.php?page=forminator-entries&form_type=forminator_forms&form_id=7&entries-action&date_range&min_id&max_id&search&order_by=entries.date_created&order=DESC&entry_status=all&entries-action-bottom HTTP/1.1 Host: localhost:8080 Content-Length: 96 …[SNIP]... forminator_export=1&form_id=7&form_type=cform&_forminator_nonce=ed27a59f8a&submission-filter=yes
It should return the submission entries in CSV format.
Now, let’s inject some SQL commands in the order_by
parameter. According to the code, the vulnerable SQL query should not return anything in the response and we’ll need to go with blind SQLi. Since it will be concatenated to the ORDER BY
clause, we will use the following query:
1,(select if((1=1),1,(select 1 union select 2)))
If the if
condition is true, the submission entries should be returned. If it is false, an empty list should be returned:
- True (
1=1
)
- False (
1=0
)
More precisely, it is an Blind Error-Based SQLi, since a false
statement will fail with an SQL error: ERROR 1242 (21000): Subquery returns more than 1 row
We now confirmed that SQLi is possible.
One big caveat to this attack is that each time a CSV is required, Forminator updates the forminator_exporter_log
Wordpress option with a timestamp:
124 $count = $export_data->entries_count; 125 // save the time for later uses. 126 $logs = get_option( 'forminator_exporter_log', array() ); 127 if ( ! isset( $logs[ $model->id ] ) ) { 128 $logs[ $model->id ] = array(); 129 } 130 $logs[ $model->id ][] = array( 131 'time' => current_time( 'timestamp' ), 132 'count' => $count, 133 ); 134 update_option( 'forminator_exporter_log', $logs );
This will cause Wordpress to update the forminator_exporter_log
option value in the wp_options
table each time a request is received. This code is not overwriting the previous timestamp but actually adding a new timestamp to the option each time a CSV is required. As a result, the forminator_exporter_log
option value will increase in size each time it is updated.
This is not a big problem per se, but if binary logging is enabled (it is enabled by default from MySQL 8.0), each database update will add an event to the binary log file (binlog.*
). Since a blind SQLi usually requires a lot of queries, the binary log files will increase exponentially and quickly fill out all the disk space on the MySQL server. This will end up causing a DoS (it happened to me many times while investigating).
To conclude, this vulnerability is relatively easy to exploit but requires privileged access to Wordpress in order to reach the Forminator CSV export functionality. Even with these privileges, it is very likely to cause a DoS before any useful data is retrieved from the database.
Would you also like to delete your Exploited in the Wild Report?
Delete Assessment Only Delete Assessment and Exploited in the Wild ReportCVSS V3 Severity and Metrics
General Information
Vendors
- WPMU DEV
Products
- Forminator
References
Additional Info
Technical Analysis
Report as Exploited in the Wild
CVE ID
AttackerKB requires a CVE ID in order to pull vulnerability data and references from the CVE list and the National Vulnerability Database. If available, please supply below: