1、服务端对前端传过来的参数值进行类型验证; 2、服务端执行sql,使用参数化传值,而不要使用sql字符串拼接; 3、服务端对前端传过来的数据进行sql关键词过来与检测; 4、在UI录入时,要控制数据的类型和长度、防止SQL注入式攻击,系统提供检测注入式攻击的函数,一旦检测出注入式攻击,该数据即不能提交; 5、业务逻辑层控制,通过在方法内部将SQL关键字用一定的方法屏蔽掉,然后检查数据长度,保证提交SQL时,不会有SQL数据库注入式攻击代码;但是这样处理后,要求UI输出时将屏蔽的字符还原。因此系统提供屏蔽字符 的函数和还原字符的函数。 6、在数据访问层,绝大多数采用存储过程访问数据,调用时以存储过程参数的方式访问,也会很好的防止注入式攻击。 |
public class SqlInjectHelper:System.Web.UI.Page { private static string StrKeyWord = "select|insert|delete|from|count(|drop table|update|truncate| asc(|mid(|char(|xp_cmdshell|exec|master|net local group administrators|net user|or|and"; private static string StrSymbol = ";|(|)|[|]|{|}|%|@|*|‘|!"; private HttpRequest request; public SqlInjectHelper(System.Web.HttpRequest _request) { this.request = _request; } public bool CheckSqlInject() { return CheckRequestQuery() || CheckRequestForm(); } ///<summary> ///检查URL中是否包含Sql注入 /// <param name="_request">当前HttpRequest对象</param> /// <returns>如果包含sql注入关键字,返回:true;否则返回:false</returns> ///</summary> public bool CheckRequestQuery() { if (request.QueryString.Count > 0) { foreach (string sqlParam in this.request.QueryString) { if (sqlParam == "__VIEWSTATE") continue; if (sqlParam == "__EVENTVALIDATION") continue; if (CheckKeyWord(request.QueryString[sqlParam].ToLower())) { return true; } } } return false; } ///<summary> ///检查提交的表单中是否包含Sql注入关键字 /// <param name="_request">当前HttpRequest对象</param> /// <returns>如果包含sql注入关键字,返回:true;否则返回:false</returns> ///</summary> public bool CheckRequestForm() { if (request.Form.Count > 0) { foreach (string sqlParam in this.request.Form) { if (sqlParam == "__VIEWSTATE") continue; if (sqlParam == "__EVENTVALIDATION") continue; if (CheckKeyWord(request.Form[sqlParam])) { return true; } } } return false; } ///<summary> ///检查字符串中是否包含Sql注入关键字 /// <param name="_key">被检查的字符串</param> /// <returns>如果包含sql注入关键字,返回:true;否则返回:false</returns> ///</summary> private static bool CheckKeyWord(string _key) { string[] pattenKeyWord = StrKeyWord.Split(‘|‘); string[] pattenSymbol = StrSymbol.Split(‘|‘); foreach (string sqlParam in pattenKeyWord) { if (_key.Contains(sqlParam + " ") || _key.Contains(" " + sqlParam)) { return true; } } foreach (string sqlParam in pattenSymbol) { if (_key.Contains(sqlParam)) { return true; } } return false; } } |
SqlInjectHelper类中,对request的query参数和form参数进行的检测,没有对cookie的检测,如有需要,可自行加上。
2.1 如果想对整个web站点的所有请求都做sql关键字检测,那就在Global.asax 的 Application_BeginRequest方法中调用
protected void Application_BeginRequest(object sender, EventArgs e) { SqlInjectHelper myCheck = new SqlInjectHelper(Request); bool result = myCheck.CheckSqlInject(); if (result) { Response.ContentType = "text/plain"; Response.Write("您提交的数据有恶意字符!"); Response.End(); } } |
如果只需对某个接口文件的接口进行sql关键字检测,那只需在该文件开始处调用SqlInjectHelper类即可
public class Handler1 : IHttpHandler { public void ProcessRequest(HttpContext context) { SqlInjectHelper myCheck = new SqlInjectHelper(context.Request); bool result = myCheck.CheckSqlInject(); context.Response.ContentType = "text/plain"; context.Response.Write(result?"您提交的数据有恶意字符!":""); context.Response.StatusCode = result ? 500 : 200; } public bool IsReusable { get { return false; } } } |
上面的代码就是对某个一般处理程序(ashx)添加了sql关键字检测。
3.1 _VIEWSTATE
ViewState是ASP.NET中用来保存WEB控件回传时状态值一种机制。在WEB窗体(FORM)的设置为runat="server",这个窗体(FORM)会被附加一个隐藏的属性_VIEWSTATE。_VIEWSTATE中存放了所有控件在ViewState中的状态值。
ViewState是类Control中的一个域,其他所有控件通过继承Control来获得了ViewState功能。它的类型是system.Web.UI.StateBag,一个名称/值的对象集合。
当请求某个页面时,ASP.NET把所有控件的状态序列化成一个字符串,然后做为窗体的隐藏属性送到客户端。当客户端把页面回传时,ASP.NET分析回传的窗体属性,并赋给控件对应的值。
3.2 _EVENTVALIDATION
__EVENTVALIDATION只是用来验证事件是否从合法的页面发送,只是一个数字签名,所以一般很短。
“id”属性为“__EVENTVALIDATION”的隐藏字段是ASP.NET 2.0的新增的安全措施。该功能可以阻止由潜在的恶意用户从浏览器端发送的未经授权的请求。
该类不仅检测了sql常用关键字还有xss攻击的常用关键字
public class SafeHelper { private const string StrRegex = @"<[^>]+?style=[\w]+?:expression\(|\b(alert|confirm|prompt)\b |^\+/v(8|9)|<[^>]*?=[^>]*?&#[^>]*?>|\b(and|or)\b.{1,6}?(=|>|<|\bin\b|\blike\b)|/\*.+?\*/|<\s*script\b| <\s*img\b|\bEXEC\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM| (CREATE|ALTER|DROP|TRUNCATE)\s+(TABLE|DATABASE)"; public static bool PostData() { bool result = false; for (int i = 0; i < HttpContext.Current.Request.Form.Count; i++) { result = CheckData(HttpContext.Current.Request.Form[i].ToString()); if (result) { break; } } return result; } public static bool GetData() { bool result = false; for (int i = 0; i < HttpContext.Current.Request.QueryString.Count; i++) { result = CheckData(HttpContext.Current.Request.QueryString[i].ToString()); if (result) { break; } } return result; } public static bool CookieData() { bool result = false; for (int i = 0; i < HttpContext.Current.Request.Cookies.Count; i++) { result = CheckData(HttpContext.Current.Request.Cookies[i].Value.ToLower()); if (result) { break; } } return result; } public static bool referer() { bool result = false; return result = CheckData(HttpContext.Current.Request.UrlReferrer.ToString()); } public static bool CheckData(string inputData) { if (Regex.IsMatch(inputData, StrRegex)) { return true; } else { return false; } } } |
var Shipcity; ShipCity = Request.form ("ShipCity"); var sql = "select * from OrdersTable where ShipCity = '" + ShipCity + "'"; |
上面的代码中用户将被提示输入一个市县名称。如果用户输入“Guangdong”,则查询将与下面内容相似的查询组成:
SELECT * FROM OrdersTable WHERE ShipCity = 'Guangdong' |
如果用户输入以下内容:
Guangdong'; drop table OrdersTable-- |
脚本则执行以下的查询
SELECT * FROM OrdersTable WHERE ShipCity = 'Guangdong';drop table OrdersTable–' |
分号 (;) 表示一个查询的结束和另一个查询的开始。双连字符 (--) 指示当前行余下的部分是一个注释,应该忽略。如果修改后的代码语法正确,则服务器将执行该代码。SQL Server 处理该语句时,SQL Server 将首先选择 OrdersTable 中的所有记录(其中 ShipCity 为 Guangdong)。然后,SQL Server 将删除 OrdersTable。
只要注入的 SQL 代码语法正确,便无法采用编程方式来检测篡改。因此,必须验证所有用户输入,并仔细检查服务器中执行构造 SQL 命令的代码。
始始终通过测试类型、长度、格式和范围来验证用户输入。实现对恶意输入的预防时,请注意应用程序的体系结构和部署方案。
拒绝包含以下字符的输入:
输入字符 | 在标准SQL中的含义 |
---|---|
; | 查询分隔符 |
, | 字符数据字符串分隔符 |
-- | 注释分隔符 |
/* ...*/ | 注释分隔符。服务器不对/*和*/之间的注释进行处理 |
xp_ | 用于目录扩展存储过程的名称的开头,例如xp_cmdshell |
SQL Server 中的 Parameters 集合提供了类型检查和长度验证。如果使用 Parameters 集合,则输入将被视为文字值而不是可执行代码。使用 Parameters 集合的另一个好处是可以强制执行类型和长度检查。范围以外的值将触发异常。以下代码段显示了如何使用 Parameters 集合:
SqlDataAdapter myCommand = new SqlDataAdapter("AuthorLogin", conn); myCommand.SelectCommand.CommandType = CommandType.StoredProcedure; SqlParameter parm = myCommand.SelectCommand.Parameters.Add("@au_id", SqlDbType.VarChar, 11); parm.Value = Login.Text; |
在此示例中,@au_id 参数被视为文字值而不是可执行代码。将对此值进行类型和长度检查。如果 @au_id 值不符合指定的类型和长度约束,则将引发异常。
存储过程如果使用未筛选的输入,则可能容易受 SQL Injection 攻击。例如,以下代码容易受到攻击:
SqlDataAdapter myCommand = new SqlDataAdapter("LoginStoredProcedure '" + Login.Text + "'", conn); |
如果不能使用存储过程,仍可使用参数,如以下代码示例所示:
SqlDataAdapter myCommand = new SqlDataAdapter( "SELECT au_lname, au_fname FROM Authors WHERE au_id = @au_id", conn); SQLParameter parm = myCommand.SelectCommand.Parameters.Add("@au_id", SqlDbType.VarChar, 11); Parm.Value = Login.Text; |
筛选输入可以删除转义符,这也可能有助于防止 SQL 注入。但由于可引起问题的字符数量很大,因此这并不是一种可靠的防护方法。以下示例可搜索字符串分隔符。
private string SafeSqlLiteral(string inputSQL) { return inputSQL.Replace("'", "''"); } |
请注意,如果要使用 LIKE 子句,还必须对通配符字符进行转义:
s = s.Replace("[", "[[]"); s = s.Replace("%", "[%]"); s = s.Replace("_", "[_]"); |
参考资料
CSRF 攻击的应对之道 https://www.ibm.com/developerworks/cn/web/1102_niugang_csrf/index.html
跨站点请求伪造解决方案 https://www.cnblogs.com/xlyslr/p/5714048.html
SQL 注入攻击与防御 https://www.jianshu.com/p/ba35a7e1c67d
SQL 注入防护指南 https://sqlwiki.netspi.com/misc/references/#sqlserver
SQL 安全参考脚本 https://github.com/nullbind/Powershellery/tree/master/Stable-ish/MSSQL
DVWA 测试环境 https://github.com/ethicalhack3r/DVWA