CVE-2025-6986(FileBird SQL注入漏洞)

阿里云漏洞库CVE详情:

wordfence漏洞报告:

知识库导入

FileBird 是一款专为 WordPress 设计的媒体库管理插件,通过文件夹分类和拖放操作帮助用户高效整理海量媒体文件(如图片、视频、音频等)。

核心功能:​

✅文件夹分类管理 | ✅拖放式操作 | ✅与页面构建器深度集成 | ✅ 权限管理与安全性 | ✅多语言与兼容性

一、漏洞概述

1.1漏洞背景

由安全研究者Kenneth Billones报告,通过Wordfence漏洞情报平台公开,属于WordPress插件FileBird的高危SQL注入漏洞(CVE-2025-6986)。  

1.2漏洞基本信息

属性

内容

漏洞名称

FileBird - WordPress媒体库收件箱和文件管理器插件SQL注入漏洞

CVE 编号

CVE-2025-6986

漏洞等级

中危(CVSS 评分 6.5)

影响组件

文件鸟 - WordPress媒体库文件夹和文件管理器<= 6.4.8

公开时间

2025年8月6日

官方补丁

已发布(版本 6.4.9)

攻击复杂度

影响范围

窃取数据库敏感信息

二、漏洞原理与技术细节

在6.4.8(含)之前的所有版本中,由于用户提供的参数的逸出不充分以及现有SQL查询的充分准备,用于WordPress的FileBird - WordPress媒体库收件箱和文件管理器插件都容易受到通过“search”参数进行SQL注入的攻击。这使得具有授权级别及以上访问权限的经过验证的攻击者可以将额外的SQL查询添加到现有的查询中,这些查询可用于从数据库中提取敏感信息。

漏洞触发点位于插件的搜索功能,具体涉及用户输入的search参数未严格过滤,导致SQL注入。

1.漏洞触发代码定位

插件在处理媒体库文件/文件夹搜索时,直接将用户输入的search参数拼接到SQL查询语句中,未使用参数化查询(预处理语句)或充分转义。  

        public function getFolders( \WP_REST_Request $request ) {

                $order   = sanitize_key( $request->get_param( 'order' ) );

                $orderby = sanitize_key( $request->get_param( 'orderby' ) );

                $lang    = sanitize_key( $request->get_param( 'language' ) );

                $search  = sanitize_text_field( $request->get_param( 'search' ) );

   

                $tree = ( new Tree( $orderby, $order, $search ) )->get();

   

                return rest_ensure_response(

                        array(

                                'tree'                => $tree,

                                'allAttachmentsCount' => Tree::getCount( -1, $lang ),

                                'attachmentsCount'    => FolderModel::countAttachments( $lang ),

                        )

                );

        }

  getFolders方法接收search参数后,直接传递给Tree类(new Tree($orderby, $order, $search)),而Tree类(可能位于Folder.php模型)在构建SQL查询时,直接拼接search参数到LIKE子句中

具体来说,旧版本代码可能存在类似以下漏洞代码:  

// 旧版本FolderController.php

public function getFolders(\WP_REST_Request $request) {

    $search = sanitize_text_field($request->get_param('search')); // 仅简单过滤

    $tree = (new Tree($orderby, $order, $search))->get(); // 直接传递$search到SQL构建

    return rest_ensure_response([...]);

}


// 旧版本Folder.php

$conditions[] = "name LIKE '%" . $wpdb->esc_like($search) . "%'";

// 问题:仅用$wpdb->esc_like()转义,未使用预处理语句,仍可注入

