PluginProbe
wpForo Forum / 3.1.6
wpForo Forum v3.1.6
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 1.4.11 1.4.12 1.4.13 All 138 releases
← All changes | classes/Members.php +83 -8 3.0.83.1.6 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
@@ -2263,15 +2310,43 @@
2263 2310 }
2264 2311
2265 2312 public function blog_comments( $userid, $user_email ) {
2266 2313 global $wpdb;
2267 - if( ! $userid || ! $user_email ) return 0;
2268 2314
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 - );
2315 + $userid = intval( $userid );
2316 +
2317 + // Match the author by WP user id and/or by email (for guest/imported comments).
2318 + // A comment made while logged in has both columns set, but COUNT(*) still counts
2319 + // that single row once, so the OR never double-counts.
2320 + $author_conditions = [];
2321 + $params = [];
2322 + if( $userid ) {
2323 + $author_conditions[] = 'c.user_id = %d';
2324 + $params[] = $userid;
2325 + }
2326 + if( $user_email && is_email( $user_email ) ) {
2327 + $author_conditions[] = 'c.comment_author_email = %s';
2328 + $params[] = $user_email;
2329 + }
2330 + if( empty( $author_conditions ) ) return 0;
2331 +
2332 + // WordPress stores every kind of comment in the same table (pingbacks, trackbacks,
2333 + // reviews, comments on pages/CPTs, etc.). Restrict to real, approved, public blog
2334 + // post comments only:
2335 + // - comment_approved = '1' -> approved only (not spam/trash/pending)
2336 + // - comment_type IN ('','comment') -> regular comments (not pingback/trackback/review)
2337 + // - p.post_type = 'post' -> blog posts, not pages/products/other CPTs
2338 + // - p.post_status = 'publish' -> public posts only
2339 + $sql = "SELECT COUNT(*)
2340 + FROM {$wpdb->comments} AS c
2341 + INNER JOIN {$wpdb->posts} AS p ON p.ID = c.comment_post_ID
2342 + WHERE ( " . implode( ' OR ', $author_conditions ) . " )
2343 + AND c.comment_approved = '1'
2344 + AND c.comment_type IN ( '', 'comment' )
2345 + AND p.post_type = 'post'
2346 + AND p.post_status = 'publish'";
2347 +
2348 + return (int) $wpdb->get_var( $wpdb->prepare( $sql, $params ) );
2274 2349 }
2275 2350
2276 2351 public function show_delete_form( $current_user, $userids ) {
2277 2352 if( empty( $current_user ) || empty( $userids ) ) return;
@@ -4162,9 +4237,9 @@
4162 4237 $sbj );
4163 4238 $msg = str_replace( [ '[blogname]', '[user_login]', '[login_link]', '[login_url]' ],
4164 4239 [ $blogname, $user->user_login, $login_link, $login_url ],
4165 4240 $msg );
4166 - wpforo_send_email( $user->user_email, $sbj, $msg );
4241 + wpforo_send_email( $user->user_email, $sbj, $msg, '', 'user_approved', (int) $user->ID );
4167 4242 }
4168 4243 }
4169 4244
4170 4245 public function get_activity_url( $filter = '', $boardid = null, $arg = null ) {
@@ -4308,9 +4383,9 @@
4308 4383 // Prefer buffered email if available
4309 4384 if( is_array( $this->front_delete_email_buffer ) ) {
4310 4385 $sbj = (string) wpfval( $this->front_delete_email_buffer, 'subject' );
4311 4386 $msg = (string) wpfval( $this->front_delete_email_buffer, 'message' );
4312 - wpforo_send_email( wpforo_setting( 'email', 'admin_emails' ), $sbj, $msg );
4387 + wpforo_send_email( wpforo_setting( 'email', 'admin_emails' ), $sbj, $msg, '', 'admin_notification' );
4313 4388 $this->front_delete_email_buffer = null; // clear buffer
4314 4389 }
4315 4390 }
4316 4391 }