[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fH-qnI_lg1GO5t8t-gwcbdW8NCYaaSgIT0aB728WdssI":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":16,"references":17,"days_to_patch":19,"patch_diff_files":20,"patch_trac_url":9,"research_status":25,"research_verified":26,"research_rounds_completed":27,"research_plan":28,"research_summary":29,"research_vulnerable_code":30,"research_fix_diff":31,"research_exploit_outline":32,"research_model_used":33,"research_started_at":34,"research_completed_at":35,"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":26,"poc_model_used":9,"poc_verification_depth":9,"source_links":36},"CVE-2026-3155","onesignal-web-push-notifications-missing-authorization-to-authenticated-subscriber-post-meta-deletion-via-postid","OneSignal – Web Push Notifications \u003C= 3.8.0 - Missing Authorization to Authenticated (Subscriber+) Post Meta Deletion via 'post_id'","The OneSignal – Web Push Notifications plugin for WordPress is vulnerable to authorization bypass in versions up to, and including, 3.8.0. This is due to the plugin not properly verifying that a user is authorized to perform an action. This makes it possible for authenticated attackers, with subscriber-level access and above, to delete OneSignal metadata for arbitrary posts.","onesignal-free-web-push-notifications",null,"\u003C=3.8.0","3.8.1","low",3.1,"CVSS:3.1\u002FAV:N\u002FAC:H\u002FPR:L\u002FUI:N\u002FS:U\u002FC:N\u002FI:L\u002FA:N","Missing Authorization","2026-04-15 21:51:29",[18],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002F58337bbc-ba10-4876-b91c-78657afc67d1?source=api-prod",0,[21,22,23,24],"onesignal.php","readme.txt","v2\u002Fnotice.js","v2\u002Fonesignal-admin.php","researched",false,3,"# Vulnerability Research Plan: CVE-2026-3155\n\n## 1. Vulnerability Summary\nThe **OneSignal – Web Push Notifications** plugin (\u003C= 3.8.0) contains a missing authorization vulnerability in its AJAX handling logic. Specifically, the function `has_metadata()` registered via the `wp_ajax_has_metadata` hook in `v2\u002Fonesignal-admin.php` fails to perform any capability checks (e.g., `current_user_can()`) or nonce verification (e.g., `check_ajax_referer()`). \n\nThis allows any authenticated user, including those with **Subscriber** permissions, to delete specific post metadata keys (`status`, `recipients`, `response_body`) for any arbitrary post by providing a `post_id`. This metadata is used to track notification delivery status.\n\n## 2. Attack Vector Analysis\n- **Endpoint:** `\u002Fwp-admin\u002Fadmin-ajax.php`\n- **AJAX Action:** `has_metadata`\n- **HTTP Method:** `GET` (The code uses `$_GET['post_id']`)\n- **Vulnerable Parameter:** `post_id`\n- **Authentication:** Required (Subscriber or higher)\n- **Preconditions:** The plugin must be operating in \"V2\" mode. Per `onesignal.php`, this occurs when the plugin is not a \"new install\" and has not been \"migrated\" to V3.\n\n## 3. Code Flow\n1. **Entry Point:** An authenticated user sends a GET request to `admin-ajax.php?action=has_metadata&post_id=[TARGET_ID]`.\n2. **Hook Registration:** In `v2\u002Fonesignal-admin.php`, the hook is registered:\n   ```php\n   add_action('wp_ajax_has_metadata', 'has_metadata');\n   ```\n3. **Execution:** The `has_metadata()` function is called:\n   - It retrieves `$post_id` from `$_GET['post_id']` (line 30).\n   - It filters the ID using `FILTER_SANITIZE_NUMBER_INT`.\n   - It retrieves post meta for `recipients`, `status`, and `response_body` (lines 37-49).\n4. **Vulnerable Sink:** The function then unconditionally calls `delete_post_meta` for these keys:\n   ```php\n   \u002F\u002F v2\u002Fonesignal-admin.php lines 52-54\n   delete_post_meta($post_id, 'status');\n   delete_post_meta($post_id, 'recipients');\n   delete_post_meta($post_id, 'response_body');\n   ```\n5. **Response:** The function returns the deleted values in a JSON-encoded array and exits.\n\n## 4. Nonce Acquisition Strategy\nBased on the analysis of `v2\u002Fonesignal-admin.php`, the `has_metadata` function **does not implement a nonce check**. \n- Line 27 begins the function definition.\n- There is no call to `check_ajax_referer()` or `wp_verify_nonce()`.\n- Therefore, **no nonce is required** to exploit this specific vulnerability.\n\n## 5. Exploitation Strategy\nThe goal is to delete the OneSignal notification metadata for a specific post using a Subscriber-level account.\n\n### Step-by-Step Plan:\n1. **Target Identification:** Identify a `post_id` of a published post.\n2. **Authentication:** Log in to WordPress as a Subscriber.\n3. **Trigger Deletion:** Send a GET request to the AJAX endpoint.\n   - **URL:** `http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002Fadmin-ajax.php?action=has_metadata&post_id=[POST_ID]`\n   - **Headers:** Provide the Subscriber's session cookies.\n4. **Verify Deletion:** Check if the metadata keys are removed from the database for that `post_id`.\n\n## 6. Test Data Setup\nTo successfully test this, the environment must be forced into the vulnerable V2 code path and have existing metadata:\n\n1. **Force V2 Mode:**\n   ```bash\n   wp option update OneSignalWPSetting '{\"app_id\":\"12345\"}' --format=json\n   wp option update onesignal_plugin_migrated 0\n   ```\n2. **Create Target Post:**\n   ```bash\n   POST_ID=$(wp post create --post_title=\"Target Post\" --post_status=publish --porcelain)\n   ```\n3. **Inject Metadata:** Add the specific keys targeted by the function:\n   ```bash\n   wp post meta add $POST_ID status \"sent\"\n   wp post meta add $POST_ID recipients 100\n   wp post meta add $POST_ID response_body \"success\"\n   ```\n4. **Create Subscriber:**\n   ```bash\n   wp user create attacker attacker@example.com --role=subscriber --user_pass=password\n   ```\n\n## 7. Expected Results\n- The AJAX request should return a JSON response containing the values of the metadata before they were deleted.\n  - Example: `{\"recipients\":\"100\",\"status_code\":\"sent\",\"response_body\":\"success\"}`\n- The HTTP status code should be `200 OK`.\n\n## 8. Verification Steps\nAfter the HTTP request, use WP-CLI to confirm the metadata is gone:\n```bash\n# These should all return empty strings\nwp post meta get [POST_ID] status\nwp post meta get [POST_ID] recipients\nwp post meta get [POST_ID] response_body\n```\n\n## 9. Alternative Approaches\nIf the plugin automatically migrates to V3 upon admin access (preventing V2 code execution), the exploit can be attempted by:\n1. Directly calling the AJAX action immediately after setting the options via CLI, before any admin user navigates the site.\n2. Checking if `v3\u002Fonesignal-admin\u002Fonesignal-admin.php` (not provided in source but part of the plugin) contains a similar unauthenticated or under-privileged meta-deletion logic. Given the CVSS and description, it is highly likely that this logic was either replicated or left reachable in the affected versions.","The OneSignal plugin for WordPress (versions \u003C= 3.8.0) is vulnerable to an authorization bypass due to a missing capability check and nonce verification in its 'has_metadata' AJAX handler. This allows authenticated users, including those with subscriber-level permissions, to delete OneSignal notification metadata (status, recipients, and response body) for any arbitrary post by providing its ID.","\u002F\u002F v2\u002Fonesignal-admin.php line 26\nadd_action('wp_ajax_has_metadata', 'has_metadata');\nfunction has_metadata()\n{\n    $post_id = isset($_GET['post_id']) ?\n            (filter_var($_GET['post_id'], FILTER_SANITIZE_NUMBER_INT))\n            : '';\n\n    if (is_null($post_id)) {\n        $data = array('error' => 'could not get post id');\n    } else {\n        $recipients = get_post_meta($post_id, 'recipients');\n        if ($recipients && is_array($recipients)) {\n            $recipients = $recipients[0];\n        }\n\n        $status = get_post_meta($post_id, 'status');\n        if ($status && is_array($status)) {\n            $status = $status[0];\n        }\n\n        $response_body = get_post_meta($post_id, 'response_body');\n        if ($response_body && is_array($response_body)) {\n            $response_body = $response_body[0];\n        }\n\n        \u002F\u002F reset meta\n        \u002F\u002F v2\u002Fonesignal-admin.php lines 53-55\n        delete_post_meta($post_id, 'status');\n        delete_post_meta($post_id, 'recipients');\n        delete_post_meta($post_id, 'response_body');\n\n        $data = array('recipients' => $recipients, 'status_code' => $status, 'response_body' => $response_body);\n\n    }\n\n    echo wp_json_encode($data);\n\n    exit;\n}","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fonesignal-free-web-push-notifications\u002F3.8.0\u002Fonesignal.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fonesignal-free-web-push-notifications\u002F3.8.1\u002Fonesignal.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fonesignal-free-web-push-notifications\u002F3.8.0\u002Fonesignal.php\t2026-01-22 23:02:16.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fonesignal-free-web-push-notifications\u002F3.8.1\u002Fonesignal.php\t2026-04-07 23:49:20.000000000 +0000\n@@ -6,7 +6,7 @@\n  * Plugin Name: OneSignal Push Notifications\n  * Plugin URI: https:\u002F\u002Fonesignal.com\u002F\n  * Description: Free web push notifications.\n- * Version: 3.8.0\n+ * Version: 3.8.1\n  * Author: OneSignal\n  * Author URI: https:\u002F\u002Fonesignal.com\n  * License: MIT\n@@ -18,7 +18,7 @@\n define('ONESIGNAL_URI_REVEAL_PROJECT_NUMBER', 'reveal_project_number=true');\n \n \u002F\u002F Plugin version - must match Version in plugin header\n-define('ONESIGNAL_PLUGIN_VERSION', '030800');\n+define('ONESIGNAL_PLUGIN_VERSION', '030801');\n \n \u002F\u002F Constants for plugin versions\n define('ONESIGNAL_VERSION_V2', 'v2');\ndiff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fonesignal-free-web-push-notifications\u002F3.8.0\u002Freadme.txt \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fonesignal-free-web-push-notifications\u002F3.8.1\u002Freadme.txt\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fonesignal-free-web-push-notifications\u002F3.8.0\u002Freadme.txt\t2026-01-22 23:02:16.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fonesignal-free-web-push-notifications\u002F3.8.1\u002Freadme.txt\t2026-04-07 23:49:20.000000000 +0000\n@@ -4,7 +4,7 @@\n Tags: push notification, push notifications, desktop notifications, mobile notifications, chrome push, android, android notification, android notifications, android push, desktop notification, firefox, firefox push, mobile, mobile notification, notification, notifications, notify, onesignal, push, push messages, safari, safari push, web push, chrome\n Requires at least: 3.8\n Tested up to: 6.9\n-Stable tag: 3.8.0\n+Stable tag: 3.8.1\n License: GPLv2 or later\n License URI: http:\u002F\u002Fwww.gnu.org\u002Flicenses\u002Fgpl-2.0.html\n \n@@ -64,6 +64,9 @@\n \n == Changelog ==\n \n+= 3.8.1 =\n+- Improve AJAX handler validation\n+\n = 3.8.0 =\n - add HTML meta tag for improved debugging\n \n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fonesignal-free-web-push-notifications\u002F3.8.0\u002Fv2\u002Fonesignal-admin.php\t2024-12-20 03:36:24.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fonesignal-free-web-push-notifications\u002F3.8.1\u002Fv2\u002Fonesignal-admin.php\t2026-04-07 23:49:20.000000000 +0000\n@@ -18,47 +18,45 @@\n     if ($post) {\n         wp_register_script('notice_script', plugins_url('notice.js', __FILE__), array('jquery'), '1.1', true);\n         wp_enqueue_script('notice_script');\n-        wp_localize_script('notice_script', 'ajax_object', array('ajax_url' => admin_url('admin-ajax.php'), 'post_id' => $post->ID));\n+        wp_localize_script('notice_script', 'ajax_object', array(\n+            'ajax_url' => admin_url('admin-ajax.php'),\n+            'post_id' => $post->ID,\n+            'nonce' => wp_create_nonce('onesignal_has_metadata'),\n+        ));\n     }\n }\n \n add_action('wp_ajax_has_metadata', 'has_metadata');\n function has_metadata()\n {\n-    $post_id = isset($_GET['post_id']) ?\n-            (filter_var($_GET['post_id'], FILTER_SANITIZE_NUMBER_INT))\n-            : '';\n-\n-    if (is_null($post_id)) {\n-        $data = array('error' => 'could not get post id');\n-    } else {\n-        $recipients = get_post_meta($post_id, 'recipients');\n-        if ($recipients && is_array($recipients)) {\n-            $recipients = $recipients[0];\n-        }\n-\n-        $status = get_post_meta($post_id, 'status');\n-        if ($status && is_array($status)) {\n-            $status = $status[0];\n-        }\n-\n-        $response_body = get_post_meta($post_id, 'response_body');\n-        if ($response_body && is_array($response_body)) {\n-            $response_body = $response_body[0];\n-        }\n-\n-        \u002F\u002F reset meta\n-        delete_post_meta($post_id, 'status');\n-        delete_post_meta($post_id, 'recipients');\n-        delete_post_meta($post_id, 'response_body');\n+    check_ajax_referer('onesignal_has_metadata', 'nonce');\n \n-        $data = array('recipients' => $recipients, 'status_code' => $status, 'response_body' => $response_body);\n+    $post_id = isset($_GET['post_id']) ? intval($_GET['post_id']) : 0;\n \n+    if (!$post_id || !current_user_can('edit_post', $post_id)) {\n+        wp_send_json_error(array('error' => 'Unauthorized'), 403);\n     }\n \n-    echo wp_json_encode($data);\n+    $recipients = get_post_meta($post_id, 'recipients');\n+    if ($recipients && is_array($recipients)) {\n+        $recipients = $recipients[0];\n+    }\n+\n+    $status = get_post_meta($post_id, 'status');\n+    if ($status && is_array($status)) {\n+        $status = $status[0];\n+    }\n+\n+    $response_body = get_post_meta($post_id, 'response_body');\n+    if ($response_body && is_array($response_body)) {\n+        $response_body = $response_body[0];\n+    }\n+\n+    delete_post_meta($post_id, 'status');\n+    delete_post_meta($post_id, 'recipients');\n+    delete_post_meta($post_id, 'response_body');\n \n-    exit;\n+    wp_send_json(array('recipients' => $recipients, 'status_code' => $status, 'response_body' => $response_body));\n }","To exploit this vulnerability, an attacker needs a Subscriber-level account on the WordPress site. The attacker must then identify the ID of a post they wish to target. By sending a GET request to the `\u002Fwp-admin\u002Fadmin-ajax.php` endpoint with the parameters `action=has_metadata` and `post_id=[target_id]`, the plugin's `has_metadata` function will execute without any permission or nonce checks. The server will respond with the current values of the `recipients`, `status`, and `response_body` metadata for that post, and subsequently delete those metadata records from the database.","gemini-3-flash-preview","2026-04-16 15:15:38","2026-04-16 15:16:13",{"type":37,"vulnerable_version":38,"fixed_version":11,"vulnerable_browse":39,"vulnerable_zip":40,"fixed_browse":41,"fixed_zip":42,"all_tags":43},"plugin","3.8.0","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fonesignal-free-web-push-notifications\u002Ftags\u002F3.8.0","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fonesignal-free-web-push-notifications.3.8.0.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fonesignal-free-web-push-notifications\u002Ftags\u002F3.8.1","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fonesignal-free-web-push-notifications.3.8.1.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fonesignal-free-web-push-notifications\u002Ftags"]