SQL注入的防范措施

转载自:https://blog.csdn.net/qq_35420342/article/details/80380382

经过正则表达校验用户输入

首先咱们能够经过正则表达式校验用户输入数据中是包含:对单引号和双"-"进行转换等字符。html

而后继续校验输入数据中是否包含SQL语句的保留字,如:WHERE,EXEC,DROP等。正则表达式

如今让咱们编写正则表达式来校验用户的输入吧,正则表达式定义以下:sql

 

private static readonly Regex RegSystemThreats =
        new Regex(@"\s?or\s*|\s?;\s?|\s?drop\s|\s?grant\s|^'|\s?--|\s?union\s|\s?delete\s|\s?truncate\s|" +
            @"\s?sysobjects\s?|\s?xp_.*?|\s?syslogins\s?|\s?sysremote\s?|\s?sysusers\s?|\s?sysxlogins\s?|\s?sysdatabases\s?|\s?aspnet_.*?|\s?exec\s?",
            RegexOptions.Compiled | RegexOptions.IgnoreCase);

上面咱们定义了一个正则表达式对象RegSystemThreats,而且给它传递了校验用户输入的正则表达式。数据库

因为咱们已经完成了对用户输入校验的正则表达式了,接下来就是经过该正则表达式来校验用户输入是否合法了,因为.NET已经帮咱们实现了判断字符串是否匹配正则表达式的方法——IsMatch(),因此咱们这里只需给传递要匹配的字符串就OK了。架构

示意代码以下:框架

 

/// <summary>
/// A helper method to attempt to discover [known] SqlInjection attacks.  
/// </summary>
/// <param name="whereClause">string of the whereClause to check</param>
/// <returns>true if found, false if not found </returns>
public static bool DetectSqlInjection(string whereClause)
{
    return RegSystemThreats.IsMatch(whereClause);
}

/// <summary>
/// A helper method to attempt to discover [known] SqlInjection attacks.  
/// </summary>
/// <param name="whereClause">string of the whereClause to check</param>
/// <param name="orderBy">string of the orderBy clause to check</param>
/// <returns>true if found, false if not found </returns>
public static bool DetectSqlInjection(string whereClause, string orderBy)
{
    return RegSystemThreats.IsMatch(whereClause) || RegSystemThreats.IsMatch(orderBy);
}

 

如今咱们完成了校验用的正则表达式,接下来让咱们须要在页面中添加校验功能。.net

/// <summary>
/// Handles the Load event of the Page control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        // Gets departmentId from http request.
        string queryString = Request.QueryString["jobId"];
        if (!string.IsNullOrEmpty(queryString))
        {
            if (!DetectSqlInjection(queryString) && !DetectSqlInjection(queryString, queryString))
            {
                // Gets data from database.
                gdvData.DataSource = GetData(queryString.Trim());

                // Binds data to gridview.
                gdvData.DataBind();
            }
            else
            {
                throw new Exception("Please enter correct field");
            }
        }
    }
}

 

当咱们再次执行如下URL时,被嵌入的恶意语句被校验出来了,从而在必定程度上防止了SQL Injection。3d

http://localhost:3452/ExcelUsingXSLT/Default.aspx?jobid=1'or'1'='1orm

 

sqlinjection9

图6 添加校验查询结果htm

 

但使用正则表达式只能防范一些常见或已知SQL Injection方式,并且每当发现有新的攻击方式时,都要对正则表达式进行修改,这但是吃力不讨好的工做。

 

经过参数化存储过程进行数据查询存取

首先咱们定义一个存储过程根据jobId来查找jobs表中的数据。

 

-- =============================================
-- Author:        JKhuang
-- Create date: 12/31/2011
-- Description:    Get data from jobs table by specified jobId.
-- =============================================
ALTER PROCEDURE [dbo].[GetJobs]
    -- ensure that the id type is int
    @jobId INT
AS
BEGIN
--    SET NOCOUNT ON;
    SELECT job_id, job_desc, min_lvl, max_lvl
    FROM dbo.jobs
    WHERE job_id = @jobId
    GRANT EXECUTE ON GetJobs TO pubs 
END

 

接着修改咱们的Web程序使用参数化的存储过程进行数据查询。

 

