PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.14
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.14
6.2.14 6.2.13 6.2.12 6.2.10 6.2.11 6.2.9 6.2.8 6.2.7 6.2.6 6.2.5 6.2.4 6.2.3 6.2.2 3.6.22 3.6.31 3.6.40 3.6.41 3.6.42 3.6.50 3.6.51 3.6.60 3.6.61 3.6.62 3.6.64 3.6.65 All 196 releases
← All changes | app/Services/FormBuilder/ShortCodeParser.php +73 -25 6.2.46.2.14 View file →
@@ -10,8 +10,10 @@
10 10 use FluentForm\App\Helpers\Helper;
11 11
12 12 class ShortCodeParser
13 13 {
14 + const USER_SECRET_PROPERTIES = ['user_pass', 'user_activation_key', 'session_tokens', 'data'];
15 +
14 16 protected static $form = null;
15 17
16 18 protected static $entry = null;
17 19
@@ -95,9 +97,9 @@
95 97 'ff_will_return_html',
96 98 [
97 99 false,
98 100 $provider,
99 - $key
101 + $key,
100 102 ],
101 103 FLUENTFORM_FRAMEWORK_UPGRADE,
102 104 'fluentform/will_return_html',
103 105 'Use fluentform/will_return_html instead of ff_will_return_html.'
@@ -106,9 +108,9 @@
106 108 }
107 109 $parsable[$key] = static::parseShortCodeFromString($value, $isUrl, $isHtml, $htmlSanitized);
108 110 }
109 111 }
110 -
112 +
111 113 return $parsable;
112 114 }
113 115
114 116 protected static function parseShortCodeFromString($parsable, $isUrl = false, $isHtml = false, $htmlSanitized = false)
@@ -124,9 +126,9 @@
124 126 $value = '';
125 127 if (false !== strpos($matches[1], 'inputs.')) {
126 128 $formProperty = substr($matches[1], strlen('inputs.'));
127 129 $value = static::getFormData($formProperty, $isHtml);
128 - } else if (false !== strpos($matches[1], 'labels.')) {
130 + } elseif (false !== strpos($matches[1], 'labels.')) {
129 131 $formLabelProperty = substr($matches[1], strlen('labels.'));
130 132 $value = static::getFormLabelData($formLabelProperty);
131 133 } elseif (false !== strpos($matches[1], 'user.')) {
132 134 $userProperty = substr($matches[1], strlen('user.'));
@@ -141,17 +143,17 @@
141 143 $submissionProperty = substr($matches[1], strlen('submission.'));
142 144 $value = static::getSubmissionData($submissionProperty);
143 145 } elseif (false !== strpos($matches[1], 'cookie.')) {
144 146 $scookieProperty = substr($matches[1], strlen('cookie.'));
145 - $value = array_key_exists($scookieProperty, $_COOKIE) ? wp_unslash($_COOKIE[$scookieProperty]) : '';
147 + $value = array_key_exists($scookieProperty, $_COOKIE) ? sanitize_text_field(wp_unslash($_COOKIE[$scookieProperty])) : '';
146 148 } elseif (false !== strpos($matches[1], 'payment.')) {
147 149 $property = substr($matches[1], strlen('payment.'));
148 150 $deprecatedValue = apply_filters_deprecated(
149 151 'fluentform_payment_smartcode', [
150 - '',
151 - $property,
152 - self::getInstance()
153 - ],
152 + '',
153 + $property,
154 + self::getInstance(),
155 + ],
154 156 FLUENTFORM_FRAMEWORK_UPGRADE,
155 157 'fluentform/payment_smartcode',
156 158 'Use fluentform/payment_smartcode instead of fluentform_payment_smartcode.'
157 159 );
@@ -165,10 +167,13 @@
165 167 $value = fluentImplodeRecursive(', ', $value);
166 168 }
167 169
168 170 if ($isUrl) {
169 - $value = rawurlencode($value);
170 - } else if ($htmlSanitized) {
171 + // Don't encode values that are already complete URLs like {wp.site_url}
172 + if (!preg_match('#^https?://#i', (string) $value)) {
173 + $value = rawurlencode($value);
174 + }
175 + } elseif ($htmlSanitized) {
171 176 $value = fluentform_sanitize_html($value);
172 177 }
173 178
174 179 return $value;
@@ -223,9 +228,9 @@
223 228 [
224 229 $originalInput,
225 230 $field,
226 231 static::getForm()->id,
227 - $isHtml
232 + $isHtml,
228 233 ],
229 234 FLUENTFORM_FRAMEWORK_UPGRADE,
230 235 'fluentform/response_render_' . $field['element'],
231 236 'Use fluentform/response_render_' . $field['element'] . ' instead of fluentform_response_render_' . $field['element']
@@ -244,9 +249,9 @@
244 249 [
245 250 static::$store['inputs'][$key],
246 251 $field,
247 252 static::getForm()->id,
248 - $isHtml
253 + $isHtml,
249 254 ],
250 255 FLUENTFORM_FRAMEWORK_UPGRADE,
251 256 'fluentform/response_render_' . $field['element'],
252 257 'Use fluentform/response_render_' . $field['element'] . ' instead of fluentform_response_render_' . $field['element']
@@ -277,9 +282,9 @@
277 282 $key = $currentFieldName;
278 283 }
279 284 $inputLabel = ArrayHelper::get(ArrayHelper::get(static::$formFields, $key, []), 'label', '');
280 285 $inputLabel = str_replace(['[', ']'], '', $inputLabel);
281 - $keys = explode(".", $key);
286 + $keys = explode('.', $key);
282 287 if (count($keys) > 1) {
283 288 $parentKey = array_shift($keys);
284 289 $inputLabel = str_replace($parentKey, '', $inputLabel);
285 290 }
@@ -298,9 +303,34 @@
298 303 {
299 304 if (is_null(static::$store['user'])) {
300 305 static::$store['user'] = wp_get_current_user();
301 306 }
302 - return static::$store['user']->{$key};
307 +
308 + $user = static::$store['user'];
309 +
310 + // SECURITY (FINDING-11): `$user->{$key}` reads straight from the wp_users row via
311 + // WP_User::__get, so an author-controlled {user.user_pass} (or {user.user_activation_key})
312 + // would exfiltrate the *submitting* user's password hash / reset token in a notification.
313 + // Allow only a fixed set of safe profile fields; resolve anything else from user meta,
314 + // which never contains the sensitive wp_users columns.
315 + $allowed = [
316 + 'ID', 'id', 'display_name', 'first_name', 'last_name', 'user_email',
317 + 'user_login', 'user_nicename', 'nickname', 'user_url', 'description', 'roles',
318 + 'user_registered', // non-sensitive wp_users column; keep {user.user_registered} working
319 + ];
320 + if (static::isDeniedUserProperty($key)) {
321 + return '';
322 + }
323 + if (in_array($key, $allowed, true)) {
324 + return $user->{$key};
325 + }
326 +
327 + $key = (string) $key;
328 + if ($user->ID && '' !== $key) {
329 + return get_user_meta($user->ID, $key, true);
330 + }
331 +
332 + return '';
303 333 }
304 334
305 335 protected static function getPostData($key)
306 336 {
@@ -315,9 +345,9 @@
315 345
316 346 if (false !== strpos($key, 'author.')) {
317 347 $authorProperty = substr($key, strlen('author.'));
318 348 $authorId = static::$store['post']->post_author;
319 - if ($authorId) {
349 + if ($authorId && !static::isDeniedUserProperty($authorProperty)) {
320 350 $data = get_the_author_meta($authorProperty, $authorId);
321 351 if (!is_array($data)) {
322 352 return $data;
323 353 }
@@ -342,11 +372,30 @@
342 372 return '';
343 373 }
344 374 }
345 375
376 + if ('post_password' === $key) {
377 + return '';
378 + }
379 +
346 380 return static::$store['post']->{$key};
347 381 }
348 382
383 + // Shared by {user.*} and {embed_post.author.*} in both parsers. get_the_author_meta() and
384 + // WP_User fall through to any user meta, where plugins keep 2FA secrets and tokens under
385 + // protected (underscore) keys, so the secret columns alone are not enough to deny.
386 + public static function isDeniedUserProperty($property)
387 + {
388 + // Same aliases get_the_author_meta() accepts: 'pass' means user_pass
389 + if (in_array($property, ['login', 'pass', 'nicename', 'email', 'url', 'registered', 'activation_key', 'status'], true)) {
390 + $property = 'user_' . $property;
391 + }
392 +
393 + $denied = (array) apply_filters('fluentform/smartcode_user_denied_properties', self::USER_SECRET_PROPERTIES);
394 +
395 + return in_array($property, $denied, true) || is_protected_meta($property, 'user');
396 + }
397 +
349 398 protected static function getWPData($key)
350 399 {
351 400 if ('admin_email' == $key) {
352 401 return get_option('admin_email');
@@ -367,9 +416,11 @@
367 416 if (empty($entry->id)) {
368 417 return '';
369 418 }
370 419
371 - if (property_exists($entry, $key)) {
420 + $columns = Helper::getEntryColumns($entry);
421 +
422 + if (array_key_exists($key, $columns)) {
372 423 if ('total_paid' == $key || 'payment_total' == $key) {
373 424 return round($entry->{$key} / 100, 2);
374 425 }
375 426 if ('payment_method' == $key && 'test' == $entry->{$key}) {
@@ -415,9 +466,9 @@
415 466
416 467 $status = apply_filters_deprecated(
417 468 'fluentform_all_data_skip_password_field',
418 469 [
419 - __return_true()
470 + __return_true(),
420 471 ],
421 472 FLUENTFORM_FRAMEWORK_UPGRADE,
422 473 'fluentform/all_data_skip_password_field',
423 474 'Use fluentform/all_data_skip_password_field instead of fluentform_all_data_skip_password_field.'
@@ -435,9 +486,9 @@
435 486 $hideHiddenField = true;
436 487 $hideHiddenField = apply_filters_deprecated(
437 488 'fluentform_all_data_without_hidden_fields',
438 489 [
439 - $hideHiddenField
490 + $hideHiddenField,
440 491 ],
441 492 FLUENTFORM_FRAMEWORK_UPGRADE,
442 493 'fluentform/all_data_without_hidden_fields',
443 494 'Use fluentform/all_data_without_hidden_fields instead of fluentform_all_data_without_hidden_fields.'
@@ -470,9 +521,9 @@
470 521 [
471 522 $html,
472 523 $formFields,
473 524 $inputLabels,
474 - $response
525 + $response,
475 526 ],
476 527 FLUENTFORM_FRAMEWORK_UPGRADE,
477 528 'fluentform/all_data_shortcode_html',
478 529 'Use fluentform/all_data_shortcode_html instead of fluentform_all_data_shortcode_html.'
@@ -483,9 +534,9 @@
483 534 } elseif (0 === strpos($key, 'pdf.download_link.')) {
484 535 $key = apply_filters_deprecated(
485 536 'fluentform_shortcode_parser_callback_pdf.download_link.public',
486 537 [
487 - $key, static::getInstance()
538 + $key, static::getInstance(),
488 539 ],
489 540 FLUENTFORM_FRAMEWORK_UPGRADE,
490 541 'fluentform/shortcode_parser_callback_pdf.download_link.public',
491 542 'Use fluentform/shortcode_parser_callback_pdf.download_link.public instead of fluentform_shortcode_parser_callback_pdf.download_link.public.'
@@ -523,15 +574,13 @@
523 574 }
524 575 return '';
525 576 }
526 577
527 -
528 578 // if it's multi line then just return
529 579 if (false !== strpos($key, PHP_EOL)) { // most probably it's a css
530 580 return '{' . $key . '}';
531 581 }
532 582
533 -
534 583 $groups = explode('.', $key);
535 584 if (count($groups) > 1) {
536 585 $group = array_shift($groups);
537 586 $property = implode('.', $groups);
@@ -538,9 +587,9 @@
538 587 $handlerValue = apply_filters_deprecated(
539 588 'fluentform_smartcode_group_' . $group,
540 589 [
541 590 $property,
542 - static::getInstance()
591 + static::getInstance(),
543 592 ],
544 593 FLUENTFORM_FRAMEWORK_UPGRADE,
545 594 'fluentform/smartcode_group_' . $group,
546 595 'Use fluentform/smartcode_group_' . $group . ' instead of fluentform_smartcode_group_' . $group
@@ -556,15 +605,14 @@
556 605 $handlerValue = apply_filters_deprecated(
557 606 'fluentform_shortcode_parser_callback_' . $key,
558 607 [
559 608 '{' . $key . '}',
560 - static::getInstance()
609 + static::getInstance(),
561 610 ],
562 611 FLUENTFORM_FRAMEWORK_UPGRADE,
563 612 'fluentform/shortcode_parser_callback_' . $key,
564 613 'Use fluentform/shortcode_parser_callback_' . $key . ' instead of fluentform_shortcode_parser_callback_' . $key
565 614 );
566 -
567 615
568 616 $handlerValue = apply_filters('fluentform/shortcode_parser_callback_' . $key, $handlerValue, static::getInstance());
569 617
570 618 if ($handlerValue) {