作者:黃忠成
一連串的 Mass SQL Injection 攻擊,讓我們回憶起數年前的 SQL Injection 攻擊,多年後的今天,我們仍深陷於同樣的危機中,本文詳述 SQL Injection 的歷史、肇因、解決及偵測方法,更為讀者們引介全新、更加安全的防堵 SQL Injection 策略。
什麼是 SQL Injection?
SQL Injection,中譯為 SQL 注入,更為人知的名稱是【資料隱碼攻擊】,意指開發人員於撰寫網頁應用程式之際,貪圖一時方便或是依循前人的慣性寫法而開啟的一道門。在數年前,一次大型 的隱碼攻擊行動,喚起了所有網站擁有者及設計人員的防駭之心,讓我們認知到,網站是一個曝露在所有人面前的公共園地,其安全性不容忽視!在那次的攻擊行動 中,有數千個網站遭到同一種手法入侵,洩露的資料及因入侵所損失的金額難以估計,而起源竟只是程式設計師的慣性及疏於防範,而我們都曾經是其中一份子。
那具體上,什麼是 SQL Injection 呢?其實說穿了很簡單,就是透過網頁上的輸入區域 (INPUT 如文字輸入框,或是 URL 中的查詢字串),將特定的 SQL 語句透過網頁送往資料庫執行。以一個登入網頁為例,在設計登入網頁時,我們會放兩個 TextBox 控件,分別讓使用者填入使用者 ID 及密碼,類似畫面如下:
圖 1:

在使用者按下登入按鈕後,我們將其輸入的資訊送往資料庫,驗證使用者輸入的登入資訊是否正確:
using System;
using System.Configuration;
using System.Data;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Data.SqlClient;
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void Button1_Click(object sender, EventArgs e)
{
if (ValidateUser(TextBox1.Text, TextBox2.Text))
Label1.Text = "歡迎你";
else
Label1.Text = "登入失敗";
}
private bool ValidateUser(string userName, string password)
{
SqlConnection conn = new SqlConnection(
"Data Source=JEFFRAY;Initial Catalog=Northwind;Integrated Security=True");
using (conn)
{
SqlCommand cmd = new SqlCommand("SELECT COUNT(*) FROM USERS WHERE USER_ID = '"
+ userName + "' AND PASSWORD = '" + password + "'", conn);
conn.Open();
return ((int)cmd.ExecuteScalar() > 0);
}
}
}
當你寫下這些程式碼時,已經開啟了 SQL Injection 的大門了,只要使用者於登入時,填入下圖的資訊,那麼不管 ID 密碼是什麼,一律可以登入系統。
圖 2:

這是為什麼呢?很簡單,起因於下面這行程式碼:
SqlCommand cmd = new SqlCommand("SELECT COUNT(*) FROM USERS WHERE USER_ID = '"
+ userName + "' AND PASSWORD = '" + password + "'", conn);
我們使用傳統 ASP 常見的手法,以組裝 SQL 指令的方式,將使用者的輸入融入既定的 SQL 語句中,但卻忽略了一件重要的事:使用者可以輸入任意的字串,包括了部份的 SQL 指令!透過輸入部份的 SQL 指令及微調,使用者可以輕易的改變這段 SQL 指令,甚至是疊加另一串 SQL 指令,而我們的網頁則照單全收,以上的輸入,會將整句 SQL 語句調整成下面這樣:
圖 3:

透過必然成真的條件式,再加上 SQL 的註解,我們的網站就這樣曝露在網路上,今天我加的是 OR,若是狠一點的加上 DROP TABLE 等破壞性指令,網站就此拜拜。
這種攻擊不僅僅出現在上例這種 POST 狀況,另一種 GET 狀態也常常受到同樣的攻擊,例如下面的程式碼即開啟了 SQL Injection 的大門。
using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Data.SqlClient;
public partial class QueryStringInjection : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
SqlConnection conn = new SqlConnection("Data Source=JEFFRAY;Initial Catalog=Northwind;Integrated Security=True");
using (conn)
{
SqlCommand cmd = new SqlCommand(
"SELECT * FROM Customers WHERE CustomerID = '"+Request.QueryString["ID"]+”‘”, conn);
conn.Open();
DetailsView1.DataSource =
cmd.ExecuteReader(CommandBehavior.CloseConnection);
DetailsView1.DataBind();
}
}
}
}
試著在 URL 上鍵入:
| http://localhost:43236/FirstInjection/QueryStringInjection.aspx?ID=VINET’ OR 1=1 – |
| 註:http://localhost:43236 是你的 Web Development Server 自動產生的 Port,你必須視情況修改。 |
結果你會看到 CustomerID=”VINET” 以外的 ALFKI 資料列,如下圖:
圖 4:

