Back to Druid

SQL 防火墙指南 | SQL Firewall (WallFilter) Guide

doc/wall-security-guide.md

1.2.2820.6 KB
Original Source

SQL 防火墙指南 | SQL Firewall (WallFilter) Guide

English | 中文


中文

Druid WallFilter 是基于 SQL AST(抽象语法树)分析的 SQL 安全防护组件,可以有效防御 SQL 注入攻击和危险 SQL 操作。

架构原理

WallFilter 作为 Druid Filter-Chain 中的一环,在 SQL 到达数据库之前拦截并检查所有 SQL 操作。

Application
    │
    ▼
┌──────────────────────────────────────────────────┐
│                   WallFilter                      │
│                                                   │
│  ┌─────────────┐    ┌──────────────────────────┐ │
│  │ WallConfig  │    │     WallProvider          │ │
│  │ (安全规则)   │    │  ┌────────────────────┐  │ │
│  │             │    │  │ SQL Parser (方言)   │  │ │
│  │ tenantColumn│    │  │        ↓            │  │ │
│  │ updateCheck │    │  │ AST → WallVisitor   │  │ │
│  │ permitTable │    │  │        ↓            │  │ │
│  │ denyTable   │    │  │ 白名单/黑名单 Cache  │  │ │
│  └─────────────┘    │  └────────────────────┘  │ │
│                     └──────────────────────────┘ │
└──────────────────────────────────────────────────┘
    │
    ▼
JDBC Driver

核心组件:

组件职责
WallFilterFilter-Chain 入口,拦截所有 JDBC 操作,管理多租户列隐藏和结果集映射
WallProvider执行 SQL 检查,管理白名单/黑名单缓存和统计数据,按方言特化
WallConfig安全规则配置,包括 DML/DDL 控制、注入检测、多租户设置
WallContext线程级上下文,通过 ThreadLocal 传递当前 SQL 检查状态
WallVisitorAST 遍历器,按方言特化的规则检查器

检查流程

与基于正则匹配的安全方案不同,AST 分析能精确理解 SQL 语义,大幅减少误报和漏报:

SQL → Parser → AST → WallVisitor(检查规则) → 允许/拒绝

WallProvider 内部维护高性能缓存加速检查:

  1. 白名单缓存 — 已知安全 SQL 的 AST 摘要,命中后跳过完整检查
  2. 黑名单缓存 — 已知危险 SQL 直接拦截
  3. 合并 SQL 缓存 — 参数化后的 SQL 模板去重统计

快速启用

Spring Boot

yaml
spring:
  datasource:
    druid:
      filter:
        wall:
          enabled: true
          db-type: mysql

Java 代码

java
WallFilter wallFilter = new WallFilter();
wallFilter.setDbType(DbType.mysql);
dataSource.setProxyFilters(Arrays.asList(wallFilter));

快捷方式

yaml
spring.datasource.druid.filters: wall

配置项

WallFilter 级别配置

通过 JVM 系统属性或 Properties 对象设置:

配置项默认值说明
druid.wall.logViolationfalse违规 SQL 是否输出到日志(ERROR 级别)
druid.wall.throwExceptiontrue违规 SQL 是否抛出 SQLException

两者可组合使用:

logViolationthrowException行为
falsetrue抛异常,不写日志(默认)
truetrue抛异常并写日志
truefalse写日志但放行 SQL
falsefalse静默放行(不推荐)

logViolationthrowExceptionvolatile 字段,支持运行时通过 JMX 动态修改,无需重启。

WallConfig — 基本安全规则

配置项默认值说明
selectAllowtrue是否允许 SELECT
selectAllColumnAllowtrue是否允许 SELECT *
insertAllowtrue是否允许 INSERT
updateAllowtrue是否允许 UPDATE
deleteAllowtrue是否允许 DELETE
mergeAllowtrue是否允许 MERGE
callAllowtrue是否允许存储过程调用

WallConfig — DDL 控制

