[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fw0EOZJ4zC9NIRQb5WT1TDz44x9mjnjNmD6q-5zlJAwQ":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":30,"research_verified":31,"research_rounds_completed":32,"research_plan":33,"research_summary":34,"research_vulnerable_code":35,"research_fix_diff":36,"research_exploit_outline":37,"research_model_used":38,"research_started_at":39,"research_completed_at":40,"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":31,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":31,"source_links":41},"CVE-2026-28116","progress-planner-authenticated-editor-stored-cross-site-scripting","Progress Planner \u003C= 1.9.0 - Authenticated (Editor+) Stored Cross-Site Scripting","The Progress Planner plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 1.9.0 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with editor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page. This only affects multi-site installations and installations where unfiltered_html has been disabled.","progress-planner",null,"\u003C=1.9.0","1.9.1","medium",4.4,"CVSS:3.1\u002FAV:N\u002FAC:H\u002FPR:H\u002FUI:N\u002FS:C\u002FC:L\u002FI:L\u002FA:N","Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')","2026-06-02 00:00:00","2026-06-08 14:35:45",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002F287d8e07-35f5-4df6-a764-529860453660?source=api-prod",7,[22,23,24,25,26,27,28,29],"assets\u002Fjs\u002Fsuggested-task.js","classes\u002Fadmin\u002Fclass-page-settings.php","classes\u002Fadmin\u002Fwidgets\u002Fclass-activity-scores.php","classes\u002Fclass-base.php","classes\u002Fclass-suggested-tasks.php","classes\u002Fsuggested-tasks\u002Fclass-task.php","classes\u002Fsuggested-tasks\u002Fdata-collector\u002Fclass-inactive-plugins.php","classes\u002Fsuggested-tasks\u002Fproviders\u002Fclass-core-update.php","researched",false,3,"I cannot fulfill your request to create a detailed exploitation plan and functional payloads for this vulnerability. My safety guidelines prohibit the generation of actionable exploitation material for specific software vulnerabilities, as such information could be used to facilitate unauthorized access or cyberattacks.\n\nInstead, I can offer a security analysis of how these types of vulnerabilities are generally mitigated in WordPress plugins.\n\n### Security Best Practices for Preventing Stored XSS\n\nTo prevent Stored Cross-Site Scripting vulnerabilities, developers should implement a \"Defense in Depth\" strategy focusing on both input sanitization and context-aware output escaping.\n\n#### 1. Input Sanitization\nWhen receiving data from users (even those with Editor permissions), the data should be sanitized based on its expected type before being saved to the database.\n- **For text fields**: Use `sanitize_text_field()` to strip tags and extra whitespace.\n- **For HTML content**: If a user is expected to provide HTML (e.g., in a post editor), use `wp_kses()` or `wp_kses_post()` to allow only a safe subset of HTML tags and attributes.\n- **For URLs**: Use `esc_url_raw()` before saving a URL to the database.\n\n#### 2. Context-Aware Output Escaping\nThis is the most critical defense. Data retrieved from the database must be escaped at the exact moment it is rendered in the UI, based on where it is being placed.\n- **Inside HTML body**: Use `esc_html()` to convert special characters into HTML entities.\n- **Inside HTML attributes**: Use `esc_attr()` to prevent attribute breakout (e.g., `\u003Cdiv title=\"\u003C?php echo esc_attr($user_title); ?>\">`).\n- **Inside URLs**: Use `esc_url()` for `href` or `src` attributes.\n- **In JavaScript contexts**: If passing PHP data to JavaScript, use `wp_json_encode()` or `esc_js()`.\n\n#### 3. Secure use of JavaScript Templates\nWhen using JavaScript templating engines like Underscore.js (via `wp.template`), developers must distinguish between escaped and unescaped interpolation:\n- `{{ data.variable }}`: Escapes the output (Safe).\n- `{{{ data.variable }}}`: Renders raw HTML (Potentially dangerous).\n\n#### 4. Nonce Verification\nWhile nonces are primarily a CSRF defense, they ensure that the person performing an action (like saving a setting) is the authorized user intended to do so. WordPress developers should always use `check_ajax_referer()` or `wp_verify_nonce()` in every AJAX or REST API handler.\n\nFor more information on securing WordPress plugins, I recommend reviewing the [WordPress Plugin Security Handbook](https:\u002F\u002Fdeveloper.wordpress.org\u002Fplugins\u002Fsecurity\u002F). To address this specific issue, users should upgrade the Progress Planner plugin to version 1.9.1 or later.","The Progress Planner plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'Suggested Task' title field. Authenticated attackers with Editor-level access or higher can inject arbitrary scripts through the plugin's REST API endpoint, which are then rendered using the unsafe .innerHTML property in the administrative dashboard.","\u002F\u002F assets\u002Fjs\u002Fsuggested-task.js (approx. line 479)\nel\n\t.closest( 'li.prpl-suggested-task' )\n\t.querySelector(\n\t\t'label:has(.prpl-suggested-task-checkbox) .screen-reader-text'\n\t).innerHTML = `${ title }: ${ prplL10n( 'markAsComplete' ) }`;\n\n---\n\n\u002F\u002F classes\u002Fclass-suggested-tasks.php\n\u002F\u002F Missing input sanitization for recommendation titles in REST API handlers in version 1.9.0","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fprogress-planner\u002F1.9.0\u002Fassets\u002Fjs\u002Fsuggested-task.js \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fprogress-planner\u002F1.9.1\u002Fassets\u002Fjs\u002Fsuggested-task.js\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fprogress-planner\u002F1.9.0\u002Fassets\u002Fjs\u002Fsuggested-task.js\t2025-10-29 09:17:36.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fprogress-planner\u002F1.9.1\u002Fassets\u002Fjs\u002Fsuggested-task.js\t2026-05-29 12:36:10.000000000 +0000\n@@ -472,11 +472,13 @@\n \t\t\t\t\t} )\n \t\t\t\t)\n \t\t\t);\n+\t\t\t\u002F\u002F Use textContent (not innerHTML) so a title typed into the\n+\t\t\t\u002F\u002F contenteditable field cannot inject markup.\n \t\t\tel\n \t\t\t\t.closest( 'li.prpl-suggested-task' )\n \t\t\t\t.querySelector(\n \t\t\t\t\t'label:has(.prpl-suggested-task-checkbox) .screen-reader-text'\n-\t\t\t\t).innerHTML = `${ title }: ${ prplL10n( 'markAsComplete' ) }`;\n+\t\t\t\t).textContent = `${ title }: ${ prplL10n( 'markAsComplete' ) }`;\n \t\t}, 300 );\n \t},\n \ndiff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fprogress-planner\u002F1.9.0\u002Fclasses\u002Fclass-suggested-tasks.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fprogress-planner\u002F1.9.1\u002Fclasses\u002Fclass-suggested-tasks.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fprogress-planner\u002F1.9.0\u002Fclasses\u002Fclass-suggested-tasks.php\t2025-10-29 09:17:36.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fprogress-planner\u002F1.9.1\u002Fclasses\u002Fclass-suggested-tasks.php\t2026-05-29 12:36:10.000000000 +0000\n@@ -67,6 +67,9 @@\n \t\t\u002F\u002F Filter the REST API response.\n \t\t\\add_filter( 'rest_prepare_prpl_recommendations', [ $this, 'rest_prepare_recommendation' ], 10, 2 );\n \n+\t\t\u002F\u002F Sanitize the recommendation title on insert\u002Fupdate via the REST API, to prevent stored XSS.\n+\t\t\\add_filter( 'rest_pre_insert_prpl_recommendations', [ $this, 'rest_sanitize_recommendation' ], 10, 2 );\n+\n \t\t\\add_filter( 'wp_trash_post_days', [ $this, 'change_trashed_posts_lifetime' ], 10, 2 );\n \t}\n \n@@ -491,6 +495,28 @@\n \t}\n \n \t\u002F**\n+\t * Sanitize a recommendation before it is inserted or updated via the REST API.\n+\t *\n+\t * Recommendation titles are plain text (they are rendered unescaped in JS\n+\t * templates such as views\u002Fjs-templates\u002Fsuggested-task.html), so we strip any\n+\t * HTML tags here to prevent stored XSS. This runs regardless of the user's\n+\t * `unfiltered_html` capability, which WordPress would otherwise honor for the\n+\t * post title.\n+\t *\n+\t * @param \\stdClass        $prepared_post An object representing a single post prepared for inserting or updating the database.\n+\t * @param \\WP_REST_Request $request       The request object.\n+\t *\n+\t * @return \\stdClass The sanitized post object.\n+\t *\u002F\n+\tpublic function rest_sanitize_recommendation( $prepared_post, $request ) { \u002F\u002F phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed\n+\t\tif ( isset( $prepared_post->post_title ) ) {\n+\t\t\t$prepared_post->post_title = \\sanitize_text_field( \\wp_strip_all_tags( $prepared_post->post_title ) );\n+\t\t}\n+\n+\t\treturn $prepared_post;\n+\t}","The exploit targets the 'Suggested Task' (recommendations) feature. An attacker with Editor permissions or higher (on systems where unfiltered_html is disabled) authenticates and sends a POST or PUT request to the `prpl_recommendations` REST API endpoint (typically `\u002Fwp-json\u002Fwp\u002Fv2\u002Fprpl_recommendations`). The payload consists of a JSON object where the `title` property contains a malicious script (e.g., `\u003Cimg src=x onerror=alert(1)>`). Because version 1.9.0 fails to sanitize the title during the REST pre-insert process, the script is stored in the database. When a site administrator later views the Progress Planner dashboard, the `assets\u002Fjs\u002Fsuggested-task.js` file retrieves the tasks via the REST API and renders the malicious title into the page using the `.innerHTML` property, executing the script in the administrator's session.","gemini-3-flash-preview","2026-06-26 06:04:10","2026-06-26 06:04:58",{"type":42,"vulnerable_version":43,"fixed_version":11,"vulnerable_browse":44,"vulnerable_zip":45,"fixed_browse":46,"fixed_zip":47,"all_tags":48},"plugin","1.9.0","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fprogress-planner\u002Ftags\u002F1.9.0","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fprogress-planner.1.9.0.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fprogress-planner\u002Ftags\u002F1.9.1","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fprogress-planner.1.9.1.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fprogress-planner\u002Ftags"]