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 +91 -19 3.0.9 → 3.2.1 View file →
@@ -640,8 +640,26 @@
640 640 */
641 641 public function update( $data, $type = 'full', $check_permissions = true ) {
642 642 $type = (array) $type;
643 643
644 + // SECURITY: when permission checks are skipped, strip reserved wp_users
645 + // column names from custom-field input to block mass assignment.
646 + if( ! $check_permissions ) {
647 + if( isset( $data['data'] ) && is_array( $data['data'] ) ) {
648 + foreach( [
649 + 'user_email',
650 + 'user_login',
651 + 'user_pass',
652 + 'user_pass1',
653 + 'user_pass2',
654 + 'userid',
655 + 'ID',
656 + ] as $reserved ) {
657 + unset( $data['data'][ $reserved ] );
658 + }
659 + }
660 + }
661 +
644 662 switch( WPF()->current_object['template'] ) {
645 663 case 'register':
646 664 $form = 'wpfreg';
647 665 $form_fields = $this->get_register_fields();
@@ -674,8 +692,17 @@
674 692 ) ) {
675 693 $data[ $form ]['userid'] = $data['userid'];
676 694 }
677 695
696 + // SECURITY: when permission checks are skipped, force the form userid
697 + // to the trusted caller-supplied $data['userid'] (overrides user input).
698 + if( ! $check_permissions && wpfval( $data, 'userid' ) ) {
699 + if( ! isset( $data[ $form ] ) || ! is_array( $data[ $form ] ) ) {
700 + $data[ $form ] = [];
701 + }
702 + $data[ $form ]['userid'] = (int) $data['userid'];
703 + }
704 +
678 705 if( wpfval( $data, $form, 'userid' ) ) {
679 706 $result_user = true;
680 707 $result_fields = true;
681 708 $result_profile = true;
@@ -683,8 +710,17 @@
683 710
684 711 //Define $user
685 712 $user = $data[ $form ];
686 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 +
687 723 //Define $userid
688 724 $userid = intval( $data[ $form ]['userid'] );
689 725
690 726 //Check profile editor permissions
@@ -865,8 +901,10 @@
865 901 $this->upload_avatar( $userid );
866 902 }
867 903
868 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 );
869 907 if( ! empty( $custom_fields ) && ( in_array( 'full', $type ) || in_array( 'custom_fields', $type ) ) ) {
870 908 $result_fields = $this->update_custom_fields( $userid, $custom_fields, false );
871 909 }
872 910
@@ -970,8 +1008,17 @@
970 1008 $result_profile = true;
971 1009
972 1010 if( $check_permissions ) {
973 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 + }
974 1021 }
975 1022
976 1023 $member = $this->encode( $data );
977 1024
@@ -1621,16 +1668,17 @@
1621 1668 } else {
1622 1669 $wheres[] = "`" . esc_sql( $field ) . "` LIKE '%" . esc_sql( $needle ) . "%'";
1623 1670 }
1624 1671 } else {
1625 - $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 );
1626 1674 if( in_array( $f['type'], [ 'text', 'search', 'textarea' ], true ) ) {
1627 1675 $wheres[] = "`fields` REGEXP '[{,]\"" . $field . "\":(\\\[[^\\\[]*)?\"[^\"]*" . esc_sql(
1628 - $needle
1676 + $n
1629 1677 ) . "[^\"]*\"'";
1630 1678 } else {
1631 1679 $wheres[] = "`fields` REGEXP '[{,]\"" . $field . "\":(\\\[[^\\\[]*)?\"" . esc_sql(
1632 - $needle
1680 + $n
1633 1681 ) . "\"'";
1634 1682 }
1635 1683 }
1636 1684 }
@@ -1673,31 +1721,27 @@
1673 1721 }
1674 1722 } else {
1675 1723 if( in_array( $f['type'], [ 'text', 'search', 'textarea' ], true ) ) {
1676 1724 if( is_scalar( $needle ) ) {
1677 - $needle = preg_quote( preg_quote( wpforo_encode( $needle ) ) );
1678 1725 $wheres[] = "`fields` REGEXP '[{,]\"" . $field . "\":(\\\[[^\\\[]*)?\"[^\"]*" . esc_sql(
1679 - $needle
1726 + wpforo_json_regexp_needle( wpforo_encode( $needle ) )
1680 1727 ) . "[^\"]*\"'";
1681 1728 } elseif( is_array( $needle ) ) {
1682 1729 foreach( $needle as $n ) {
1683 - $n = preg_quote( preg_quote( wpforo_encode( $n ) ) );
1684 1730 $wheres[] = "`fields` REGEXP '[{,]\"" . $field . "\":(\\\[[^\\\[]*)?\"[^\"]*" . esc_sql(
1685 - $n
1731 + wpforo_json_regexp_needle( wpforo_encode( $n ) )
1686 1732 ) . "[^\"]*\"'";
1687 1733 }
1688 1734 }
1689 1735 } else {
1690 1736 if( is_scalar( $needle ) ) {
1691 - $needle = preg_quote( preg_quote( wpforo_encode( $needle ) ) );
1692 1737 $wheres[] = "`fields` REGEXP '[{,]\"" . $field . "\":(\\\[[^\\\[]*)?\"" . esc_sql(
1693 - $needle
1738 + wpforo_json_regexp_needle( wpforo_encode( $needle ) )
1694 1739 ) . "\"'";
1695 1740 } elseif( is_array( $needle ) ) {
1696 1741 foreach( $needle as $n ) {
1697 - $n = preg_quote( preg_quote( wpforo_encode( $n ) ) );
1698 1742 $wheres[] = "`fields` REGEXP '[{,]\"" . $field . "\":(\\\[[^\\\[]*)?\"" . esc_sql(
1699 - $n
1743 + wpforo_json_regexp_needle( wpforo_encode( $n ) )
1700 1744 ) . "\"'";
1701 1745 }
1702 1746 }
1703 1747 }
@@ -2263,15 +2307,43 @@
2263 2307 }
2264 2308
2265 2309 public function blog_comments( $userid, $user_email ) {
2266 2310 global $wpdb;
2267 - if( ! $userid || ! $user_email ) return 0;
2268 2311
2269 - return (int) $wpdb->get_var(
2270 - "SELECT COUNT(*) FROM " . $wpdb->comments . " WHERE `user_id` = " . intval(
2271 - $userid
2272 - ) . " OR `comment_author_email` = '" . esc_sql( $user_email ) . "'"
2273 - );
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 ) );
2274 2346 }
2275 2347
2276 2348 public function show_delete_form( $current_user, $userids ) {
2277 2349 if( empty( $current_user ) || empty( $userids ) ) return;
@@ -4162,9 +4234,9 @@
4162 4234 $sbj );
4163 4235 $msg = str_replace( [ '[blogname]', '[user_login]', '[login_link]', '[login_url]' ],
4164 4236 [ $blogname, $user->user_login, $login_link, $login_url ],
4165 4237 $msg );
4166 - wpforo_send_email( $user->user_email, $sbj, $msg );
4238 + wpforo_send_email( $user->user_email, $sbj, $msg, '', 'user_approved', (int) $user->ID );
4167 4239 }
4168 4240 }
4169 4241
4170 4242 public function get_activity_url( $filter = '', $boardid = null, $arg = null ) {
@@ -4308,9 +4380,9 @@
4308 4380 // Prefer buffered email if available
4309 4381 if( is_array( $this->front_delete_email_buffer ) ) {
4310 4382 $sbj = (string) wpfval( $this->front_delete_email_buffer, 'subject' );
4311 4383 $msg = (string) wpfval( $this->front_delete_email_buffer, 'message' );
4312 - wpforo_send_email( wpforo_setting( 'email', 'admin_emails' ), $sbj, $msg );
4384 + wpforo_send_email( wpforo_setting( 'email', 'admin_emails' ), $sbj, $msg, '', 'admin_notification' );
4313 4385 $this->front_delete_email_buffer = null; // clear buffer
4314 4386 }
4315 4387 }
4316 4388 }