黑魔法防御术

1. SQLite 总是验证它的输入

SQLite 不应崩溃、溢出缓冲区、泄漏内存或表现出任何其他有害行为,即使出现恶意格式错误的 SQL 输入或数据库文件时也是如此。SQLite 应该始终检测错误的输入并引发错误,而不是崩溃或损坏内存。由 SQL 输入或数据库文件引起的任何故障都被视为严重错误,当引起 SQLite 开发人员的注意时,将立即解决。SQLite 经过广泛的模糊测试,以帮助确保它能够抵抗这些类型的错误。

尽管如此,错误还是会发生。如果您正在编写一个将不受信任的 SQL 输入或数据库文件发送到 SQLite 的应用程序,您可以采取其他步骤来帮助减少攻击面并防止由未检测到的错误引起的零日攻击。

1.1. 不受信任的 SQL 输入

接受不受信任的 SQL 输入的应用程序应采取以下预防措施:

  1. 设置SQLITE_DBCONFIG_DEFENSIVE标志。这可以防止普通的 SQL 语句故意破坏数据库文件。SQLite 应该能够抵御同时涉及恶意 SQL 输入和恶意损坏的数据库文件的攻击。然而,拒绝仅脚本攻击者访问损坏的数据库输入提供了额外的防御层。

  2. 减少SQLite 对输入施加的限制。这有助于防止拒绝服务攻击和由于异常大的输入而可能发生的其他类型的恶作剧。您可以在编译时使用 -DSQLITE_MAX_... 选项或在运行时使用 sqlite3_limit()接口执行此操作。大多数应用程序可以在不影响功能的情况下显着降低限制。下表提供了一些建议,但具体值会因应用而异:

    Limit SettingDefault ValueHigh-security Value
    LIMIT_LENGTH1,000,000,0001,000,000
    LIMIT_SQL_LENGTH1,000,000,000100,000
    LIMIT_COLUMN2,000100
    LIMIT_EXPR_DEPTH1,00010
    LIMIT_COMPOUND_SELECT5003
    LIMIT_VDBE_OP250,000,00025,000
    LIMIT_FUNCTION_ARG1278
    LIMIT_ATTACH100
    LIMIT_LIKE_PATTERN_LENGTH50,00050
    LIMIT_VARIABLE_NUMBER99910
    LIMIT_TRIGGER_DEPTH1,00010
  3. 考虑使用sqlite3_set_authorizer()接口来限制将要处理的 SQL 的范围。例如,不需要更改数据库架构的应用程序可能会添加一个 sqlite3_set_authorizer() 回调,这会导致任何 CREATE 或 DROP 语句失败。

  4. SQL语言非常强大,所以总是有可能出现恶意的SQL输入(或者是应用程序bug导致的错误的SQL输入)提交运行了很长时间的SQL。为防止这成为拒绝服务攻击,请考虑使用 sqlite3_progress_handler()接口在每个 SQL 语句运行时定期调用回调,并在语句运行时间过长时让该回调返回非零值以中止该语句. 或者,在单独的线程中设置一个计时器,并在计时器关闭时调用sqlite3_interrupt()以防止 SQL 语句永远运行。

  5. 使用sqlite3_hard_heap_limit64()接口 限制 SQLite 将分配的最大内存量。这有助于防止拒绝服务攻击。要找出应用程序实际需要多少堆空间,请针对典型输入运行它,然后使用 sqlite3_memory_highwater()接口测量最大瞬时内存使用量。将硬堆限制设置为观察到的最大瞬时内存使用量加上一些余量。

  6. 对于嵌入式系统,考虑使用 -DSQLITE_ENABLE_MEMSYS5选项编译 SQLite,然后通过sqlite3_config ( SQLITE_CONFIG_HEAP ) 接口为 SQLite 提供固定的内存块以用作其堆 。这将防止恶意 SQL 通过使用过多的内存来执行拒绝服务攻击。如果(比如说)为 SQLite 提供了 5 MB 的内存,一旦消耗了那么多,SQLite 将开始返回 SQLITE_NOMEM 错误,而不是吸收应用程序其他部分所需的内存。这也沙箱化了 SQLite 的内存,这样应用程序其他部分的 write-after-free 错误就不会导致 SQLite 出现问题,反之亦然。

  7. 要控制printf() SQL 函数 中的内存使用,请使用“ -DSQLITE_PRINTF_PRECISION_LIMIT=100000 ”或一些类似的合理值进行编译。这个 #define 限制了 printf() 函数中 %-substitutions 的宽度和精度,从而防止恶意 SQL 语句通过诸如“ printf('%1000000000s','hi') ”之类的结构消耗大量 RAM

    请注意,SQLite 在内部使用其内置的 printf() 来帮助它格式化sqlite_schema 表中的 sql 列。因此,任何表、索引、视图或触发器定义都不能比精度限制大很多。您可以将精度限制设置为小于 100000,但请注意您使用的任何精度限制至少与模式中最长的 CREATE 语句一样长。

1.2. 不受信任的 SQLite 数据库文件

读取或写入来源不明的 SQLite 数据库文件的应用程序应采取下面列举的预防措施。

即使应用程序没有故意接受来自不受信任来源的数据库文件,也要提防更改本地数据库文件的攻击。为了获得最佳安全性,任何可能被不同安全域中的代理写入的数据库文件都应被视为可疑文件。

  1. 如果应用程序包含任何具有副作用或可能泄露特权信息的自定义 SQL 函数自定义虚拟表,则应用程序应使用以下一种或多种技术来防止恶意制作的数据库模式暗中运行这些 SQL 函数和/或用于恶意目的的虚拟表:

    1. 在每个数据库连接打开后立即调用sqlite3_db_config (db, SQLITE_DBCONFIG_TRUSTED_SCHEMA ,0,0)
    2. 在每个数据库连接打开后立即 运行PRAGMA trusted_schema=OFF语句。
    3. 使用-DSQLITE_TRUSTED_SCHEMA=0编译时选项 编译 SQLite 。
    4. 通过在所有自定义 SQL 函数上设置 SQLITE_DIRECTONLY 标志并在所有自定义虚拟表上设置SQLITE_VTAB_DIRECTONLY标志,禁止秘密使用自定义 SQL 函数和虚拟表。
  2. 如果应用程序不使用触发器或视图,请考虑通过以下方式禁用未使用的功能:

    sqlite3_db_config(db,SQLITE_DBCONFIG_ENABLE_TRIGGER,0,0);
    sqlite3_db_config(db,SQLITE_DBCONFIG_ENABLE_VIEW,0,0);
    

对于读取异常高风险的数据库文件,例如从远程机器接收的数据库文件,以及可能来自匿名贡献者的数据库文件,以下额外预防措施可能是合理的。然而,这些额外的防御措施会带来性能成本,因此可能并不适用于所有情况:

  1. 在打开数据库文件之后和运行任何其他 SQL 语句之前,在数据库上运行PRAGMA integrity_checkPRAGMA quick_check作为第一个 SQL 语句。拒绝并拒绝处理任何包含错误的数据库文件。

  2. 启用PRAGMA cell_size_check=ON设置。

  3. 不要启用内存映射 I/O。换句话说,确保PRAGMA mmap_size=0

2.总结

为了安全地使用 SQLite 和潜在的恶意输入,不需要上述预防措施。但是,它们确实为零日攻击提供了额外的防御层,并且鼓励将数据从不受信任的来源传递到 SQLite 的应用程序。