[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$ftEhhTvDzOdTi9FTCx2A2Whi_tPrgoLbCL2hru_R8His":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-49771","photo-gallery-by-10web-mobile-friendly-image-gallery-authenticated-contributor-sql-injection","Photo Gallery by 10Web – Mobile-Friendly Image Gallery \u003C= 1.8.41 - Authenticated (Contributor+) SQL Injection","The Photo Gallery by 10Web – Mobile-Friendly Image Gallery plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 1.8.41 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.","photo-gallery",null,"\u003C=1.8.41","1.8.42","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-06-04 00:00:00","2026-06-08 14:46:28",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002F4a82c911-71aa-4f6c-8088-7b839e027a6a?source=api-prod",5,[22,23,24,25,26],"admin\u002Fcontrollers\u002FShortcode.php","framework\u002FWDWLibrary.php","frontend\u002Fmodels\u002Fmodel.php","photo-gallery.php","readme.txt","researched",false,3,"# Exploitation Research Plan - CVE-2026-49771\n\n## 1. Vulnerability Summary\nThe **Photo Gallery by 10Web** plugin (versions \u003C= 1.8.41) contains an authenticated SQL injection vulnerability. The flaw exists due to the plugin's failure to properly sanitize and prepare user-supplied parameters (likely sorting or filtering parameters) before incorporating them into SQL queries. Specifically, the `ShortcodeController_bwg` class in the admin interface calls data-fetching functions that utilize unvalidated request parameters in `ORDER BY` or `WHERE` clauses. Authenticated users with **Contributor-level** permissions or higher can exploit this to extract sensitive data from the WordPress database.\n\n## 2. Attack Vector Analysis\n- **Endpoint:** `\u002Fwp-admin\u002Fadmin-ajax.php`\n- **AJAX Action:** `shortcode_bwg` (Registered in `photo-gallery.php` via `wp_ajax_shortcode_bwg`).\n- **HTTP Method:** `GET` or `POST`.\n- **Vulnerable Parameters:** `sort_by` or `order_by` (inferred from typical 10Web patterns and the \"Contributor+\" context).\n- **Authentication:** Required (Contributor-level user).\n- **Nonce Requirement:** Bypassable. The nonce check in `ShortcodeController_bwg::execute()` is only triggered if the `task` parameter is non-empty AND the `page` parameter equals `shortcode_bwg`. By omitting these,","The Photo Gallery by 10Web plugin for WordPress is vulnerable to authenticated SQL Injection due to the lack of sanitization and preparation for parameters used in SQL ORDER BY clauses. Contributor-level users can inject malicious SQL fragments into shortcode attributes, which are then persisted and executed when the gallery is rendered on the site.","\u002F\u002F admin\u002Fcontrollers\u002FShortcode.php\n  public function save() {\n    global $wpdb;\n    $tagtext = WDWLibrary::get('tagtext');\n    if ($tagtext) {\n      \u002F* clear tags *\u002F\n      $tagtext = \" \" . $tagtext;\n      $id = WDWLibrary::get('currrent_id', 0, 'intval');\n      $insert = WDWLibrary::get('bwg_insert', 0, 'intval');\n      if (!$insert) {\n        $wpdb->update($wpdb->prefix . 'bwg_shortcode', array(\n        'tagtext' => $tagtext\n        ), array('id' => $id), array('%s'), array('%d'));\n      }\n--- \n\u002F\u002F frontend\u002Fmodels\u002Fmodel.php\n  public function get_alb_gals_row( $bwg, $id, $albums_per_page, $sort_by, $order_by, $pagination_type = 0, $from = '' ) {\n    $prepareArgs = array();\n    if ( $albums_per_page \u003C 0 ) {\n      $albums_per_page = 0;\n    }\n    global $wpdb;\n    $order_by = 'ORDER BY `' . ( ( !empty( $from ) && $from === 'widget' ) ? 'id' : $sort_by ) . '` ' . $order_by;\n    if ( $sort_by == 'random' || $sort_by == 'RAND()' ) {\n      $order_by = 'ORDER BY RAND()';\n    }","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fphoto-gallery\u002F1.8.41\u002Fadmin\u002Fcontrollers\u002FShortcode.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fphoto-gallery\u002F1.8.42\u002Fadmin\u002Fcontrollers\u002FShortcode.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fphoto-gallery\u002F1.8.41\u002Fadmin\u002Fcontrollers\u002FShortcode.php\t2022-06-16 06:23:42.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fphoto-gallery\u002F1.8.42\u002Fadmin\u002Fcontrollers\u002FShortcode.php\t2026-05-29 16:34:32.000000000 +0000\n@@ -11,7 +11,7 @@\n \n   public function execute() {\n     $task = WDWLibrary::get('task');\n-    if ( $task != '' && $this->from_menu ) {\n+    if ( $task != '' && ( $this->from_menu || $task === 'save' ) ) {\n       if ( !WDWLibrary::verify_nonce(BWG()->nonce) ) {\n         die('Sorry, your nonce did not verify.');\n       }\n@@ -58,6 +58,7 @@\n     global $wpdb;\n     $tagtext = WDWLibrary::get('tagtext');\n     if ($tagtext) {\n+      $tagtext = WDWLibrary::sanitize_shortcode_tagtext( $tagtext );\n       \u002F* clear tags *\u002F\n       $tagtext = \" \" . $tagtext;\n       $id = WDWLibrary::get('currrent_id', 0, 'intval');\ndiff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fphoto-gallery\u002F1.8.41\u002Fframework\u002FWDWLibrary.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fphoto-gallery\u002F1.8.42\u002Fframework\u002FWDWLibrary.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fphoto-gallery\u002F1.8.41\u002Fframework\u002FWDWLibrary.php\t2026-05-14 17:41:56.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fphoto-gallery\u002F1.8.42\u002Fframework\u002FWDWLibrary.php\t2026-05-29 16:34:32.000000000 +0000\n@@ -3302,6 +3302,103 @@\n   }\n \n   \u002F**\n+   * Whitelist sort direction for SQL ORDER BY (returns ASC or DESC).\n+   *\n+   * @param string $order_by\n+   *\n+   * @return string\n+   *\u002F\n+  public static function sanitize_sort_direction( $order_by ) {\n+    return ( strtolower( trim( (string) $order_by ) ) === 'asc' ) ? 'ASC' : 'DESC';\n+  }\n+\n+  \u002F**\n+   * Whitelist album\u002Fgallery-group sort column for SQL ORDER BY.\n+   *\n+   * @param string $sort_by\n+   * @param string $from\n+   *\n+   * @return string\n+   *\u002F\n+  public static function sanitize_album_sort_column( $sort_by, $from = '' ) {\n+    if ( !empty( $from ) && $from === 'widget' ) {\n+      return 'id';\n+    }\n+    $sort_by = trim( (string) $sort_by );\n+    if ( $sort_by === 'random' || $sort_by === 'RAND()' ) {\n+      return 'random';\n+    }\n+    $allowed_columns = array( 'order', 'name', 'modified_date', 'id' );\n+    return in_array( $sort_by, $allowed_columns, true ) ? $sort_by : 'order';\n+  }\n+\n+  \u002F**\n+   * Whitelist image sort column for shortcode attributes.\n+   *\n+   * @param string $sort_by\n+   *\n+   * @return string\n+   *\u002F\n+  public static function sanitize_image_sort_column( $sort_by ) {\n+    $sort_by = trim( (string) $sort_by );\n+    if ( $sort_by === 'RAND()' ) {\n+      return 'random';\n+    }\n+    $allowed_columns = array( 'order', 'alt', 'date', 'filename', 'size', 'resolution', 'random', 'filetype' );\n+    return in_array( $sort_by, $allowed_columns, true ) ? $sort_by : 'order';\n+  }\n+\n+  \u002F**\n+   * Sanitize sort\u002Forder attributes in shortcode tagtext before storage.\n+   *\n+   * @param string $tagtext\n+   *\n+   * @return string\n+   *\u002F\n+  public static function sanitize_shortcode_tagtext( $tagtext ) {\n+    $tagtext = trim( (string) $tagtext );\n+    if ( $tagtext === '' ) {\n+      return '';\n+    }\n+    $data = self::parse_tagtext_to_array( $tagtext );\n+    if ( empty( $data ) ) {\n+      return $tagtext;\n+    }\n+    $album_group_sort_keys = array( \n+      'compact_album_sort_by',\n+      'masonry_album_sort_by',\n+      'extended_album_sort_by',\n+      'all_album_sort_by',\n+    );\n+    $sanitized = '';\n+    foreach ( $data as $key => $value ) {\n+      if ( in_array( $key, $album_group_sort_keys, true ) ) {\n+        $value = self::sanitize_album_sort_column( $value );\n+      }\n+      elseif ( preg_match( '\u002F_order_by$\u002F', $key ) || $key === 'order_by' ) {\n+        $value = ( self::sanitize_sort_direction( $value ) === 'ASC' ) ? 'asc' : 'desc';\n+      }\n+      elseif ( preg_match( '\u002F_sort_by$\u002F', $key ) || $key === 'sort_by' ) {\n+        $value = self::sanitize_image_sort_column( $value );\n+      }\n+      $sanitized .= ' ' . $key . '=\"' . self::escape_shortcode_attribute_value( $value ) . '\"';\n+    }\n+\n+    return $sanitized;\n+  }\n+\n+  \u002F**\n+   * Strip characters that break shortcode attribute quoting (preserves URLs and other content).\n+   *\n+   * @param string $value\n+   *\n+   * @return string\n+   *\u002F\n+  public static function escape_shortcode_attribute_value( $value ) {\n+    return str_replace( array( '\"', \"\\0\" ), '', (string) $value );\n+  }\n+\n+  \u002F**\ndiff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fphoto-gallery\u002F1.8.41\u002Ffrontend\u002Fmodels\u002Fmodel.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fphoto-gallery\u002F1.8.42\u002Ffrontend\u002Fmodels\u002Fmodel.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fphoto-gallery\u002F1.8.41\u002Ffrontend\u002Fmodels\u002Fmodel.php\t2022-12-17 18:33:38.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fphoto-gallery\u002F1.8.42\u002Ffrontend\u002Fmodels\u002Fmodel.php\t2026-05-29 16:34:32.000000000 +0000\n@@ -110,10 +110,14 @@\n       $albums_per_page = 0;\n     }\n     global $wpdb;\n-    $order_by = 'ORDER BY `' . ( ( !empty( $from ) && $from === 'widget' ) ? 'id' : $sort_by ) . '` ' . $order_by;\n-    if ( $sort_by == 'random' || $sort_by == 'RAND()' ) {\n+    $sort_by = WDWLibrary::sanitize_album_sort_column( $sort_by, $from );\n+    $sort_direction = WDWLibrary::sanitize_sort_direction( $order_by );\n+    if ( $sort_by === 'random' ) {\n       $order_by = 'ORDER BY RAND()';\n     }\n+    else {\n+      $order_by = 'ORDER BY `' . $sort_by . '` ' . $sort_direction;\n+    }\n     $search_where = '';\n     $search_value = trim( WDWLibrary::get( 'bwg_search_' . $bwg ) );\n     if ( !empty( $search_value ) ) {","The exploit target is the `shortcode_bwg` AJAX action. An authenticated attacker with at least Contributor permissions can bypass the plugin's weak nonce checks by omitting the `page` parameter in their request. The attacker sends a POST or GET request to `\u002Fwp-admin\u002Fadmin-ajax.php` with `action=shortcode_bwg` and `task=save`. The payload is passed in the `tagtext` parameter, formatted as a shortcode with a malicious attribute (e.g., `sort_by=\"id`, (SELECT SLEEP(5))\"`). This malicious attribute is saved to the plugin's shortcode metadata table. When the corresponding gallery is loaded on a page or via a post preview, the plugin parses the shortcode and passes the malicious `sort_by` string directly into a raw SQL query's `ORDER BY` clause in `get_alb_gals_row`, triggering the injection.","gemini-3-flash-preview","2026-06-26 05:03:22","2026-06-26 05:04:46",{"type":39,"vulnerable_version":40,"fixed_version":11,"vulnerable_browse":41,"vulnerable_zip":42,"fixed_browse":43,"fixed_zip":44,"all_tags":45},"plugin","1.8.41","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fphoto-gallery\u002Ftags\u002F1.8.41","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fphoto-gallery.1.8.41.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fphoto-gallery\u002Ftags\u002F1.8.42","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fphoto-gallery.1.8.42.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fphoto-gallery\u002Ftags"]