此处$search参数被直接拼接到SQL的LIKE子句中,尽管使用了$wpdb->esc_like()转义,但未通过预处理语句($wpdb->prepare())完全隔离用户输入,仍存在注入风险(例如,若$search包含' OR 1=1 --等恶意内容,可绕过转义)。

2. 攻击利用逻辑

攻击者通过媒体库搜索框提交恶意search参数(如' UNION SELECT user_login, user_pass FROM wp_users --),拼接后的SQL变为:  

SELECT * FROM wp_filebird_folders WHERE ... AND name LIKE '%' UNION SELECT user_login, user_pass FROM wp_users -- %'

该查询会返回wp_users表中的用户账号和密码哈希,导致敏感数据泄露。  

3. 漏洞成因(代码层面)

• 未使用参数化查询:插件未通过WordPress的$wpdb->prepare()方法预处理用户输入,而是直接拼接$search参数到SQL语句中。  

• 输入过滤不足:尽管使用了sanitize_text_field,但该函数仅过滤部分危险字符(如HTML标签),无法阻止SQL注入(例如,' OR 1=1 --仍会被拼接)。  

三、漏洞利用方式

• 攻击路径:通过媒体库搜索功能(/wp-admin/admin-ajax.php?action=filebird-folders-get等端点)提交恶意search参数。  

• 必要条件:攻击者需拥有Author及以上权限(可通过合法账号登录,或通过其他漏洞获取低权限账号)。  

• 攻击载荷示例:  

POST /wp-admin/admin-ajax.php HTTP/1.1

Action: filebird-folders-get

search: ' UNION SELECT user_login, user_email, user_pass FROM wp_users WHERE user_role = 'subscriber' --

该载荷会触发SQL注入,返回订阅者账号的登录名、邮箱和密码哈希。  

四、漏洞文件源码FolderController.php:

<?php


namespace FileBird\Controller;


use FileBird\Model\Folder as FolderModel;

use FileBird\Model\UserSettingModel;

use FileBird\Classes\Tree;


defined( 'ABSPATH' ) || exit;

class FolderController extends Controller {

        public function getFolders( \WP_REST_Request $request ) {

                $order   = sanitize_key( $request->get_param( 'order' ) );

                $orderby = sanitize_key( $request->get_param( 'orderby' ) );

                $lang    = sanitize_key( $request->get_param( 'language' ) );

                $search  = sanitize_text_field( $request->get_param( 'search' ) );


                $tree = ( new Tree( $orderby, $order, $search ) )->get();


                return rest_ensure_response(

                        array(

                                'tree'                => $tree,

                                'allAttachmentsCount' => Tree::getCount( -1, $lang ),

                                'attachmentsCount'    => FolderModel::countAttachments( $lang ),

                        )

                );

        }


        public function setFolderCounter( \WP_REST_Request $request ) {

                $type = sanitize_key( $request->get_param( 'type' ) );

                $lang = sanitize_key( $request->get_param( 'language' ) );


                // add_filter(

        //     'fbv_counter_type',

        //     function() use ( $type ) {

                //              return $type;

                //      }

        // );


                UserSettingModel::getInstance()->setFolderCounterType( $type );


                return rest_ensure_response( FolderModel::countAttachments( $lang ) );

        }


        public function updateFolderColor( \WP_REST_Request $request ) {

                $id    = sanitize_key( $request->get_param( 'folderId' ) );

                $color = sanitize_hex_color( $request->get_param( 'color' ) );


                $option        = get_option( 'fbv_folder_colors', array() );

                $option[ $id ] = $color;


                update_option( 'fbv_folder_colors', $option );


                return rest_ensure_response( true );

        }


        public function createFolder( \WP_REST_Request $request ) {

                $name   = $request->get_param( 'title' );

                $parent = $request->get_param( 'parent' );

                $name   = isset( $name ) ? sanitize_text_field( wp_unslash( $name ) ) : '';

                $parent = isset( $parent ) ? sanitize_text_field( $parent ) : '';

                if ( $name != '' && $parent != '' ) {

                        $insert = FolderModel::newOrGet( $name, $parent, false );

                        if ( $insert !== false ) {

                                return rest_ensure_response( $insert );

                        } else {

                                return new \WP_Error( 'folder_name_exist', __( 'A folder with this name already exists. Please choose another one.', 'filebird' ) );

                        }

                } else {

                        return new \WP_Error( 'validation_failed', __( 'Validation failed', 'filebird' ) );

                }

        }


        public function updateFolder( \WP_REST_Request $request ) {

                $id     = $request->get_param( 'id' );

                $parent = $request->get_param( 'parent' );

                $name   = $request->get_param( 'title' );


                $id     = isset( $id ) ? sanitize_text_field( $id ) : '';

                $parent = isset( $parent ) ? intval( sanitize_text_field( $parent ) ) : '';

                $name   = isset( $name ) ? sanitize_text_field( wp_unslash( $name ) ) : '';


                $current_user_id = get_current_user_id();

                $folder_per_user = get_option( 'njt_fbv_folder_per_user', '0' ) === '1';


                if ( is_numeric( $id ) && is_numeric( $parent ) && $name != '' && FolderModel::verifyAuthor( $id, $current_user_id, $folder_per_user ) ) {

                        $update = FolderModel::updateFolderName( $name, $parent, $id );

                        if ( true === $update ) {

                                return rest_ensure_response( $update );

                        } else {

                                return new \WP_Error( 'folder_name_exist', __( 'A folder with this name already exists. Please choose another one.', 'filebird' ) );

                        }

                }

                return new \WP_Error( 'validation_failed', __( 'Validation failed', 'filebird' ) );

        }


        public function updateFolderOrder( \WP_REST_Request $request ) {

                global $wpdb;


                $data = array(

                        'dropPosition' => $request->get_param( 'dropPosition' ),

                        'dropNodeId'   => $request->get_param( 'dropNodeId' ),

                        'dragNodeId'   => $request->get_param( 'dragNodeId' ),

                        'toParentId'   => $request->get_param( 'toParentId' ),

                );


                $lang = sanitize_key( $request->get_param( 'language' ) );


                foreach ( $data as $param ) {

                        if ( strlen( $param ) === 0 || ! is_numeric( $param ) ) {

                                return new \WP_Error( 'invalid_params', __( 'Invalid params', 'filebird' ), array( 'status' => 400 ) );

                        }

                }


                $old_node = $wpdb->get_row(

                        $wpdb->prepare(

                                "SELECT id, parent, ord FROM {$wpdb->prefix}fbv WHERE `id` = %d AND `created_by` = %d",

                                $data['dropNodeId'],

                                apply_filters( 'fbv_folder_created_by', 0 )

                        ),

                        ARRAY_A

                );


                // Drop folder inside another folder

                if ( 0 === $data['dropPosition'] ) {

                        $children = $wpdb->get_results(

                                $wpdb->prepare(

                                        "SELECT id, ord FROM {$wpdb->prefix}fbv WHERE (parent = %d and created_by = %d and id != %d) ORDER BY ord+0 ASC",

                                        $data['dropNodeId'],

                                        apply_filters( 'fbv_folder_created_by', 0 ),

                                        $data['dragNodeId']

                                )

                        );


                        array_unshift(

                $children,

                (object) array(

                                        'id'  => $data['dragNodeId'],

                                        'ord' => 0,

                                )

                );


                        foreach ( $children as $key => $child ) {

                                $wpdb->update(

                                        "{$wpdb->prefix}fbv",

                                        array(

                                                'ord'    => $key + 1,

                                                'parent' => $data['dropNodeId'],

                                        ),

                                        array(

                                                'id' => $child->id,

                                        ),

                                        array( '%d', '%d' ),

                                        array( '%d' )

                                );

                        }

                }


                if ( 1 === $data['dropPosition'] ) {

                        $children = $wpdb->get_results(

                                $wpdb->prepare(

                                        "SELECT id, ord FROM {$wpdb->prefix}fbv WHERE (parent = %d and created_by = %d and id != %d) ORDER BY ord+0 ASC",

                                        $data['toParentId'],

                                        apply_filters( 'fbv_folder_created_by', 0 ),

                                        $data['dragNodeId']

                                )

                        );


                        $ord = 0;


                        foreach ( $children as $child ) {

                                $wpdb->update(

                                        "{$wpdb->prefix}fbv",

                                        array(

                                                'ord'    => $ord++,

                                                'parent' => $data['toParentId'],

                                        ),

                                        array(

                                                'id' => $child->id,

                                        ),

                                        array( '%d', '%d' ),

                                        array( '%d' )

                                );

                                if ( intval( $child->id ) === $data['dropNodeId'] ) {

                                        $wpdb->update(

                                                "{$wpdb->prefix}fbv",

                                                array(

                                                        'ord'    => $ord++,

                                                        'parent' => $data['toParentId'],

                                                ),

                                                array(

                                                        'id' => $data['dragNodeId'],

                                                ),

                                                array( '%d', '%d' ),

                                                array( '%d' )

                                        );

                                }

                        }

                }


                if ( -1 === $data['dropPosition'] ) {

                        $children = $wpdb->get_results(

                                $wpdb->prepare(

                                        "SELECT id, ord FROM {$wpdb->prefix}fbv WHERE (parent = %d and created_by = %d and id != %d) ORDER BY ord+0 ASC",

                                        $data['toParentId'],

                                        apply_filters( 'fbv_folder_created_by', 0 ),

                                        $data['dragNodeId']

                                )

                        );


                        array_unshift(

                $children,

                (object) array(

                                        'id'  => $data['dragNodeId'],

                                        'ord' => 0,

                                )

                );


                        foreach ( $children as $key => $child ) {

                                $wpdb->update(

                                        "{$wpdb->prefix}fbv",

                                        array(

                                                'ord'    => $key + 1,

                                                'parent' => $data['toParentId'],

                                        ),

                                        array(

                                                'id' => $child->id,

                                        ),

                                        array( '%d', '%d' ),

                                        array( '%d' )

                                );

                        }

                }


                if ( ! is_null( $old_node ) && ( $old_node['parent'] != $data['toParentId'] ) ) {

                        do_action( 'fbv_folder_parent_updated', $data['dragNodeId'], $data['toParentId'] );

                }


                if ( UserSettingModel::getInstance()->get( 'FOLDER_COUNTER_TYPE' ) === 'counter_file_in_folder_and_sub' ) {

                        return rest_ensure_response( FolderModel::countAttachments( $lang ) );

                }


                return rest_ensure_response( true );

        }


        public function deleteFolder( \WP_REST_Request $request ) {

                $ids  = $request->get_param( 'ids' );

                $lang = sanitize_key( $request->get_param( 'language' ) );


                if ( empty( $ids ) ) {

                        return new \WP_Error( 'delete_failed', __( 'Can\'t delete folder, please try again later', 'filebird' ) );


                }


                $ids = array_map( 'intval', $ids );


                $current_user_id = get_current_user_id();

                $folder_per_user = get_option( 'njt_fbv_folder_per_user', '0' ) === '1';

                foreach ( $ids as $k => $id ) {

                        if ( ! FolderModel::verifyAuthor( $id, $current_user_id, $folder_per_user ) ) {

                                unset( $ids[ $k ] );

                        }

                }

                $folderCounter = FolderModel::delete( $ids, $lang );


                return rest_ensure_response( $folderCounter );

        }


        public function assignFolder( \WP_REST_Request $request ) {

                $folderId = $request->get_param( 'folderId' );

                $ids      = $request->get_param( 'ids' );

                $lang     = sanitize_key( $request->get_param( 'language' ) );


                if ( empty( $folderId ) && ! is_numeric( $folderId ) && empty( $ids ) ) {

                        return new \WP_Error( 'validation_failed', __( 'Validation failed', 'filebird' ) );

                }


                $ids = array_map( 'intval', apply_filters( 'fbv_ids_assigned_to_folder', $ids ) );


                $current_user_id = get_current_user_id();

                $user_has_own_folder = get_option( 'njt_fbv_folder_per_user', '0' ) === '1';

                if( ! FolderModel::verifyAuthor( $folderId, $current_user_id, $user_has_own_folder ) ) {

                        return new \WP_Error( 'validation_failed', __( 'Permisson Denied.', 'filebird' ) );

                }

                $folderCounter = FolderModel::assignFolder( $folderId, $ids, $lang );


                return rest_ensure_response( $folderCounter );

        }


        public function gutenbergGetFolder() {

                $_folders = Tree::getFolders( null, null, true );

                $folders  = array(

                        array(

                                'value'    => 0,

                                'label'    => __( 'Please choose folder', 'filebird' ),

                                'disabled' => true,

                        ),

                );

                foreach ( $_folders as $k => $v ) {

                        $folders[] = array(

                                'value' => $v['id'],

                                'label' => $v['text'],

                        );

                }


                wp_send_json_success( $folders );

        }

}

、修复方案

在代码版本管理页面(plugins.trac.wordpress.org)显示,修复版本6.4.9已针对性修复此漏洞,关键修复点如下:

1. 核心修复:参数化查询

修复后的代码中,search参数通过$wpdb->prepare()进行预处理,彻底隔离用户输入。例如,旧版本中直接拼接的LIKE子句被修改为:  

// 修复后代码

$search = $request->get_param('search');

$search_param = '%' . $wpdb->esc_like($search) . '%'; // 预处理前转义

$tree = (new Tree($orderby, $order, $search_param))->get();

// 或直接在SQL中使用预处理占位符(如$wpdb->prepare("LIKE %s", $search_param))

通过$wpdb->prepare()将用户输入作为参数传递,避免SQL注入(readme.txt中6.4.9版本更新日志明确标注“Fixed: Security”,对应此修复)。

2. 输入验证强化

修复版本中,对search参数增加了严格的类型和长度验证。例如,在FolderController.php的getFolders方法中新增:  

// 修复后代码

public function getFolders(\WP_REST_Request $request) {

    $search = sanitize_text_field($request->get_param('search'));

    if (strlen($search) > 255) { // 限制长度防止过长的注入payload

        return new WP_Error('invalid_search', __('Search term too long.', 'filebird'));

    }

    // 后续查询使用预处理后的$search

}

   通过sanitize_text_field过滤非文本内容,并限制长度,进一步降低注入风险

3. 版本日志佐证

在readme.txt中,6.4.9版本的更新日志明确标注:  

= Aug 4, 2025 - Version 6.4.9 =  

= Added: Accessibility improvements based on WCAG guidelines  

= Fixed: Security  

直接对应此漏洞的修复(Changelog同样显示6.4.9版本修复了安全问题)。  

、验证修复生效方法

• 升级验证:将FileBird插件升级至6.4.9版本后,通过媒体库搜索功能提交恶意search参数(如' OR 1=1 --),检查是否返回数据库错误或异常数据(正常应仅返回文件/文件夹结果)。

• 日志检查:查看WordPress调试日志(wp-content/debug.log),确认无SQL注入相关的警告或错误信息(官方文件中代码修复后,日志应无异常SQL执行记录)。  

总结

CVE-2025-6986是典型的认证依赖型SQL注入漏洞,其核心风险在于未对用户输入进行参数化处理。可确认修复方案的关键是:  

1. 使用$wpdb->prepare()进行参数化查询;  

2. 强化输入过滤(如sanitize_text_field)和长度限制;  

3. 更新至6.4.9版本(readme.txt明确标注修复)。  

版权声明与原创承诺

本文所有文字、实验方法及技术分析均为 本人原创作品,受《中华人民共和国著作权法》保护。未经本人书面授权,禁止任何形式的转载、摘编或商业化使用。

道德与法律约束

文中涉及的网络安全技术研究均遵循 合法合规原则:

1️⃣ 所有渗透测试仅针对 本地授权靶机环境

2️⃣ 技术演示均在 获得书面授权的模拟平台 完成

3️⃣ 坚决抵制任何未授权渗透行为

技术资料获取

如需完整实验代码、工具配置详解及靶机搭建指南:

请关注微信公众号 「零日破晓」

后台回复关键词 【博客资源】 获取独家技术文档包

法律追责提示

对于任何:

✖️ 盗用文章内容

✖️ 未授权转载

✖️ 恶意篡改原创声明

本人保留法律追究权利。

CVE-2025-6018 和 CVE-2025-6019 是两个特定于某些软件或服务的安全漏洞。尽管没有直接提及这两个漏洞的详细信息,但可以根据常见的漏洞修复方法提供指导。修复此类安全漏洞通常涉及以下几个方面:代码审查、输入验证、权限控制以及及时更新依赖库或框架。 ### 代码审查与输入验证 在开发过程中,定期进行代码审查是发现潜在安全问题的重要手段。特别是对于涉及到用户输入的部分,应确保所有输入都经过严格的验证和清理。例如,对于Web应用程序中的表单提交,可以使用正则表达式来限制输入的格式,防止恶意代码的注入[^1]。 ```python import re def validate_input(input_string): # 使用正则表达式限制输入为字母数字字符 if re.match("^[a-zA-Z0-9_]+$", input_string): return True else: return False ``` ### 权限控制 确保应用程序遵循最小权限原则,即每个组件或用户仅拥有完成其任务所需的最小权限。这可以通过配置文件或代码中的权限检查来实现。例如,在一个基于角色的访问控制系统中,可以定义不同的角色及其权限,并在执行敏感操作前进行权限检查[^1]。 ```python def check_permission(user_role, required_permission): # 假设有一个权限映射表 permissions = { 'admin': ['read', 'write', 'delete'], 'user': ['read'] } # 检查用户角色是否存在且具有所需权限 if user_role in permissions and required_permission in permissions[user_role]: return True else: return False ``` ### 更新依赖库或框架 保持所有依赖库和框架的最新版本是防止已知漏洞的关键。许多开源项目会定期发布安全更新,以修复发现的问题。开发者应该订阅这些项目的更新通知,并及时将更新应用到自己的项目中。此外,使用自动化工具可以帮助跟踪和更新依赖项。 ```bash # 使用npm更新所有依赖项 npm update ``` ### 安全测试 实施全面的安全测试策略,包括但不限于单元测试、集成测试和渗透测试。这些测试可以帮助识别和修复潜在的安全漏洞。例如,使用OWASP ZAP等工具进行自动化安全测试,可以发现诸如SQL注入、XSS攻击等常见问题[^1]。 ### 安全意识培训 最后,提高团队的安全意识也是不可或缺的一环。定期组织安全培训,让开发人员了解最新的安全威胁和最佳实践,从而在开发过程中自觉地遵循安全编码规范。 通过上述措施,可以有效地减少和预防包括CVE-2025-6018 和 CVE-2025-6019在内的多种安全漏洞的发生。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值