如果有心人士在 URL 上鍵入 DROP TABLE 或是 INSERT 的 QueryString,將資料任意的刪除或插入惡意的連結 Script (詳見後述的 Mass SQL Injection 一節),那後果不堪設想。
未啟用 Custom Error Page 的漏洞
你應該已經知道,寫 ASP.NET 應用程式的第一道安全手續就是啟用 Custom Error Page 功能,讓駭客們無法透過預設的錯誤網頁來取得不該取得的資訊,若未啟用 Custom Error Page,那麼下圖是可能發生在你的網站中的:
圖 5:

有了這些資訊,具有耐心的駭客,要透過輸入不同的字元來探測整段 SQL 語句就不困難了,防堵的最佳辦法就是啟用 Custom Error Page 設定:
| Web.config |
...............略
<customErrors mode="On" defaultRedirect="DefaultError.htm">
</customErrors>
............略
|
一旦啟用後,錯誤發生時會導向 DefaultError.html,結果變成下面這樣:
圖 6:

檢測你的網頁有無 SQL Injection 的可能性
OK,那有沒有辦法可以檢測現在的網頁是否受 SQL Injection 威脅呢?如果你是網站管理者,而非設計師,那麼你只有依賴現在常見的網頁漏洞檢測工具,對網頁進行黑箱測試,不過提醒你,目前的網頁漏洞測試工具大多是針對 PHP、ASP 所設計的,能測出來的漏洞相當有限,有時即使是安全的網頁,也會因為未實作過濾法(後述),而導致誤判。
如果你是程式設計師,事情就簡單的多了,只要檢視一下程式碼,看看動態組裝 SQL 語句的部份是否有 SQL Injection 即可,圖 007 是一個確認 SQL Injection 是否存在於你的程式中的公式。
圖 7:

只要你的程式中,有 SQL 字串加上使用者輸入值的情況,那麼該網頁存在 SQL Injection 危機的可能性就高達 99.9%。
前輩的叮嚀:防止 SQL Injection 的方法
在數千個網站的入侵事件發生後,許多資安專家提出了各種防範 SQL Injection 的方法,其中不外乎圖 008 的四種。
圖 8:

過濾法可以阻止特定字如【–】、【 OR 】、【’】的輸入,能有效防堵必然成真條件式及錯誤訊息顯示時的漏洞,不過魔高一丈,此法最後仍然遭受破解,透過 SQL 的轉碼函式,駭客可以將部份 SQL 語句做出編碼來逃避偵測,最後突破這道防線。但由於轉碼後的字串相當長,所以只要設計師細心些,搭配 MaxLength 的設定,還是可以讓過濾法奏效,但過濾法其實很脆弱,所以一定要搭配其它的手法方能行之。
下面是一個使用過濾法的例子,利用引入外部 JavaScript 檔案及 Form 的 onSubmit 事件,在送出資料前先檢測擁有 ci Attribute 標示的 text tag,此法可運行於 IE 及 FireFox 上:
Injectiondetect.js
function validateInjection()
{
var i = 0;
for(i = 0; i < document.forms[0].elements.length;i++)
{
if(document.forms[0].elements[i].type == ‘text’ &&
document.forms[0].elements[i].getAttribute(”ci”) != null)
{
var elem = document.forms[0].elements[i];
if(elem.value != null &&
(elem.value.indexOf(’\”) != -1 ||
elem.value.indexOf(’–’) != -1 ||
elem.value.indexOf(’ OR ‘) != -1))
{
alert(’possible injection detected.’)
return false;
}
}
}
return true;
}
.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="DefaultWithFilter.aspx.cs" Inherits="DefaultWithFilter" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>
<script language='javascript' type="text/javascript" src='injectiondetect.js'>
</script>
</head>
<body>
<form id="form1" onsubmit="return validateInjection()" runat="server">
<div>
<table border="1">
<tr>
<td>使用者編號</td>
<td><asp:TextBox ID="TextBox1" ci="true" MaxLength="12"
runat="server"></asp:TextBox></td>
</tr>
<tr>
<td>密碼</td>
<td><asp:TextBox ID="TextBox2" ci="true" MaxLength="12"
runat="server"></asp:TextBox></td>
</tr>
<tr>
<td colspan=2>
<asp:Button ID="Button1" runat="server" Text="登入" onclick="Button1_Click" />
</td>
</tr>
</table>
<asp:Label ID="Label1" runat="server" Text=""></asp:Label>
</div>
</form>
</body>
</html>1 2 3 4 5 下一页