Hi guys, we're getting urgent security notices from our host and we can't change to 3.x. Help and thanks!

You've already been asnwered here: https://wordpress.org/support/topic/wpforo-2-4-17-vulnerability/
Hi @hoop-ball,
Could you please explain why you cannot switch to wpForo 3.x? It has been designed to have near to zero impact on update. Even the 2.x theme has not been changed and you can continue to use it after update by simple switching from 2026 to 2022 in wpForo > Themes admin page. There is no any blocker or hard change in 3.x that will bring you problems.
Honestly we just didn't have the manpower/resources to vet it. We bumped it up the priority chain and got there, finding what you found.
fixed 😉
1. **`wpforo/includes/functions.php `** is the function `wpforo_fix_upload_dir()` (~line 2568). The main fix: previously, if the path did not match the wpforo download template, the function returned the path unchanged, now it returns `false`, plus a check has been added via `realpath()` that the resulting path is actually inside the download directory.
function wpforo_fix_upload_dir( $upload_dir ) {
$folders = wpforo_ram_get( 'wpforo_get_upload_dir_folders' );
$folders = array_map( 'preg_quote', $folders );
if( $folders && preg_match( '#[/\\\]wpforo(?:_\d+)?[/\\\](?:' . implode( '|', $folders ) . ')[/\\\].+?$#iu', (string) $upload_dir, $match ) ) {
$upload_dir = wpforo_fix_dir_sep( WPF()->folders['wp_upload']['dir'] . $match[0] );
$upload_dir = urldecode( (string) $upload_dir );
// Defense-in-depth: confirm the fully resolved path is really located inside the
// wpforo upload directory. This blocks traversal sequences (e.g. "../../")
// that could otherwise slip through the regex match above.
$real_base = realpath( WPF()->folders['upload']['dir'] );
$real_path = realpath( $upload_dir );
if( ! $real_base || ! $real_path ) {
return false;
}
$real_base = wpforo_fix_dir_sep( $real_base );
$real_path = wpforo_fix_dir_sep( $real_path );
if( strpos( $real_path . '/', $real_base . '/' ) !== 0 ) {
return false;
}
return $upload_dir;
}
// The supplied value did not match a legitimate wpforo upload path.
// Previously this function returned the value unchanged, which allowed a
// crafted/attacker-controlled path (e.g. an absolute path to wp-config.php)
// to be passed straight through to unlink()/wp_delete_file() by callers such
// as Actions::ucf_file_delete() and PostMeta::delete_file(), resulting in
// arbitrary file deletion (CVE-2026-6248, CVE-2026-5809). Returning false
// forces callers to treat this as "no valid file" instead of a real path.
return false;
}
2. **`wpforo/classes/Actions.php `** — method `ucf_file_delete()` (~line 341). Added a rights check for `WPF()->perm->can_edit_user($userid)` and checking that `$file` is not `false' before calling `unlink()'.
public function ucf_file_delete() {
$userid = 0;
if( wpfval( WPF()->GET, 'foro_f' ) && wpfval( WPF()->GET, 'foro_u' ) && wpfval( WPF()->GET, 'foro_n' ) ) {
if( wp_verify_nonce( WPF()->GET['foro_n'], 'wpforo_delete_profile_field' ) ) {
$userid = intval( WPF()->GET['foro_u'] );
// Make sure the requester is actually allowed to edit this profile
// (the nonce alone isn't bound to $userid, so this must be checked explicitly).
WPF()->perm->can_edit_user( $userid );
$field = sanitize_title( WPF()->GET['foro_f'] );
if( $file = WPF()->member->get_custom_field( $userid, $field ) ) {
// wpforo_fix_upload_dir() returns false unless $file genuinely resolves
// inside the wpforo upload directory. Never unlink() anything else.
$file = wpforo_fix_upload_dir( $file );
$result = WPF()->member->update_custom_field( $userid, $field, '' );
if( $result ) {
if( $file && file_exists( $file ) ) @unlink( $file );
WPF()->phrase->clear_cache();
WPF()->notice->add( 'Deleted Successfully!', 'success' );
} else {
WPF()->notice->clear();
WPF()->notice->add( 'Sorry, this file cannot be deleted', 'error' );
}
}
}
}
wp_safe_redirect( $userid ? WPF()->member->get_profile_url( $userid, 'account' ) : wpforo_home_url() );
exit();
}
3. **`wpforo/classes/PostMeta.php `** is the `delete_file()` method (~line 402). `wp_delete_file()` is now called only if `wpforo_fix_upload_dir()` returned a valid path.
private function delete_file( $postid, $metakey ) {
$postid = wpforo_bigintval( $postid );
if( $postid && $metakey ) {
if( $postmeta = $this->get_postmeta( $postid, $metakey ) ) {
foreach( $postmeta as $file ) {
$mediaid = (int) wpfval( $file, 'mediaid' );
$fileurl = (string) wpfval( $file, 'fileurl' );
$filedir = wpforo_fix_upload_dir( $fileurl );
if( $mediaid ) wp_delete_attachment( $mediaid );
// Security: Only delete files within wpforo upload directory
if( $filedir ) {
$realpath = realpath( $filedir );
$upload_base = realpath( WPF()->folders['wp_upload']['dir'] );
if( $realpath && $upload_base && strpos( $realpath, $upload_base . DIRECTORY_SEPARATOR . 'wpforo' ) === 0 ) {
wp_delete_file( $filedir );
}
}
}
$this->delete( [ 'postid' => $postid, 'metakey' => $metakey ] );
}
}
}
you can manually replace some code from files v3 to v2