PluginProbe
wpForo Forum / 3.2.1
wpForo Forum v3.2.1
3.2.1 3.2.0 3.1.7 3.1.6 3.1.5 3.1.4 3.1.2 3.1.1 3.1.0 3.0.9 3.0.8 3.0.7 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.10 All 141 releases
← All changes | classes/Members.php +62 -17 3.1.1 → 3.2.1 View file →
@@ -710,8 +710,17 @@
710 710
711 711 //Define $user
712 712 $user = $data[ $form ];
713 713
714 + // SECURITY: Strip admin-only profile fields from form input unless
715 + // user has edit members permission. Prevents mass assignment attacks
716 + // where users manipulate status, reputation, or email confirmation.
717 + if( ! WPF()->usergroup->can( 'em' ) ) {
718 + unset( $user['custom_points'] );
719 + unset( $user['status'] );
720 + unset( $user['is_email_confirmed'] );
721 + }
722 +
714 723 //Define $userid
715 724 $userid = intval( $data[ $form ]['userid'] );
716 725
717 726 //Check profile editor permissions
@@ -892,8 +901,10 @@
892 901 $this->upload_avatar( $userid );
893 902 }
894 903
895 904 //Update Custom Fields
905 + // $custom_fields is still the raw $_POST['data']. The same values were merged into $user, where validate() has since dropped every field the current user may not edit and sanitize() has cleaned the rest, so read them back from $user: a key that is still there is one this user was allowed to submit, and its value is the sanitized one.
906 + $custom_fields = array_intersect_key( $user, $custom_fields );
896 907 if( ! empty( $custom_fields ) && ( in_array( 'full', $type ) || in_array( 'custom_fields', $type ) ) ) {
897 908 $result_fields = $this->update_custom_fields( $userid, $custom_fields, false );
898 909 }
899 910
@@ -997,8 +1008,17 @@
997 1008 $result_profile = true;
998 1009
999 1010 if( $check_permissions ) {
1000 1011 WPF()->perm->can_edit_user( $userid );
1012 +
1013 + // SECURITY: Strip admin-only fields from user input unless user has
1014 + // edit members permission. Prevents mass assignment attacks where
1015 + // users manipulate their own status, reputation, or email confirmation.
1016 + if( ! WPF()->usergroup->can( 'em' ) ) {
1017 + unset( $data['custom_points'] );
1018 + unset( $data['status'] );
1019 + unset( $data['is_email_confirmed'] );
1020 + }
1001 1021 }
1002 1022
1003 1023 $member = $this->encode( $data );
1004 1024
@@ -1648,16 +1668,17 @@
1648 1668 } else {
1649 1669 $wheres[] = "`" . esc_sql( $field ) . "` LIKE '%" . esc_sql( $needle ) . "%'";
1650 1670 }
1651 1671 } else {
1652 - $needle = preg_quote( preg_quote( $needle ) );
1672 + // Local var: $needle must stay raw for the remaining fields of this loop
1673 + $n = wpforo_json_regexp_needle( $needle );
1653 1674 if( in_array( $f['type'], [ 'text', 'search', 'textarea' ], true ) ) {
1654 1675 $wheres[] = "`fields` REGEXP '[{,]\"" . $field . "\":(\\\[[^\\\[]*)?\"[^\"]*" . esc_sql(
1655 - $needle
1676 + $n
1656 1677 ) . "[^\"]*\"'";
1657 1678 } else {
1658 1679 $wheres[] = "`fields` REGEXP '[{,]\"" . $field . "\":(\\\[[^\\\[]*)?\"" . esc_sql(
1659 - $needle
1680 + $n
1660 1681 ) . "\"'";
1661 1682 }
1662 1683 }
1663 1684 }
@@ -1700,31 +1721,27 @@
1700 1721 }
1701 1722 } else {
1702 1723 if( in_array( $f['type'], [ 'text', 'search', 'textarea' ], true ) ) {
1703 1724 if( is_scalar( $needle ) ) {
1704 - $needle = preg_quote( preg_quote( wpforo_encode( $needle ) ) );
1705 1725 $wheres[] = "`fields` REGEXP '[{,]\"" . $field . "\":(\\\[[^\\\[]*)?\"[^\"]*" . esc_sql(
1706 - $needle
1726 + wpforo_json_regexp_needle( wpforo_encode( $needle ) )
1707 1727 ) . "[^\"]*\"'";
1708 1728 } elseif( is_array( $needle ) ) {
1709 1729 foreach( $needle as $n ) {
1710 - $n = preg_quote( preg_quote( wpforo_encode( $n ) ) );
1711 1730 $wheres[] = "`fields` REGEXP '[{,]\"" . $field . "\":(\\\[[^\\\[]*)?\"[^\"]*" . esc_sql(
1712 - $n
1731 + wpforo_json_regexp_needle( wpforo_encode( $n ) )
1713 1732 ) . "[^\"]*\"'";
1714 1733 }
1715 1734 }
1716 1735 } else {
1717 1736 if( is_scalar( $needle ) ) {
1718 - $needle = preg_quote( preg_quote( wpforo_encode( $needle ) ) );
1719 1737 $wheres[] = "`fields` REGEXP '[{,]\"" . $field . "\":(\\\[[^\\\[]*)?\"" . esc_sql(
1720 - $needle
1738 + wpforo_json_regexp_needle( wpforo_encode( $needle ) )
1721 1739 ) . "\"'";
1722 1740 } elseif( is_array( $needle ) ) {
1723 1741 foreach( $needle as $n ) {
1724 - $n = preg_quote( preg_quote( wpforo_encode( $n ) ) );
1725 1742 $wheres[] = "`fields` REGEXP '[{,]\"" . $field . "\":(\\\[[^\\\[]*)?\"" . esc_sql(
1726 - $n
1743 + wpforo_json_regexp_needle( wpforo_encode( $n ) )
1727 1744 ) . "\"'";
1728 1745 }
1729 1746 }
1730 1747 }
@@ -2290,15 +2307,43 @@
2290 2307 }
2291 2308
2292 2309 public function blog_comments( $userid, $user_email ) {
2293 2310 global $wpdb;
2294 - if( ! $userid || ! $user_email ) return 0;
2295 2311
2296 - return (int) $wpdb->get_var(
2297 - "SELECT COUNT(*) FROM " . $wpdb->comments . " WHERE `user_id` = " . intval(
2298 - $userid
2299 - ) . " OR `comment_author_email` = '" . esc_sql( $user_email ) . "'"
2300 - );
2312 + $userid = intval( $userid );
2313 +
2314 + // Match the author by WP user id and/or by email (for guest/imported comments).
2315 + // A comment made while logged in has both columns set, but COUNT(*) still counts
2316 + // that single row once, so the OR never double-counts.
2317 + $author_conditions = [];
2318 + $params = [];
2319 + if( $userid ) {
2320 + $author_conditions[] = 'c.user_id = %d';
2321 + $params[] = $userid;
2322 + }
2323 + if( $user_email && is_email( $user_email ) ) {
2324 + $author_conditions[] = 'c.comment_author_email = %s';
2325 + $params[] = $user_email;
2326 + }
2327 + if( empty( $author_conditions ) ) return 0;
2328 +
2329 + // WordPress stores every kind of comment in the same table (pingbacks, trackbacks,
2330 + // reviews, comments on pages/CPTs, etc.). Restrict to real, approved, public blog
2331 + // post comments only:
2332 + // - comment_approved = '1' -> approved only (not spam/trash/pending)
2333 + // - comment_type IN ('','comment') -> regular comments (not pingback/trackback/review)
2334 + // - p.post_type = 'post' -> blog posts, not pages/products/other CPTs
2335 + // - p.post_status = 'publish' -> public posts only
2336 + $sql = "SELECT COUNT(*)
2337 + FROM {$wpdb->comments} AS c
2338 + INNER JOIN {$wpdb->posts} AS p ON p.ID = c.comment_post_ID
2339 + WHERE ( " . implode( ' OR ', $author_conditions ) . " )
2340 + AND c.comment_approved = '1'
2341 + AND c.comment_type IN ( '', 'comment' )
2342 + AND p.post_type = 'post'
2343 + AND p.post_status = 'publish'";
2344 +
2345 + return (int) $wpdb->get_var( $wpdb->prepare( $sql, $params ) );
2301 2346 }
2302 2347
2303 2348 public function show_delete_form( $current_user, $userids ) {
2304 2349 if( empty( $current_user ) || empty( $userids ) ) return;