ASP.NET中的SQL注入攻擊與防護


什么是SQL注入攻擊?

它是在執行SQL查詢的時候,由於接收了用戶的非法參數從而導致,所執行的SQL語義與業務邏輯原本所要查詢的語義不相符,從而實現的攻擊。

例如我們經常使用的用戶登錄,通常會出現這樣的表單:

用戶名:________________

密   碼:________________

                                登錄

正常情況下,我們需要讓用戶填寫他們自己的用戶名和密碼之后,程序會接收用戶輸入的參數 執行查詢操作,然后根據查詢結果,判斷用戶是否能夠登錄。

但是,如果程序員在編寫SQL查詢操作時候,沒有注意SQL注入問題的話,那么用戶可以通過這個表單很輕易的實現一些非正常的訪問,下面我們據具體的例子來說明。

 

我們先來布局一個簡單的表單:

 

其中這個表單中包含了用戶名 密碼的輸入文本框,為了大家便於觀看,密碼我沒有使用password,明文顯示。其中防止注入的復選框是為了我寫第二段 防止注入的登錄邏輯時,可以用一個按鈕來表示,而用了單選框是否被選中來進行區別。

 

我們先來看第一段登錄代碼:

 1     //聲明連接
 2     private static OleDbConnection ConnAcc;
 3     protected void Page_Load(object sender, EventArgs e)
 4     {
 5 
 6     }
 7 
 8     /// <summary>
 9     /// 按鈕點擊事件
10     /// </summary>
11     /// <param name="sender"></param>
12     /// <param name="e"></param>
13     protected void Button1_Click(object sender, EventArgs e)
14     {
15         string name = tbx_name.Text;
16         string pwd = tbx_pwd.Text;
17 
18         UserLogin(name, pwd);
19     }
20 
21 
22 
23     /// <summary>
24     /// 第一種登錄驗證操作
25     /// </summary>
26     /// <param name="_loginname"></param>
27     /// <param name="_loginpwd"></param>
28     private void UserLogin(string _loginname,string _loginpwd)
29     {
30         DataSet ds = new DataSet(); //聲明數據集
31         string sql = "select * from Users Where rg_LoginName='" + _loginname + "' and rg_LoginPwd='" + _loginpwd + "'"; //創建查詢語句
32         try
33         {
34 
35             Open(); //打開連接
36             OleDbCommand comm = new OleDbCommand(sql, ConnAcc); //創建查詢
37             new OleDbDataAdapter(comm).Fill(ds, 0, 1, "Users"); //填充數據集
38         }
39         catch (OleDbException exception)
40         {
41             ds = null;
42             Label1.Text = exception.Message; //異常處理
43             return;
44         }
45         finally
46         {
47             Free();
48         }
49         if (ds != null && ds.Tables[0].Rows.Count > 0) //如果數據集中有記錄 代表輸入的用戶名密碼組合正確
50         {
51             Label1.Text = "用戶[" + tbx_name.Text + "]登錄成功!"; //顯示
52 
53             //int uid = int.Parse(ds.Tables[0].Rows[0]["rg_ID"].ToString());
54             //保存UID等用戶信息到Session或者cookies中
55             //由於本案例重點是登錄身份驗證通過環節,因此此處邏輯可以不繼續寫
56         }
57         else
58         {
59             Label1.Text = "用戶名或密碼錯誤";
60         }
61     }
62 
63 
64 
65 
66     /// <summary>
67     /// 打開數據庫連接
68     /// </summary>
69     private static void Open()
70     {
71         ConnAcc = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + HttpContext.Current.Server.MapPath("~/App_Data/db.mdb") + ";");
72         if (ConnAcc.State == ConnectionState.Closed)
73         {
74             ConnAcc.Open();
75         }
76     }
77 
78 
79     /// <summary>
80     /// 關閉連接
81     /// </summary>
82     private static void Free()
83     {
84         if (ConnAcc != null)
85         {
86             ConnAcc.Close();
87             ConnAcc.Dispose();
88             ConnAcc = null;
89         }
90     }

 