配置项默认值说明
createTableAllowtrue是否允许 CREATE TABLE
alterTableAllowtrue是否允许 ALTER TABLE
dropTableAllowtrue是否允许 DROP TABLE
truncateAllowtrue是否允许 TRUNCATE
commentAllowfalse是否允许 SQL 注释

WallConfig — 注入防护

配置项默认值说明
multiStatementAllowfalse是否允许多语句执行(堆叠注入防护)
noneBaseStatementAllowfalse是否允许非基本语句(如 SETSHOW
conditionAndAlwayTrueAllowtrue是否允许恒真条件 AND 1=1
conditionAndAlwayFalseAllowfalse是否允许恒假条件 AND 1=0
selectIntoAllowtrue是否允许 SELECT INTO
selectUnionChecktrue检查 UNION 注入
selectWhereAlwayTrueChecktrue检查 WHERE 恒真
selectHavingAlwayTrueChecktrue检查 HAVING 恒真
deleteWhereAlwayTrueChecktrue检查 DELETE WHERE 恒真
updateWhereAlayTrueChecktrue检查 UPDATE WHERE 恒真

WallConfig — 对象访问控制

配置项默认值说明
tableChecktrue检查表名是否在白名单中
schemaChecktrue检查 Schema 名
functionChecktrue检查函数调用
objectChecktrue检查对象访问
variantChecktrue检查变量使用
metadataAllowtrue是否允许 Connection.getMetaData()
wrapAllowtrue是否允许 isWrapperFor/unwrap 操作

配置示例

严格模式(生产环境推荐)

yaml
spring:
  datasource:
    druid:
      filter:
        wall:
          enabled: true
          db-type: mysql
          config:
            # 禁止危险操作
            delete-allow: false
            drop-table-allow: false
            truncate-allow: false
            alter-table-allow: false

            # 注入防护
            multi-statement-allow: false
            comment-allow: false
            condition-and-alway-true-allow: false
            select-union-check: true
            delete-where-alway-true-check: true
            update-where-alay-true-check: true

宽松模式(开发/测试环境)

yaml
spring:
  datasource:
    druid:
      filter:
        wall:
          enabled: true
          db-type: mysql
          config:
            multi-statement-allow: true
            comment-allow: true
            none-base-statement-allow: true

白名单与黑名单

WallFilter 支持通过白名单和黑名单控制可访问的表和 Schema:

java
WallConfig config = new WallConfig();

// 添加允许访问的表(白名单)
config.getPermitTables().add("users");
config.getPermitTables().add("orders");

// 添加禁止访问的表(黑名单)
config.getDenyTables().add("admin_secrets");

WallFilter wallFilter = new WallFilter();
wallFilter.setConfig(config);

也可以通过配置文件批量管理白名单/黑名单,在 WallConfig 资源目录下放置 permit-table.txtdeny-table.txt 文件,每行一个表名。

方言支持

WallFilter 针对不同数据库方言提供了专门的 WallProvider,每种 Provider 使用对应方言的 SQL Parser 进行精准的语义分析。

WallProvider覆盖的数据库
MySqlWallProviderMySQL, MariaDB, OceanBase, DRDS, TiDB, H2, Lealone, Presto, Trino, SuperSQL, PolarDB-X
OracleWallProviderOracle, AliOracle, OceanBase Oracle 模式, PolarDB-O
SQLServerWallProviderSQL Server, jTDS
PGWallProviderPostgreSQL, EDB, PolarDB, Greenplum, GaussDB
DB2WallProviderDB2
SQLiteWallProviderSQLite
CKWallProviderClickHouse

说明: 对于上表中未列出的数据库类型,WallFilter 会尝试通过 SPI 机制加载自定义 WallProvider。

SPI 扩展自定义方言

对于 Druid 未内置支持的数据库,可以通过实现 WallProviderCreator 接口注册自定义 WallProvider:

java
public class MyDbWallProviderCreator implements WallProviderCreator {
    @Override
    public WallProvider createWallConfig(DataSourceProxy dataSource,
                                          WallConfig config, DbType dbType) {
        if (dbType == DbType.mydb) {
            if (config == null) {
                config = new WallConfig("META-INF/druid/wall/mydb");
            }
            return new MyDbWallProvider(config);
        }
        return null; // 返回 null 表示不处理,交给下一个 Creator
    }

    @Override
    public int getOrder() {
        return 100; // 数值越小优先级越高
    }
}

注册 SPI:在 META-INF/services/com.alibaba.druid.wall.WallProviderCreator 文件中添加实现类全限定名。

多个 Creator 按 getOrder() 升序排列,优先级高的先被调用,第一个返回非 null 的 WallProvider 生效。

多租户数据隔离

WallFilter 支持结果集级别的多租户数据隔离,可以在 SQL 查询结果返回给应用时自动隐藏租户标识列并进行租户过滤。

基本配置

java
WallConfig config = new WallConfig();

// 方式一:全局设置租户列名
config.setTenantColumn("tenant_id");

// 方式二:设置租户表匹配模式(支持 glob 通配符)
config.setTenantTablePattern("t_*"); // 仅对 t_ 前缀的表生效

TenantCallBack 回调

通过 TenantCallBack 可以实现精细化的多租户控制:

java
config.setTenantCallBack(new TenantCallBack() {
    @Override
    public String getTenantColumn(StatementType statementType, String tableName) {
        // 按表名返回该表的租户列名
        return "tenant_id";
    }

    @Override
    public String getHiddenColumn(String tableName) {
        // 返回需要对应用隐藏的列名(结果集中不可见)
        return "tenant_id";
    }

    @Override
    public void filterResultsetTenantColumn(Object value) {
        // 每行数据返回时调用,可用于校验租户值
        String currentTenant = TenantContext.getCurrentTenant();
        if (!currentTenant.equals(String.valueOf(value))) {
            throw new SecurityException("Tenant mismatch");
        }
    }
});

列隐藏机制

当配置了隐藏列后,WallFilter 在获取 ResultSet 时自动处理:

  1. 扫描 ResultSetMetaData,识别需要隐藏的列
  2. 建立逻辑列号 ↔ 物理列号的双向映射
  3. 应用层通过 resultSet.getXxx(columnIndex) 访问时,自动转换为正确的物理列号
  4. ResultSetMetaData.getColumnCount() 返回的列数自动减去隐藏列数
  5. resultSet.findColumn(hiddenColumnName) 对隐藏列抛出 SQLException

UPDATE 安全检查

WallFilter 支持对 UPDATE 操作进行值级别的安全检查,通过 WallConfig.updateCheckHandler 可以在执行前校验 SET 子句的赋值和 WHERE 子句的过滤条件:

java
config.setUpdateCheckHandler(new WallUpdateCheckHandler() {
    @Override
    public boolean check(String tableName, String columnName,
                         Object setValue, List<Object> filterValues) {
        // tableName: 被更新的表名
        // columnName: 被更新的列名
        // setValue: SET 子句中的新值
        // filterValues: WHERE 子句中的过滤值列表
        //
        // 返回 true 允许更新,false 拒绝(抛出 SQLException)

        // 示例:禁止将 status 列设为 "deleted"
        if ("status".equals(columnName) && "deleted".equals(setValue)) {
            return false;
        }
        return true;
    }
});

该检查在 PreparedStatement 执行时触发,能够读取绑定参数的实际值(不仅是 SQL 文本),提供运行时的值级校验能力。

SQL 注入防护示例

WallFilter 可以检测并阻止常见的 SQL 注入攻击:

sql
-- 堆叠注入(被 multiStatementAllow=false 拦截)
SELECT * FROM users WHERE id = 1; DROP TABLE users;

-- UNION 注入(被 selectUnionCheck=true 拦截)
SELECT * FROM users WHERE id = 1 UNION SELECT * FROM admin;

-- 恒真条件注入(被 conditionAndAlwayTrueAllow=false 拦截)
SELECT * FROM users WHERE id = 1 OR 1=1;

-- 注释注入(被 commentAllow=false 拦截)
SELECT * FROM users WHERE id = 1 -- AND password = 'xxx';

处理拦截

当 SQL 被拦截时,WallFilter 会抛出 SQLException(消息以 "sql injection violation" 开头)。可在应用层捕获处理:

java
try {
    jdbcTemplate.query(sql, ...);
} catch (SQLException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("sql injection violation")) {
        // SQL 被防火墙拦截
        logger.warn("SQL blocked by WallFilter: {}", e.getMessage());
        // 进行告警、审计等处理
    }
}

