即使面对恶意格式化的 SQL 输入或数据库文件,SQLite 也不应该崩溃、缓冲区溢出、内存泄漏或表现出任何其他有害行为。SQLite 应该始终检测到错误输入并引发错误,而不是崩溃或损坏内存。任何由 SQL 输入或数据库文件引起的故障都被视为严重错误,将在引起 SQLite 开发者注意后立即解决。SQLite 进行了广泛的模糊测试,以帮助确保其能够抵抗此类错误。
然而,错误确实会发生。如果您正在编写将不可信 SQL 输入或数据库文件发送到 SQLite 的应用程序,则可以采取一些额外的步骤来帮助减少攻击面并防止由未检测到的错误引起的零日漏洞。
接受不可信 SQL 输入的应用程序应采取以下预防措施
设置 SQLITE_DBCONFIG_DEFENSIVE 标志。这可以防止普通的 SQL 语句故意破坏数据库文件。SQLite 应该能够抵御同时包含恶意 SQL 输入和恶意损坏的数据库文件的攻击。然而,拒绝脚本攻击者访问损坏的数据库输入可以提供额外的防御层。
减少 SQLite 对输入施加的 限制。这有助于防止拒绝服务攻击和其他由于异常大的输入而可能发生的恶作剧。您可以通过编译时使用 -DSQLITE_MAX_... 选项或运行时使用 sqlite3_limit() 接口来实现。大多数应用程序可以大幅度减少限制而不影响功能。下表提供了一些建议,但具体值会根据应用程序而有所不同。
限制设置 | 默认值 | 高安全值 |
---|---|---|
LIMIT_LENGTH | 1,000,000,000 | 1,000,000 |
LIMIT_SQL_LENGTH | 1,000,000,000 | 100,000 |
LIMIT_COLUMN | 2,000 | 100 |
LIMIT_EXPR_DEPTH | 1,000 | 10 |
LIMIT_COMPOUND_SELECT | 500 | 3 |
LIMIT_VDBE_OP | 250,000,000 | 25,000 |
LIMIT_FUNCTION_ARG | 127 | 8 |
LIMIT_ATTACH | 10 | 0 |
LIMIT_LIKE_PATTERN_LENGTH | 50,000 | 50 |
LIMIT_VARIABLE_NUMBER | 999 | 10 |
LIMIT_TRIGGER_DEPTH | 1,000 | 10 |
考虑使用 sqlite3_set_authorizer() 接口来限制将要处理的 SQL 范围。例如,一个不需要更改数据库模式的应用程序可能会添加一个 sqlite3_set_authorizer() 回调,该回调会导致任何 CREATE 或 DROP 语句失败。
SQL 语言非常强大,因此恶意 SQL 输入(或由应用程序错误引起的错误 SQL 输入)始终有可能提交运行时间非常长的 SQL。为了防止这成为拒绝服务攻击,请考虑使用 sqlite3_progress_handler() 接口在每个 SQL 语句运行时定期调用回调,并在回调运行时间过长时让回调返回非零值以中止语句。或者,在另一个线程中设置一个计时器,并在计时器到期时调用 sqlite3_interrupt() 来防止 SQL 语句永远运行。
使用 sqlite3_hard_heap_limit64() 接口限制 SQLite 将分配的最大内存量。这有助于防止拒绝服务攻击。要了解应用程序实际上需要多少堆空间,请针对典型输入运行它,然后使用 sqlite3_memory_highwater() 接口测量最大瞬时内存使用情况。将硬堆限制设置为观察到的最大瞬时内存使用量加上一定余量。
考虑将 SQLITE_MAX_ALLOCATION_SIZE 编译时选项设置为小于其默认值 2147483391 (0x7ffffeff) 的值。根据应用程序的不同,100000000 (1 亿) 或更小的值也并非不合理。
对于嵌入式系统,请考虑使用 -DSQLITE_ENABLE_MEMSYS5 选项编译 SQLite,然后通过 sqlite3_config(SQLITE_CONFIG_HEAP) 接口为 SQLite 提供一个固定大小的内存块作为其堆。这将防止恶意 SQL 通过使用过量的内存来执行拒绝服务攻击。如果(比如)为 SQLite 提供了 5 MB 的内存,一旦消耗完这个内存,SQLite 将开始返回 SQLITE_NOMEM 错误,而不是占用应用程序其他部分所需的内存。这也将 SQLite 的内存沙箱化,因此应用程序其他部分中的写后释放错误不会导致 SQLite 出现问题,反之亦然。
要控制 printf() SQL 函数 中的内存使用情况,请使用 "-DSQLITE_PRINTF_PRECISION_LIMIT=100000" 或类似的合理值进行编译。此 #define 限制了 printf() 函数中 %- 替换的宽度和精度,因此可以防止恶意 SQL 语句通过诸如 "printf('%1000000000s','hi')".
请注意,SQLite 使用其内置的 printf() 在内部帮助它格式化 sqlite_schema 表 中的 sql 列。因此,任何表、索引、视图或触发器定义都不能远远大于精度限制。您可以设置小于 100000 的精度限制,但请注意,您使用的任何精度限制都必须至少与模式中最长的 CREATE 语句一样长。
读取或写入来源不明的 SQLite 数据库文件的应用程序应采取以下列出的预防措施。
即使应用程序没有故意接受来自不可信来源的数据库文件,也要注意本地数据库文件被更改的攻击。为了获得最佳安全性,任何可能曾被不同安全域中的代理写入的数据库文件都应视为可疑。
如果应用程序包含任何具有副作用或可能泄漏特权信息的 自定义 SQL 函数 或 自定义虚拟表,则应用程序应使用以下一种或多种技术来防止恶意制作的数据库模式秘密运行这些 SQL 函数和/或虚拟表以达到恶意目的。
如果应用程序不使用触发器或视图,请考虑使用以下方法禁用未使用的功能。
sqlite3_db_config(db,SQLITE_DBCONFIG_ENABLE_TRIGGER,0,0); sqlite3_db_config(db,SQLITE_DBCONFIG_ENABLE_VIEW,0,0);
对于读取来自远程机器(可能来自匿名贡献者)的风险特别高的数据库文件,以下额外的预防措施可能是合理的。然而,这些额外的防御措施会带来性能成本,因此在所有情况下可能都不合适。
在打开数据库文件并在运行任何其他 SQL 语句之前,将 PRAGMA integrity_check 或 PRAGMA quick_check 作为第一个 SQL 语句运行到数据库上。拒绝处理任何包含错误的数据库文件。
启用 PRAGMA cell_size_check=ON 设置。
不要启用内存映射 I/O。换句话说,确保 PRAGMA mmap_size=0。
为了安全地将 SQLite 与潜在的敌对输入一起使用,上述预防措施并不是必需的。然而,它们确实提供了额外的防御层,以抵御零日漏洞,并且鼓励在将来自不可信来源的数据传递到 SQLite 的应用程序中使用这些措施。
此页面上次修改于 2024-01-16 14:06:27 UTC