using (var com = new SqlCommand("GetJobs", con))
{
    // Uses store procedure.
    com.CommandType = CommandType.StoredProcedure;

    // Pass jobId to store procedure.
    com.Parameters.Add("@jobId", SqlDbType.Int).Value = jobId;
    com.Connection.Open();
    gdvData.DataSource = com.ExecuteScalar();
    gdvData.DataBind(); 
}

 

如今咱们经过参数化存储过程进行数据库查询,这里咱们把以前添加的正则表达式校验注释掉。

 

sqlinjection10

图7 存储过程查询结果

 

你们看到当咱们试图在URL中嵌入恶意的SQL语句时,参数化存储过程已经帮咱们校验出传递给数据库的变量不是整形,并且使用存储过程的好处是咱们还能够很方便地控制用户权限,咱们能够给用户分配只读或可读写权限。

但咱们想一想真的有必要每一个数据库操做都定义成存储过程吗?并且那么多的存储过程也不利于平常的维护。

 

参数化SQL语句

仍是回到以前动态拼接SQL基础上,咱们知道一旦有恶意SQL代码传递过来,并且被拼接到SQL语句中就会被数据库执行,那么咱们是否能够在拼接以前进行判断呢?——命名SQL参数。

 

string sql1 = string.Format("SELECT job_id, job_desc, min_lvl, max_lvl FROM jobs WHERE job_id = @jobId");
using (var con = new SqlConnection(ConfigurationManager.ConnectionStrings["SQLCONN1"].ToString()))
using (var com = new SqlCommand(sql1, con))
{
    // Pass jobId to sql statement.
    com.Parameters.Add("@jobId", SqlDbType.Int).Value = jobId;
    com.Connection.Open();
    gdvData.DataSource = com.ExecuteReader();
    gdvData.DataBind(); 
}

sqlinjection10

图8 参数化SQL查询结果

这样咱们就能够避免每一个数据库操做(尤为一些简单数据库操做)都编写存储过程了,并且当用户具备数据库中jobs表的读权限才能够执行该SQL语句。

 

添加新架构

数据库架构是一个独立于数据库用户的非重复命名空间,您能够将架构视为对象的容器(相似于.NET中的命名空间)。

首先咱们右击架构文件夹,而后新建架构。

 

clip_image002

sqlinjection12

图9 添加HumanResource架构

 

上面咱们完成了在pubs数据库中添加HumanResource架构,接着把jobs表放到HumanResource架构中。

 

sqlinjection15

sqlinjection13

图 10 修改jobs表所属的架构

 

当咱们再次执行如下SQL语句时,SQL Server提示jobs无效,这是究竟什么缘由呢?以前还运行的好好的。

 

SELECT job_id, job_desc, min_lvl, max_lvl FROM jobs

 

sqlinjection14

图 11 查询输出

 

当咱们输入完整的表名“架构名.对象名”(HumanResource.jobs)时,SQL语句执行成功。

SELECT job_id, job_desc, min_lvl, max_lvl FROM HumanResource.jobs

 

sqlinjection16

 

为何以前咱们执行SQL语句时不用输入完整表名dbo.jobs也能够执行呢?

这是由于默认的架构(default schema)是dbo,当只输入表名时,Sql Server会自动加上当前登陆用户的默认的架构(default schema)——dbo。

因为咱们使用自定义架构,这也下降了数据库表名被猜想出来的可能性。

 

LINQ to SQL

前面使用了存储过程和参数化查询,这两种方法都是很是经常使用的,而针对于.NET Framework的ORM框架也有不少,如:NHibernate,Castle和Entity Framework,这里咱们使用比较简单LINQ to SQL。

 

sqlinjection17

图 12 添加jobs.dbml文件

 

var dc = new pubsDataContext();
int result;

// Validates jobId is int or not.
if (int.TryParse(jobId, out result))
{
    gdvData.DataSource = dc.jobs.Where(p => p.job_id == result);
    gdvData.DataBind();
}

 

相比存储过程和参数化查询,LINQ to SQL咱们只需添加jobs.dbml,而后使用LINQ对表进行查询就OK了。

 

 

摘自:http://www.cnblogs.com/rush/archive/2011/12/31/2309203.html