SQL 注入原理
SQL 注入原理
sql 注入的原因是应用程序对用户输入的数据没有进行合法判断,并且直接将数据传给数据库进行查询。
流程如下:
- 【 P 】:「用户输入」数据 P
- 【 Q = X + P 】:「应用程序」将 P 与其他数据拼接
- 【 Q --> R 】:「数据库 」将 Q 作为 SQL语句执行,返回结果 R
- 【 R 】:「应用程序」接收数据 R,将 R 返回给用户
基于查询注入
如果应用程序会直接返回查询的结果,那么我们将可以使用基于查询的注入方法。
联合查询法:
- 【 P 】:「用户输入」数据 P =
-1' union select @@version --
[1] - 【 Q = X + P 】:「应用程序」拼接数据 Q =
SELECT sc FROM score WHERE id='-1' union select @@datadir -- '
[2] - 【 Q --> R 】:「数据库 」将 Q 作为 SQL 语句执行,返回结果 R =
/var/sql/233.db
- 【 R 】:「应用程序」接收数据 R,将 R 返回给用户
基于报错注入
应用程序不返回查询结果,反而返回 sql 数据库的报错信息。其流程如下:
- 【 P 】:「用户输入」:P =
' and updatexml(1,concat(1,database()),1)#
- 【 Q = X + P 】:「应用程序」:Q =
SELECT sc FROM score WHERE id='' and updatexml(1,concat(1,database()),1)#'
[3] - 【 Q --> R 】:「数据库 」:Q --> R = updatexml 执行错误的信息,其中带有
concat(1,database())
的执行结果。 - 【 R 】:「应用程序」:将 R 返回给用户
基于布尔注入
应用程序不会返回查询结果,而是会返回一个状态。比如:
- 0 , 1
- 真, 假
- 高,中,低(当然三种状态)
其中前两个只有两种状态的信息被称为布尔值。流程如下:
- 【 P 】:「用户输入」:P =
1' and length((select @@version)) > 0 #
[4] - 【 Q = X + P 】:「应用程序」:Q =
SELECT sc FROM score WHERE id='1' and length((select @@version)) > 0 #'
- 【 Q --> R 】:「数据库 」:Q --> R =「真」 或 「假」
- 【 R 】:「应用程序」:将 R 返回给用户
基于时间注入
应用程序仅返回一种状态,意为当前查询结束。但是没有返回任何数据或者其他状态。流程如下:
- 【 C 】:「计时程序」用户开始计时 C
- 【 P 】:「用户输入」P =
1' and length((select @@version))>0 and sleep(5) #
- 【 Q = X + P 】:「应用程序」Q =
SELECT sc FROM score WHERE id='1' and length(version()) and sleep(5) > 0 #'
- 【 Q --> R 】:「数据库 」Q --> R =「sleep(5),并返回数据」 或 「不执行,返回数据」
- 【 R 】:「应用程序」将 R 返回给用户
- 【 C 】:「计时程序」结束计时 C
可以看出,时间注入与布尔注入本质相同。只是前者返回的是 「无延迟,查询结束」与「延迟 5 秒,查询结束」这两种特殊的状态。
二次注入
之前的注入都是一次输入,一次输出式的。而二次注入,则是两次输入,两次输出。
用一个 web 应用程序举例,假设它的业务逻辑为:
- 【 P 】:「用户输入」数据 P
- 【 P --> P' 】:「应用程序」将 P 中的特殊字符进行转义,得到 P'
- 【 Q = X + P' 】:「应用程序」将 P' 与其他数据拼接
- 【 Q --> R 】:「数据库 」Q 执行后,P' 将转换[5]为 P 并保存。同时返回一个结果信息 R
- 【 R 】:「应用程序」将 R 返回给用户
- 第二次查询
- 【 R 】:「用户输入」数据 R
- 【 R --> R' 】:「应用程序」将 R 中特殊字符转义,得到 R'
- 【 Q2 = X2 + R' 】:「应用程序」将 R' 与其他数据拼接
- 【 Q2 --> P 】:「数据库 」Q2 执行后,返回结果信息 P[6]
- 【 Q3 = X3 + P 】:「应用程序」将 P 与其他数据拼接(注意:数据 P 特殊字符没有被转义!)
- 【 Q3 --> R2 】:「数据库 」Q3 执行后,返回 R2
- 【 R2 】:「应用程序」将 R2 返回给用户
从业务看出,第二查询的过程中,应用程序在拼接数据 P 时没有对 P 进行转义。这意味着我们可以在第一次查询时,将注入语句 P 保存进数据库,在第二次查询执行含有 P 的语句。来成功注入。
两个减号之后是有一个空格的。如果这数据通过 url 传输的话。需要将空格等价替换成
+
再传输。 ↩︎可以观察到数据 P 在 Q 末尾的 WHERE 中,这不是必然,而是常见情况。特殊状态下,P 可能在 SELECT、GROUP BY 等之中。 ↩︎
updatexml 函数需要三个参数,因此
concat(1,database())
前后的 1 只是占位置的参数。 ↩︎如果这里
select @@version
不加括号,将会被length
认为是变量名。引发语法错误。 ↩︎数据库在存储数据时,会自动去掉数据中的转义符号。 ↩︎
这里的结果 P 就是第一次访问时保存的数据 P。 ↩︎