什麼是SQL注入攻擊?
它是在執行SQL查詢的時候,由于接收了使用者的非法參數進而導緻,所執行的SQL語義與業務邏輯原本所要查詢的語義不相符,進而實作的攻擊。
例如我們經常使用的使用者登入,通常會出現這樣的表單:
使用者名:________________
密 碼:________________
登入
正常情況下,我們需要讓使用者填寫他們自己的使用者名和密碼之後,程式會接收使用者輸入的參數 執行查詢操作,然後根據查詢結果,判斷使用者是否能夠登入。
但是,如果程式員在編寫SQL查詢操作時候,沒有注意SQL注入問題的話,那麼使用者可以通過這個表單很輕易的實作一些非正常的通路,下面我們據具體的例子來說明。
我們先來布局一個簡單的表單:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiInBnaucjMxMDNxATM0cDN4ATNy8CXzADNxAjMvw1M4IjNyMzLcl2Lc12bj5ycn9Gbi52YuAzcldWYtl2Lc9CX6MHc0RHaiojIsJye.jpg)
其中這個表單中包含了使用者名 密碼的輸入文本框,為了大家便于觀看,密碼我沒有使用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下載下傳:https://files.cnblogs.com/webconfig/SQL_Injection_Attack_DEMO.rar
本文出自 低調碼農的筆記簿 http://www.cnblogs.com/webconfig/p/3622498.html 轉載請注明出處,如有謬誤不當之處,歡迎指正拍磚,不勝感謝!!