wpForo 3.1.1 — first_name and last_name silently discarded on registration
Summary
In wpforo_do_hook_user_register(), the $wpfreg array is intersected with a four-key allowlist (user_login, user_email, user_pass1, user_pass2) before Members::update() is called. This silently strips any other fields posted via wpfreg[] — including first_name, last_name, and user_url, all of which are listed as save targets in Members::update_user_fields() at lines 948–957. The downstream save block then sees those keys as missing and skips the corresponding update_user_meta() calls. The registration completes successfully and the user is created, but with empty first_name and last_name in wp_usermeta.
This appears to be a security hardening (the inline comment mentions blocking attackers from pivoting userid via wpfreg[]) but the allowlist is overly restrictive — the userid is already forced on the next line, so widening the allowlist to include the standard WP user fields wpForo Core itself already saves doesn't reopen any attack surface.
Reproduction
- Install wpForo Core 3.1.1 + wpForo User Custom Fields (any recent version).
- Configure the registration form to include
first_nameandlast_name(default UCF setup does this). - Register a new user via the forum's
/sign-up/page, filling in First Name =FirstTestand Last Name =LastTest. - Check the new user's meta:
wp user meta list <id> | grep -E 'first_name|last_name'.
Expected: first_name = FirstTest, last_name = LastTest.
Actual: both empty.
Root cause
At includes/hooks.php ~line 2210:
$data['wpfreg'] = array_intersect_key( $wpfreg, array_flip( [
'user_login',
'user_email',
'user_pass1',
'user_pass2',
] ) );
The intersection drops first_name and last_name (and user_url) before WPF()->member->update() is called at line 2234, so Members::update_user_fields() never sees them.
Verified fix
Adding the three standard WP user fields back to the allowlist resolves the issue and re-saves them correctly via the existing update_user_meta() calls in update_user_fields():
$data['wpfreg'] = array_intersect_key( $wpfreg, array_flip( [
'user_login',
'user_email',
'user_pass1',
'user_pass2',
'first_name',
'last_name',
'user_url',
] ) );
The $data['wpfreg']['userid'] = (int) $userid; line immediately below this still forces the trusted userid, so the security goal of the original change is preserved — the allowlist just needs to permit the additional standard WP user fields that Members::update_user_fields() already handles.
Logged evidence
Instrumented Members::create() and Members::update_user_fields() with error_log() calls, registered a user, then captured the $_POST, $data at entry to create(), $user_fields after the wpfreg split, and $data at the save gate. The trace shows first_name / last_name present in $_POST['wpfreg'] and in the post-split $user_fields, then absent from $data by the time it reaches the save block — confirming the allowlist intersection as the strip point. Re-running the same test after adding the three keys to the allowlist resulted in both fields landing in wp_usermeta as expected.
Notes
- The strip is silent — no error, notice, or admin alert. The registration appears successful from every visible angle.
- Sites using wpForo User Custom Fields to add other custom fields on the registration form aren't affected by those fields being lost, because UCF custom fields arrive under
$_POST['data']rather than$_POST['wpfreg']and are handled separately. The strip only affects the standard WP user fields that arrive underwpfreg[].
Sorry for the late response.
This issue will be fixed in the next version of the wpForo plugin (no ETA yet).
Following up on this report - I'm afraid this is not fixed in 3.1.2, 3.1.3 or 3.1.4
I updated a production site to 3.1.4 on 24 July and the bug returned immediately. I've since compared the released packages from wordpress.org directly: includes/hooks.php is byte-identical across 3.1.1, 3.1.2, 3.1.3 and 3.1.4 - diff returns no output at all. wpforo_do_hook_user_register() still reads:
$data['wpfreg'] = array_intersect_key( $wpfreg, array_flip( [
'user_login',
'user_email',
'user_pass1',
'user_pass2',
] ) );
Searching the entire 3.1.1 -> 3.1.4 diff for first_name or last_name returns no matches anywhere in the plugin, so nothing in this code path has changed. I suspect the change simply didn't make it into the release.
To restate the mechanism: first_name, last_name and user_url are default profile fields with isDefault => 1, so Forms.php:1063 gives them varname 'wpfreg' and Forms.php:1121 renders them as name="wpfreg[first_name]". The allowlist above then strips them from $_POST['wpfreg'] before Members::update() runs, so Members::update_user_fields() (Members.php:952) never receives them and no update_user_meta() call happens. The saver is fine - the data is discarded upstream of it.
For completeness, in case this was closed against one of these: neither of the two changelog entries that look related actually touches this path.
- 3.1.2 "Fixed: Issue with User Custom Fields addon, the missing fields data" - that's the
$_POST['data']branch. UCF-managed fields haveisDefaultfalsy, soForms.php:1063gives themvarname'data'- a different branch of the same function. The UCF addon contains no reference tofirst_nameorlast_nameat all. - 3.1.3/3.1.4 "Security: Fixed mass assignment bypass in profile and registration forms" - that's the
custom_points/status/is_email_confirmedunsets added toMembers.php. Also unrelated to the name fields.
On the fix itself: I understand why the allowlist was added and I'm not suggesting reverting it. But first_name, last_name and user_url are not privileged fields, and the userid pivot is already neutralised on the very next line by $data['wpfreg']['userid'] = (int) $userid;. Adding these three back restores the documented behaviour without weakening that hardening:
$data['wpfreg'] = array_intersect_key( $wpfreg, array_flip( [
'user_login',
'user_email',
'user_pass1',
'user_pass2',
'first_name',
'last_name',
'user_url',
] ) );
Better still would be deriving the allowlist from the active registration form's field set rather than hardcoding it, so any enabled default field works and this can't regress again. Failing that, please consider adding a filter to the allowlist so site owners can work around it without editing plugin files - that's the part that has cost us most here.
Worth flagging the impact, because it is completely silent: registration succeeds, the names are lost with no error shown to anyone, and the values are unrecoverable afterwards. On our site 14 members registered in that state before it was spotted - and this is the second time, because the update reinstated the behaviour after we had patched it locally. We've now had to move our fix outside the plugin directory so your updates can't remove it again.
Happy to supply the diffs or any further detail if useful.
Thanks