当违规 SQL 是语法错误(SyntaxErrorViolation)时,原始的解析异常会作为 SQLException 的 cause 附带抛出,便于诊断。

JMX 监控与管理

WallFilter 实现了 WallFilterMBean 接口,注册为 JMX MBean 后可通过 JConsole 等工具进行运行时监控和管理。

MBean 操作

方法说明
getDbType()获取当前数据库方言
isLogViolation() / setLogViolation(boolean)查询/设置是否记录违规日志
isThrowException() / setThrowException(boolean)查询/设置是否抛出异常
getViolationCount()获取累计违规次数
resetViolationCount()重置违规计数器
check(String sql)手动检查指定 SQL 是否合规
checkValid(String sql)快速校验 SQL 是否合法(返回 boolean)
getProviderWhiteList()获取当前白名单中的 SQL 集合
clearProviderCache()清空白名单/黑名单缓存
clearWhiteList()清空白名单

运行时调整示例

java
// 获取 WallFilter 实例
WallFilter wallFilter = (WallFilter) dataSource.getProxyFilters().stream()
    .filter(f -> f instanceof WallFilter)
    .findFirst().orElse(null);

// 运行时开启违规日志(无需重启)
wallFilter.setLogViolation(true);

// 查看统计
long violations = wallFilter.getViolationCount();

