使用社交账号登录
这是最常见的注入类型,攻击者通过同一通信渠道来注入SQL代码并获取结果。
例子:
比如一个登录表单,攻击者在用户名字段输入 admin' --,如果后端SQL语句是
`
则实际执行的SQL语句变成了
这里 -- 后面的内容被注释掉,因此SQL语句实际上忽略了密码检查。 再或者如下语句:
如果用户输入 p' OR 1=1 --,则实际执行的SQL语句变成了
预防措施:
此时,数据库驱动会将 username 和 password 作为纯数据值传入,不会被解释为 SQL 代码。即使输入是 admin' --,它也只是被当作一个字符串字面量.
例子:在 PostgreSQL 中模拟基于错误的 SQL 注入 假设存在一个易受攻击的查询:
攻击者在输入框中提交: 1'::int; SELECT CAST((SELECT version()) AS int) --
或者更典型的写法(适用于数字型参数):1' AND 1=CAST((SELECT tablename FROM pg_tables LIMIT 1) AS int) --
如果后端将执行错误直接返回给前端,那么攻击者就可以获取到有用的信息,从而进一步攻击数据库。
例如上面的例子,数据库会抛出异常:
invalid input syntax for type integer: "PostgreSQL 17.7 on x86_64-pc-linux-musl, compiled by gcc (Alpine 14.2.0) 14.2.0, 64-bit"
CodeBlock Loading...invalid input syntax for type integer: "users"
预防措施:
这是盲注(Blind SQL Injection)的一种形式,攻击者无法直接看到数据库返回的数据或错误信息,但可以通过观察页面内容、响应长度或特定文本是否存在等间接方式,判断构造的 SQL 条件是否为真,从而逐位推断数据库中的信息。
与内联注入不同,盲注不依赖数据库回显数据,而是通过"是/否"行为差异进行推理。
例子 假设一个在线商店提供商品搜索功能,用户在搜索框中输入关键词,后端执行如下查询:
正常情况下,输入 laptop 会返回包含"laptop"的商品;若无匹配,则显示"未找到商品"或空列表。
现在,攻击者怀疑数据库中存在名为 users 的表,希望验证其是否存在。于是,在搜索框中提交:
拼接后的实际 SQL 语句变为:
如果 users 表存在:子查询成功返回 'test',条件为真,主查询逻辑不变,页面可能正常显示部分商品(如包含字母 "a" 的商品)。 如果 users 表不存在:子查询报错或返回空,导致整个 WHERE 条件为假,页面显示"未找到商品"。 通过对比输入 a 和输入上述 payload 后的页面内容差异(例如是否有商品显示),攻击者即可判断 users 表是否存在。
更进一步,攻击者可利用此方法逐字猜解数据,例如:
若页面行为与正常搜索一致,说明 admin 用户密码的第一个字符是 'a';否则尝试 'b'、'c'……以此类推。
预防措施 始终使用参数化查询(Prepared Statements) 即使在盲注场景下,参数化也能确保用户输入被当作纯数据处理,不会影响 SQL 逻辑结构。
此时,无论输入 a' AND ... -- 还是任何特殊字符,都会被转义为字符串的一部分,最终等效于:
数据库会严格查找包含该完整字符串的商品名,不会执行额外逻辑。
布尔盲注与内容盲注相似,但更常见的是通过布尔表达式的真/假来逐位推断数据,而非观察页面文本差异。攻击者发送不同的 payload,观察响应是否改变(比如返回空/非空、状态码或页面包含某元素与否)。
例子(PostgreSQL):
如果为真,页面表现可能与正常搜索一致;为假则表现不同。通过逐位判断,可以恢复敏感字段。
预防措施:
时间盲注通过让数据库在特定条件下延迟响应来传递“真/假”信息(常用 pg_sleep())。这在无法看到数据也无法看到错误信息时特别有用。
例子(PostgreSQL):
如果猜测为真,服务器响应会被延迟(例如延迟 5 秒),攻击者通过测量响应时间得知布尔结果,从而继续逐位猜解。
预防措施:
堆叠查询是指在一次请求中执行多条 SQL 语句(例如使用分号分隔)。如果数据库或驱动允许,攻击者可以在原始查询后追加任意语句(DROP、INSERT、UPDATE 等)。
例子:
注意:许多数据库驱动(包括多数安全配置下的 PostgreSQL 驱动)默认不允许在单次 execute 中执行多条语句,但某些环境或错误配置仍存在风险。
预防措施:
OOB 注入利用数据库能够向外部通道(网络、DNS、文件系统等)发送数据的能力,将敏感信息泄露到攻击者可控的接收方。常见于不能直接回显结果且存在可触发外联函数或不受限扩展时。
示例场景(Postgres,需存在不受限扩展或高权限):
COPY ... TO PROGRAM、dblink 或不受限的 PL 语言来触发对外部服务器的请求,从而把数据发送给攻击者。预防措施:
COPY TO PROGRAM、dblink 等第二阶注入发生在用户输入最初被安全地存储(看起来无害),但后续在另一个上下文中再次被拼接成 SQL 并执行时。例如:用户在注册时输入某段文本存入数据库,后来管理员界面把这段文本直接拼接进动态查询或 SQL 片段,从而触发注入。
示例:
O'Reilly(安全地被参数化存储)'SELECT * FROM docs WHERE author = ''' + db_value + '''',这里的 db_value 来自数据库,导致注入发生。预防措施:
当存储过程或函数内部使用动态 SQL(例如通过 EXECUTE 拼接字符串)并包含未受信任的输入时,会发生注入。PostgreSQL 的 plpgsql 提供 EXECUTE、format() 等,需要谨慎使用。
示例(危险用法):
更安全的写法(使用 USING 或 format + %L):
预防措施:
EXECUTE ... USING 或 format(..., %L/%I) 做好字面量/标识符引用记住下面 6 个要点,就能挡掉绝大多数注入攻击:
以上示例与防护建议均以 PostgreSQL 为测试背景;实际项目中请结合 ORM/驱动文档与运维策略实现最小变更的安全修复。
SELECT * FROM users WHERE username = '[用户输入]' AND password = '[用户输入]
SELECT * FROM users WHERE username = 'admin' --' AND password = '[用户输入]'
SELECT * FROM users WHERE username = '[用户输入]' AND password = '[用户输入]'
SELECT * FROM users WHERE username = 'p' OR 1=1 --' AND password = '[用户输入]'
# 危险示例(Python)
query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
cursor.execute(query)
# 安全示例
query = "SELECT * FROM users WHERE username = %s AND password = %s"
cursor.execute(query, (username, password))
SELECT * FROM users WHERE id = '[用户输入]';
SELECT * FROM users WHERE id = '1'::int; SELECT CAST((SELECT version()) AS int) -- ';
# 演变为:
SELECT CAST((SELECT version()) AS int) -- '
SELECT * FROM users WHERE id = '1' AND 1=CAST((SELECT tablename FROM pg_tables LIMIT 1) AS int) --'
SELECT * FROM products WHERE name LIKE '%[用户输入]%';
a' AND (SELECT 'test' FROM users LIMIT 1) = 'test' --
SELECT * FROM products WHERE name LIKE '%a' AND (SELECT 'test' FROM users LIMIT 1) = 'test' --%';
a' AND SUBSTRING((SELECT password FROM users WHERE username='admin'), 1, 1) = 'a' --
# 安全示例(Python + psycopg2)
query = "SELECT * FROM products WHERE name LIKE %s" cursor.execute(query, (f"%{search_term}%",))
SELECT * FROM products WHERE name LIKE '%a'' AND (SELECT ...) = ''test'' --%'
a' AND (SELECT SUBSTRING(password,1,1) FROM users WHERE username='admin') = 'a' --
' OR (SELECT CASE WHEN (SUBSTRING((SELECT password FROM users WHERE username='admin'),1,1) = 'a') THEN pg_sleep(5) ELSE pg_sleep(0) END) --
1; DROP TABLE users; --
EXECUTE 'SELECT * FROM users WHERE name = ''' || user_input || '''';
EXECUTE 'SELECT * FROM users WHERE name = $1' USING user_input;
-- 或者:EXECUTE format('SELECT * FROM users WHERE name = %L', user_input);