什么是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 try33 {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 finally46 {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 else58 {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 try14 {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 finally27 {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 else39 {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 try22 {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 finally42 {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
新聞熱點
疑難解答