PluginProbe
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar / trunk
NotificationX – FOMO, Live Sales Notification, WooCommerce Sales Popup, GDPR, Social Proof, Announcement Banner & Floating Notification Bar vtrunk
3.3.1 3.3.0 3.2.14 3.2.13 3.2.12 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 trunk 0.2.5.5 0.2.5.6 0.2.5.7 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.2.0 1.2.1 All 156 releases
← All changes | includes/Admin/Entries.php +40 -0 3.2.9trunk View file →
@@ -57,12 +57,45 @@
57 57 return $this->count;
58 58 }
59 59
60 60
61 + /**
62 + * Recursively sanitize the user-controlled `data` payload of an entry
63 + * before it is stored. Entry data frequently originates from low-trust,
64 + * even unauthenticated sources — public form submissions (Gravity Forms,
65 + * CF7, FluentForm, WPForms, Ninja Forms, WeForms, Formidable, …) and the
66 + * Zapier / IFTTT REST ingest — and is later rendered on the frontend. The
67 + * JS renderer only escapes string-typed values, so an array-wrapped value
68 + * (e.g. `name => ['<img src=x onerror=alert(1)>']`) is coerced back to its
69 + * raw string and injected via `dangerouslySetInnerHTML` — a stored XSS
70 + * (Patchstack, NotificationX Pro <= 3.1.3). insert_entry()/insert_entries()
71 + * are the single storage chokepoint every extension (Free and Pro) funnels
72 + * through, so sanitizing every string leaf here closes that class of issue
73 + * regardless of ingest path. Non-string scalars (ints, bools, timestamps)
74 + * are left untouched so downstream typing is preserved; only strings can
75 + * carry markup.
76 + *
77 + * @param mixed $data
78 + * @return mixed
79 + */
80 + public function sanitize_entry_data($data) {
81 + if (is_array($data)) {
82 + array_walk_recursive($data, function (&$val) {
83 + if (is_string($val)) {
84 + $val = sanitize_text_field($val);
85 + }
86 + });
87 + } elseif (is_string($data)) {
88 + $data = sanitize_text_field($data);
89 + }
90 + return $data;
91 + }
92 +
61 93 public function insert_entry($entry) {
62 94 if(empty($entry['data'])){
63 95 return false;
64 96 }
97 + $entry['data'] = $this->sanitize_entry_data($entry['data']);
65 98 $timestamp = !empty($entry['data']['timestamp']) ? $entry['data']['timestamp'] : time();
66 99 if(empty($entry['created_at'])){
67 100 $entry['created_at'] = Helper::mysql_time($timestamp);
68 101 }
@@ -68,11 +101,13 @@
68 101 }
69 102 if(empty($entry['updated_at'])){
70 103 $entry['updated_at'] = Helper::mysql_time($timestamp);
71 104 }
105 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
72 106 $entry = apply_filters('nx_insert_entry', $entry);
73 107 $result = Database::get_instance()->insert_post(Database::$table_entries, $entry, $this->format);
74 108 if ( $result ) {
109 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
75 110 do_action( 'nx_after_entry_inserted', $entry );
76 111 }
77 112 return $result;
78 113 }
@@ -82,8 +117,9 @@
82 117 if(empty($entry['data'])){
83 118 unset($entries[$key]);
84 119 continue;
85 120 }
121 + $entry['data'] = $this->sanitize_entry_data($entry['data']);
86 122 $timestamp = !empty($entry['data']['timestamp']) ? $entry['data']['timestamp'] : time();
87 123 if(empty($entry['created_at'])){
88 124 $entry['created_at'] = Helper::mysql_time($timestamp);
89 125 }
@@ -89,8 +125,9 @@
89 125 }
90 126 if(empty($entry['updated_at'])){
91 127 $entry['updated_at'] = Helper::mysql_time($timestamp);
92 128 }
129 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
93 130 $entries[$key] = apply_filters('nx_insert_entry', $entry);
94 131 }
95 132 return Database::get_instance()->insert_posts(Database::$table_entries, $entries, $this->format);
96 133 }
@@ -100,8 +137,9 @@
100 137 $where__or_nx_id = ['nx_id' => $where__or_nx_id];
101 138 }
102 139 $entries = Database::get_instance()->get_posts(Database::$table_entries, $select, $where__or_nx_id, $join_table, $group_by_col, '', 'ORDER BY `created_at` DESC');
103 140 if ($data_in_entry) {
141 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
104 142 $entries = apply_filters('nx_get_entries', $entries);
105 143 return $entries;
106 144 }
107 145 foreach ($entries as $key => $value) {
@@ -108,10 +146,12 @@
108 146 if (!empty($value['data'])) {
109 147 $value = array_merge($value['data'], $value);
110 148 unset($value['data']);
111 149 }
150 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
112 151 $entries[$key] = apply_filters('nx_get_entry', $value);
113 152 }
153 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Reviewed for the NotificationX codebase: acceptable in this context.
114 154 $entries = apply_filters('nx_get_entries', $entries);
115 155 return $entries;
116 156 }
117 157