我在數據庫中添加了一條記錄 用戶名為admin,密碼為123456

然后我們來看一下登錄的實際效果:

OK!登錄成功!看起來一切都很圓滿,但是事實上是這樣嗎?聰明的你一定會想到,如果就此結束了,那本文就毫無意義了,而且這種登錄我剛上學的時候就會了。

沒錯,這只是看上去功能實現了而已,但事實上,這種登錄的判斷邏輯存在着很大的SQL注入風險,下面我就以此段代碼為例,演示一個簡單的SQL注入的經典例子。

剛才我們登錄的時候是輸入的用戶名密碼,密碼如果與用戶名不匹配就無法登錄成功,那么當我不知道密碼的情況下,試試下面的這個組合:

看到這里你貌似瞬間懂了,然后驚出一身冷汗,然后背后隱隱發涼,為什么不輸入密碼也能登錄成功?那么我在程度代碼這段加上一個斷點調試一下相比你就明白了。

 

在填充數據集之前,加入斷點,讓程序運行時暫停在這個位置。

由於我輸入的用戶名為:admin,密碼並沒有輸入正確的123456,而是輸入的 ' or '1'='1  這意味這什么呢?請看下面的變量跟蹤信息:

 

 

這是SQL語句變成了select * from Users Where rg_LoginName='admin' and rg_LoginPwd='' or '1'='1'

我們查詢原本的意思是:select * from Users Where rg_LoginName=用戶名 and rg_LoginPwd=密碼

而現在的意思就變成了 select * from Users Where rg_LoginName='admin' and rg_LoginPwd=空 or '1'='1'

簡單的說就是相當於用戶通過輸入的密碼中帶有SQL語義的值,變相修改了我們的查詢語句,在本例中,無論用戶是否知道密碼,他只要在密碼中輸入' or '1'='1',就一定能夠登錄成功,因為有了 or '1'='1'的恆成立,所以前面的判斷語句統統失效。這就是非常典型的SQL注入攻擊,通過此種方法,可以進入后台,篡改數據等操作。

 

除此以外,包括頁面間傳參,搜索的時候都有可能被多種形式的注入,這是一個相當危險的安全隱患,使你得所有用戶身份驗證形同虛設。

 

那么如何避免SQL語句的注入呢?

一個最簡單快捷的方法就是過濾單引號,我們來修改登錄的代碼

 1     /// <summary>
 2     /// 第一種登錄驗證操作
 3     /// </summary>
 4     /// <param name="_loginname"></param>
 5     /// <param name="_loginpwd"></param>
 6     private void UserLogin(string _loginname,string _loginpwd)
 7     {
 8         _loginname = _loginname.Replace("'", ""); //過濾用戶輸入參數中的單引號
 9         _loginpwd = _loginpwd.Replace("'", ""); //過濾用戶輸入參數中的單引號
10 
11         DataSet ds = new DataSet(); //聲明數據集
12         string sql = "select * from Users Where rg_LoginName='" + _loginname + "' and rg_LoginPwd='" + _loginpwd + "'"; //創建查詢語句
13         try
14         {
15 
16             Open(); //打開連接
17             OleDbCommand comm = new OleDbCommand(sql, ConnAcc); //創建查詢
18             new OleDbDataAdapter(comm).Fill(ds, 0, 1, "Users"); //填充數據集
19         }
20         catch (OleDbException exception)
21         {
22             ds = null;
23             Label1.Text = exception.Message; //異常處理
24             return;
25         }
26         finally
27         {
28             Free();
29         }
30         if (ds != null && ds.Tables[0].Rows.Count > 0) //如果數據集中有記錄 代表輸入的用戶名密碼組合正確
31         {
32             Label1.Text = "用戶[" + tbx_name.Text + "]登錄成功!"; //顯示
33 
34             //int uid = int.Parse(ds.Tables[0].Rows[0]["rg_ID"].ToString());
35             //保存UID等用戶信息到Session或者cookies中
36             //由於本案例重點是登錄身份驗證通過環節,因此此處邏輯可以不繼續寫
37         }
38         else
39         {
40             Label1.Text = "用戶名或密碼錯誤";
41         }
42     }

 