// 清空缓存(规则变更后生效)
wallFilter.clearProviderCache();

特权模式

对于框架内部需要绕过 Wall 检查的场景(如连接池健康检查、内部元数据查询),WallProvider 提供了特权模式:

java
WallProvider.doPrivileged(() -> {
    // 此代码块内的 SQL 不经过 Wall 检查
    connection.getMetaData();
});

特权模式通过 ThreadLocal 实现,仅对当前线程当前代码块生效,代码块结束后自动恢复检查。使用前需确保 WallConfig.doPrivilegedAllow 为 true。

安全提示: 特权模式仅限框架内部使用,不应暴露给应用代码或用户输入。


English

Overview

Druid WallFilter is an AST-based SQL security component that protects against SQL injection attacks and dangerous SQL operations. Unlike regex-based approaches, AST analysis accurately understands SQL semantics, significantly reducing false positives and negatives.

Architecture

WallFilter intercepts all JDBC operations as part of Druid's Filter-Chain, checking SQL before it reaches the database.

Core components:

ComponentResponsibility
WallFilterFilter-Chain entry point; intercepts JDBC operations, manages tenant column hiding
WallProviderExecutes SQL checks, manages whitelist/blacklist caches and statistics
WallConfigSecurity rule configuration: DML/DDL control, injection detection, tenant settings
WallVisitorDialect-specific AST visitor that enforces security rules

Quick Start

yaml
spring:
  datasource:
    druid:
      filter:
        wall:
          enabled: true
          db-type: mysql
          config:
            delete-allow: false
            drop-table-allow: false
            multi-statement-allow: false

Or via shorthand: spring.datasource.druid.filters: wall

WallFilter-Level Configuration

PropertyDefaultDescription
druid.wall.logViolationfalseLog violations at ERROR level
druid.wall.throwExceptiontrueThrow SQLException on violation

Both are volatile fields and can be changed at runtime via JMX without restart.

Protection Capabilities

  • Stack injection — Blocks multi-statement execution (multiStatementAllow=false)
  • UNION injection — Detects UNION-based attacks (selectUnionCheck=true)
  • Tautology injection — Blocks always-true conditions (conditionAndAlwayTrueAllow=false)
  • Comment injection — Blocks SQL comments used to bypass checks (commentAllow=false)
  • DDL protection — Blocks DROP TABLE, TRUNCATE, ALTER TABLE
  • Object access control — Table/schema whitelist and blacklist
  • Metadata protection — Controls access to Connection.getMetaData() (metadataAllow)

Dialect Support

WallProviderDatabases
MySqlWallProviderMySQL, MariaDB, OceanBase, DRDS, TiDB, H2, Lealone, Presto, Trino, SuperSQL, PolarDB-X
OracleWallProviderOracle, AliOracle, OceanBase Oracle mode, PolarDB-O
SQLServerWallProviderSQL Server, jTDS
PGWallProviderPostgreSQL, EDB, PolarDB, Greenplum, GaussDB
DB2WallProviderDB2
SQLiteWallProviderSQLite
CKWallProviderClickHouse

For unlisted databases, implement the WallProviderCreator SPI interface:

java
public class MyWallProviderCreator implements WallProviderCreator {
    @Override
    public WallProvider createWallConfig(DataSourceProxy ds, WallConfig config, DbType dbType) {
        if (dbType == DbType.mydb) {
            return new MyDbWallProvider(config != null ? config : new WallConfig("META-INF/druid/wall/mydb"));
        }
        return null;
    }

    @Override
    public int getOrder() { return 100; } // lower value = higher priority
}

Register in META-INF/services/com.alibaba.druid.wall.WallProviderCreator.

Multi-Tenant Data Isolation

WallFilter supports result-set-level tenant isolation by automatically hiding tenant identifier columns and filtering tenant values.

java
WallConfig config = new WallConfig();
config.setTenantColumn("tenant_id");
config.setTenantTablePattern("t_*"); // only apply to tables matching pattern

config.setTenantCallBack(new TenantCallBack() {
    @Override
    public String getTenantColumn(StatementType type, String tableName) {
        return "tenant_id";
    }

    @Override
    public String getHiddenColumn(String tableName) {
        return "tenant_id"; // hidden from application ResultSet
    }

    @Override
    public void filterResultsetTenantColumn(Object value) {
        // called for each row; validate tenant value here
    }
});

When hidden columns are configured:

  • ResultSetMetaData.getColumnCount() excludes hidden columns
  • resultSet.getXxx(columnIndex) transparently maps logical to physical column indices
  • resultSet.findColumn(hiddenColumnName) throws SQLException

UPDATE Value Check

WallFilter can validate UPDATE operations at the value level before execution:

java
config.setUpdateCheckHandler((tableName, columnName, setValue, filterValues) -> {
    // return true to allow, false to block (throws SQLException)
    return !"deleted".equals(setValue);
});

This check reads actual bind parameter values from PreparedStatement, providing runtime value-level validation beyond static SQL analysis.

JMX Monitoring

WallFilter implements WallFilterMBean for runtime monitoring:

MethodDescription
getViolationCount()Cumulative violation count
check(String sql)Manually check SQL compliance
checkValid(String sql)Quick boolean validation
clearProviderCache()Clear whitelist/blacklist caches
setLogViolation(boolean)Toggle violation logging at runtime
setThrowException(boolean)Toggle exception throwing at runtime

Privileged Mode

For framework-internal operations that need to bypass Wall checks (e.g., health checks):

java
WallProvider.doPrivileged(() -> {
    // SQL in this block bypasses Wall checks
    connection.getMetaData();
});

Requires WallConfig.doPrivilegedAllow=true. Scoped to current thread via ThreadLocal, automatically restored after the block completes.