通過把用戶輸入的單引號進行過濾,那么他在輸入中所帶的SQL語義的值 就不成立了,

我們繼續使用剛才的組合:admin,' or '1'='1  來斷點查看下變量:

 

 

這個時候我們可以發現,rg_LoginPwd=' or 1=1'  在把用戶輸入的單引號過濾掉之后 用戶輸入的密碼就是 or 1=1(被單引號括起來,被當作字符串處理) 不具有任何的SQL語義了,當然這個密碼是不正確的,因此就不會出現SQL注入的問題了

 

然而利用過濾單引號的方式,只能在一定程度上避免SQL注入的攻擊,卻並不能100%的保證安全,最安全的做法就是使用微軟提供的參數數組的形式進行提交命令。下面我們再來寫一個登錄的方法。

 1 /// <summary>
 2     /// 第二種登錄驗證操作(參數數組形式)
 3     /// </summary>
 4     /// <param name="_loginname"></param>
 5     /// <param name="_loginpwd"></param>
 6     private void UserLoginSafeMode(string _loginname, string _loginpwd)
 7     {
 8         DataSet ds = new DataSet();
 9         string sql = "select * from Users Where rg_LoginName=@rg_LoginName and rg_LoginPwd=@rg_LoginPwd"; //創建查詢語句
10 
11         //創建參數數組
12         OleDbParameter[] parameters ={
13             new OleDbParameter("@rg_LoginName",OleDbType.VarChar),
14             new OleDbParameter("@rg_LoginPwd",OleDbType.VarChar)
15         };
16 
17         //參數數組賦值
18         parameters[0].Value = _loginname;
19         parameters[1].Value = _loginpwd;
20 
21         try
22         {
23             Open();
24             OleDbCommand comm = new OleDbCommand(sql, ConnAcc);
25             if (parameters != null)
26             {
27                 //向comm中插入參數
28                 foreach (OleDbParameter p in parameters)
29                 {
30                     comm.Parameters.Add(p);
31                 }
32             }
33             new OleDbDataAdapter(comm).Fill(ds, 0, 1, "Users");
34         }
35         catch (OleDbException exception)
36         {
37             ds = null;
38             Label1.Text = exception.Message; //異常處理
39             return;
40         }
41         finally
42         {
43             Free();
44         }
45         if (ds != null && ds.Tables[0].Rows.Count > 0) //如果數據集中有記錄 代表輸入的用戶名密碼組合正確
46         {
47             Label1.Text = "用戶[" + tbx_name.Text + "]登錄成功!"; //顯示
48 
49             //int uid = int.Parse(ds.Tables[0].Rows[0]["rg_ID"].ToString());
50             //保存UID等用戶信息到Session或者cookies中
51             //由於本案例重點是登錄身份驗證通過環節,因此此處邏輯可以不繼續寫
52         }
53         else
54         {
55             Label1.Text = "用戶名或密碼錯誤";
56         }
57     }

 

你一定會細心的注意到,與第一個登錄方法不同的地方時,在聲明了sql語句之后,又聲明了一個參數數組,並且賦值,在comm中遍歷插入。通過這樣的方式,我們無需過濾單引號,參數數組會把那些帶有語義的SQL語句當作字符串值來處理,從而絕對的從根本上避免了SQL注入攻擊

 

 

OK,關於SQL注入攻擊與防范就說到這里,關於此類話題還有很多很多,不過我們只要知道使用參數數組的方式提交查詢命名就可以從根本上避免此類問題的發生,具體的其他關於SQL注入的問題,感興趣的同學可以自行在網上查找資料,有什么疑問歡迎留言

 

本文的DEMO下載:http://files.cnblogs.com/webconfig/SQL_Injection_Attack_DEMO.rar

 

本文出自 低調碼農的筆記簿 http://www.cnblogs.com/webconfig/p/3622498.html 轉載請注明出處,如有謬誤不當之處,歡迎指正拍磚,不勝感謝!!

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM