什么是SQL注入?
SQL注入(SQLi)是一种注入攻击,,可以执行恶意SQL语句。它通过将任意SQL代码插入数据库查询,使攻击者能够完全控制Web应用程序后面的数据库服务器。攻击者可以使用SQL注入漏洞绕过应用程序安全措施;可以绕过网页或Web应用程序的身份验证和授权,并检索整个SQL数据库的内容;还可以使用SQL注入来添加,修改和删除数据库中的记录。
SQL注入漏洞可能会影响使用SQL数据库(如MySQL,Oracle,SQL Server或其他)的任何网站或Web应用程序。犯罪分子可能会利用它来未经授权访问用户的敏感数据:客户信息,个人数据,商业机密,知识产权等。SQL注入攻击是最古老,最流行,最危险的Web应用程序漏洞之一。
SQL注入攻击的类型
SQL注入攻击可以通过多种方式执行。在选择特定攻击方法之前,攻击者可能会观察系统的行为。
带内注入
这是典型的攻击,攻击者可以通过相同的通信通道发起攻击并获得结果。这是通过两种带内技术完成的:
● 基于错误的SQL注入:从显示的错误消息中获取有关数据库的信息
● 基于联合的SQL注入:依赖于攻击者能够将UNION ALL被盗信息的结果与合法结果连接起来。
这两种技术都依赖于攻击者修改应用程序发送的SQL,以及浏览器中显示的错误和返回的信息。如果应用程序开发人员或数据库开发人员无法正确地参数化他们在查询中使用的值,那么它会成功。两者都是试错法,可以检测到错误。
盲注入
也称为推理SQL注入,盲注入攻击不会直接从目标数据库中显示数据;相反,攻击者会仔细检查行为中的间接线索。HTTP响应中的详细信息,某些用户输入的空白网页以及数据库响应某些用户输入需要多长时间,这些都可以是线索,具体取决于攻击者的目标。他们还可以指向攻击者尝试的另一个SQLi攻击途径。
带外注入
这种攻击有点复杂,当攻击者无法在单个直接查询 - 响应攻击中实现其目标时,攻击者可能会使用此攻击。通常,攻击者会制作SQL语句,这些语句在呈现给数据库时会触发数据库系统创建与攻击者控制的外部服务器的连接。以这种方式,攻击者可以收集数据或可能控制数据库的行为。
二阶注入就是一种带外注入攻击。在这种情况下,攻击者将提供SQL注入,该注入将由数据库系统的单独行为存储和执行。当二级系统行为发生时(它可能类似于基于时间的作业或由其他典型管理员或用户使用数据库触发的某些事情)并且执行攻击者的SQL注入,那就是当“伸出”到系统时攻击者控制发生了。
如何防止SQL注入攻击?
以下建议可以帮助防止SQL注入攻击成功:
不要使用动态SQL
避免将用户提供的输入直接放入SQL语句中;最好使用准备好的语句和参数化查询,这样更安全。
不要将敏感数据保留在纯文本中
加密存储在数据库中的私有/机密数据;这样可以提供了另一级保护,以防攻击者成功地排出敏感数据。
限制数据库权限和特权
将数据库用户的功能设置为最低要求;这将限制攻击者在设法获取访问权限时可以执行的操作。
避免直接向用户显示数据库错误
攻击者可以使用这些错误消息来获取有关数据库的信息。
对访问数据库的Web应用程序使用Web应用程序防火墙(WAF)
这为面向Web的应用程序提供了保护,它可以帮助识别SQL注入尝试;根据设置,它还可以帮助防止SQL注入尝试到达应用程序(以及数据库)。
定期测试与数据库交互的Web应用程序
这样做可以帮助捕获可能允许SQL注入的新错误或回归。
将数据库更新为最新的可用修补程序
这可以防止攻击者利用旧版本中存在的已知弱点/错误。
《案列讲解》
SQL Injection:就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有应用程序,将(恶意)的SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。
首先让我们了解什么时候可能发生SQL Injection。假设我们在浏览器中输入URL www.sample.com,由于它只是对页面的简单请求无需对数据库动进行动态请求,所以它不存在SQL Injection,当我们输入www.sample.com?testid=23时,我们在URL中传递变量testid,并且提供值为23,由于它是对数据库进行动态查询的请求(其中?testid=23表示数据库查询变量),所以我们可以该URL中嵌入恶意SQL语句。现在我们知道SQL Injection适用场合,接下来我们将通过具体的例子来说明SQL Injection的应用,这里我们以pubs数据库作为例子。我们通过Web页面查询job表中的招聘信息,job表的设计如下:
图1 jobs表
接着让我们实现Web程序,它根据工作Id(job_id)来查询相应的招聘信息,示意代码如下:
- <span style="color:#333333"><span style="color:gray">/// <summary>
- /// </span><span style="color:green">Handles the Load event of the Page control.
- </span><span style="color:gray">/// </summary>
- /// <param name="sender"></span><span style="color:green">The source of the event.</span><span style="color:gray"></param>
- /// <param name="e"></span><span style="color:green">The </span><span style="color:gray"><see cref="System.EventArgs"/> </span><span style="color:green">instance containing the event data.</span><span style="color:gray"></param>
- </span><span style="color:blue">protected void </span>Page_Load(<span style="color:blue">object </span>sender, <span style="color:#2b91af">EventArgs </span>e)
- {
- <span style="color:blue">if </span>(!IsPostBack)
- {
- <span style="color:green">// Gets departmentId from http request.
- </span><span style="color:blue">string </span>queryString = Request.QueryString[<span style="color:#a31515">"departmentID"</span>];
- <span style="color:blue">if </span>(!<span style="color:blue">string</span>.IsNullOrEmpty(queryString))
- {
- <span style="color:green">// Gets data from database.
- </span>gdvData.DataSource = GetData(queryString.Trim());
- <span style="color:green">// Binds data to gridview.
- </span>gdvData.DataBind();
- }
- }
- }</span>
现在我们已经完成了Web程序,接下来让我们查询相应招聘信息吧。
图2 job表查询结果
如图所示,我们要查询数据库中工作Id值为1的工作信息,而且在页面显示了该工作的Id,Description,Min Lvl和Max Lvl等信息。
现在要求我们实现根据工作Id查询相应工作信息的功能,想必大家很快可以给出解决方案,SQL示意代码如下:
- <span style="color:#333333"><span style="color:blue">SELECT </span>job_id<span style="color:gray">, </span>job_desc<span style="color:gray">, </span>min_lvl<span style="color:gray">, </span>max_lvl
- <span style="color:blue">FROM </span>jobs
- <span style="color:blue">WHERE </span><span style="color:gray">(</span>job_id <span style="color:gray">= </span>1<span style="color:gray">)</span></span>
假设现在要求我们获取Department表中的所有数据,而且必须保留WHERE语句,那我们只要确保WHERE恒真就OK了,SQL示意代码如下:
- <span style="color:#333333"><span style="color:blue">SELECT job_id</span><span style="color:gray">, job_desc, min_lvl, max_lvl
- </span><span style="color:blue">FROM jobs
- WHERE </span><span style="color:gray">(job_id = 1) OR 1 = 1
- </span></span>
上面我们使得WHERE恒真,所以该查询中WHERE已经不起作用了,其查询结果等同于以下SQL语句。
- <span style="color:#333333"><span style="color:blue">SELECT job_id</span><span style="color:gray">, job_desc, min_lvl, max_lvl
- </span><span style="color:blue">FROM jobs
- </span></span>
SQL查询代码实现如下
- <span style="color:#333333"><span style="color:blue">string </span>sql1 = <span style="color:blue">string</span>.Format(
- <span style="color:#a31515">"SELECT job_id, job_desc, min_lvl, max_lvl FROM jobs WHERE job_id='{0}'"</span>, jobId);</span>
现在我们要通过页面请求的方式,让数据库执行我们的SQL语句,我们要在URL中嵌入恶意表达式1=1(或2=2等等),如下URL所示:
http://localhost:3452/ExcelUsingXSLT/Default.aspx?jobid=1'or'1'='1
等效SQL语句如下:
- <span style="color:#333333"><span style="color:blue">SELECT </span>job_id<span style="color:gray">, </span>job_desc<span style="color:gray">, </span>min_lvl<span style="color:gray">, </span>max_lvl
- <span style="color:blue">FROM </span>jobs
- <span style="color:blue">WHERE </span>job_id <span style="color:gray">= </span><span style="color:red">'1' </span><span style="color:gray">OR </span><span style="color:red">'1' </span><span style="color:gray">= </span>1<span style="color:red">'</span></span>
图3 job表查询结果
现在我们把job表中的所有数据都查询出来了,仅仅通过一个简单的恒真表达式就可以进行了一次简单的攻击。
虽然我们把job表的数据都查询出来了,但数据并没有太大的价值,由于我们把该表临时命名为job表,所以接着我们要找出该表真正表名。
首先我们假设表名就是job,然后输入以下URL:
http://localhost:3452/ExcelUsingXSLT/Default.aspx?jobid=1'or 1=(select count(*) from job)--
等效SQL语句如下:
- <span style="color:#333333"><span style="color:blue">SELECT </span>job_id<span style="color:gray">, </span>job_desc<span style="color:gray">, </span>min_lvl<span style="color:gray">, </span>max_lvl
- <span style="color:blue">FROM </span>jobs
- <span style="color:blue">WHERE </span>job_id<span style="color:gray">=</span><span style="color:red">'1'</span><span style="color:gray">or </span>1<span style="color:gray">=(</span><span style="color:blue">select </span><span style="color:magenta">count</span><span style="color:gray">(*) </span><span style="color:blue">from </span>job<span style="color:gray">) </span><span style="color:green">--'</span></span>
图4 job表查询结果
当我们输入了以上URL后,结果服务器返回我们错误信息,这证明了我们的假设是错误的,那我们该感觉到挫败吗?不,其实这里返回了很多信息,首先它证明了该表名不是job,而且它还告诉我们后台数据库是SQL Server,不是MySQL或Oracle,这也设计一个漏洞把错误信息直接返回给了用户。
接下假定表名是jobs,然后输入以下URL:
http://localhost:3452/ExcelUsingXSLT/Default.aspx?jobid=1'or1=(select count(*) from jobs) --
等效SQL语句如下:
- <span style="color:#333333"><span style="color:blue">SELECT </span>job_id<span style="color:gray">, </span>job_desc<span style="color:gray">, </span>min_lvl<span style="color:gray">, </span>max_lvl
- <span style="color:blue">FROM </span>jobs
- <span style="color:blue">WHERE </span>job_id<span style="color:gray">=</span><span style="color:red">'1'</span><span style="color:gray">or </span>1<span style="color:gray">=(</span><span style="color:blue">select </span><span style="color:magenta">count</span><span style="color:gray">(*) </span><span style="color:blue">from </span>jobs<span style="color:gray">) </span><span style="color:green">--'</span></span>
图5 job表查询结果
现在证明了该表名是jobs,这可以迈向成功的一大步,由于我们知道了表名就可以对该表进行增删改操作了,而且我们还可以猜测出更多的表对它们作出修改,一旦修改成功那么这将是一场灾难。
现在大家已经对SQL Injection的攻击有了初步的了解了,接下让我们学习如何防止SQL Injection。
总的来说有以下几点:
1.永远不要信任用户的输入,要对用户的输入进行校验,可以通过正则表达式,或限制长度,对单引号和双"-"进行转换等。
2.永远不要使用动态拼装SQL,可以使用参数化的SQL或者直接使用存储过程进行数据查询存取。
3.永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接。
4.不要把机密信息明文存放,请加密或者hash掉密码和敏感的信息。
5.应用的异常信息应该给出尽可能少的提示,最好使用自定义的错误信息对原始错误信息进行包装,把异常信息存放在独立的表中。
通过正则表达校验用户输入
首先我们可以通过正则表达式校验用户输入数据中是包含:对单引号和双"-"进行转换等字符。
然后继续校验输入数据中是否包含SQL语句的保留字,如:WHERE,EXEC,DROP等。
现在让我们编写正则表达式来校验用户的输入吧,正则表达式定义如下:
- <span style="color:#333333"><span style="color:blue">private static readonly </span><span style="color:#2b91af">Regex </span>RegSystemThreats =
- <span style="color:blue">new </span><span style="color:#2b91af">Regex</span>(<span style="color:#a31515">@"s?ors*|s?;s?|s?drops|s?grants|^'|s?--|s?unions|s?deletes|s?truncates|" </span>+
- <span style="color:#a31515">@"s?sysobjectss?|s?xp_.*?|s?sysloginss?|s?sysremotes?|s?sysuserss?|s?sysxloginss?|s?sysdatabasess?|s?aspnet_.*?|s?execs?"</span>,
- <span style="color:#2b91af">RegexOptions</span>.Compiled | <span style="color:#2b91af">RegexOptions</span>.IgnoreCase);</span>
上面我们定义了一个正则表达式对象RegSystemThreats,并且给它传递了校验用户输入的正则表达式。
由于我们已经完成了对用户输入校验的正则表达式了,接下来就是通过该正则表达式来校验用户输入是否合法了,由于.NET已经帮我们实现了判断字符串是否匹配正则表达式的方法——IsMatch(),所以我们这里只需给传递要匹配的字符串就OK了。
示意代码如下:
- <span style="color:#333333"><span style="color:gray">/// <summary>
- /// </span><span style="color:green">A helper method to attempt to discover [known] SqlInjection attacks.
- </span><span style="color:gray">/// </summary>
- /// <param name="whereClause"></span><span style="color:green">string of the whereClause to check</span><span style="color:gray"></param>
- /// <returns></span><span style="color:green">true if found, false if not found </span><span style="color:gray"></returns>
- </span><span style="color:blue">public static bool </span>DetectSqlInjection(<span style="color:blue">string </span>whereClause)
- {
- <span style="color:blue">return </span>RegSystemThreats.IsMatch(whereClause);
- }
- <span style="color:gray">/// <summary>
- /// </span><span style="color:green">A helper method to attempt to discover [known] SqlInjection attacks.
- </span><span style="color:gray">/// </summary>
- /// <param name="whereClause"></span><span style="color:green">string of the whereClause to check</span><span style="color:gray"></param>
- /// <param name="orderBy"></span><span style="color:green">string of the orderBy clause to check</span><span style="color:gray"></param>
- /// <returns></span><span style="color:green">true if found, false if not found </span><span style="color:gray"></returns>
- </span><span style="color:blue">public static bool </span>DetectSqlInjection(<span style="color:blue">string </span>whereClause, <span style="color:blue">string </span>orderBy)
- {
- <span style="color:blue">return </span>RegSystemThreats.IsMatch(whereClause) || RegSystemThreats.IsMatch(orderBy);
- }</span>
现在我们完成了校验用的正则表达式,接下来让我们需要在页面中添加校验功能。
- <span style="color:#333333"><span style="color:gray">/// <summary>
- /// </span><span style="color:green">Handles the Load event of the Page control.
- </span><span style="color:gray">/// </summary>
- /// <param name="sender"></span><span style="color:green">The source of the event.</span><span style="color:gray"></param>
- /// <param name="e"></span><span style="color:green">The </span><span style="color:gray"><see cref="System.EventArgs"/> </span><span style="color:green">instance containing the event data.</span><span style="color:gray"></param>
- </span><span style="color:blue">protected void </span>Page_Load(<span style="color:blue">object </span>sender, <span style="color:#2b91af">EventArgs </span>e)
- {
- <span style="color:blue">if </span>(!IsPostBack)
- {
- <span style="color:green">// Gets departmentId from http request.
- </span><span style="color:blue">string </span>queryString = Request.QueryString[<span style="color:#a31515">"jobId"</span>];
- <span style="color:blue">if </span>(!<span style="color:blue">string</span>.IsNullOrEmpty(queryString))
- {
- <span style="color:blue">if </span>(!DetectSqlInjection(queryString) && !DetectSqlInjection(queryString, queryString))
- {
- <span style="color:green">// Gets data from database.
- </span>gdvData.DataSource = GetData(queryString.Trim());
- <span style="color:green">// Binds data to gridview.
- </span>gdvData.DataBind();
- }
- <span style="color:blue">else
- </span>{
- <span style="color:blue">throw new </span><span style="color:#2b91af">Exception</span>(<span style="color:#a31515">"Please enter correct field"</span>);
- }
- }
- }
- }</span>
当我们再次执行以下URL时,被嵌入的恶意语句被校验出来了,从而在一定程度上防止了SQL Injection。
http://localhost:3452/ExcelUsingXSLT/Default.aspx?jobid=1'or'1'='1
图6 添加校验查询结果
但使用正则表达式只能防范一些常见或已知SQL Injection方式,而且每当发现有新的攻击方式时,都要对正则表达式进行修改,这可是吃力不讨好的工作。
通过参数化存储过程进行数据查询存取
首先我们定义一个存储过程根据jobId来查找jobs表中的数据。
- <span style="color:#333333"><span style="color:green">-- =============================================
- -- Author: JKhuang
- -- Create date: 12/31/2011
- -- Description: Get data from jobs table by specified jobId.
- -- =============================================
- </span><span style="color:blue">ALTER PROCEDURE </span>[dbo]<span style="color:gray">.</span>[GetJobs]
- <span style="color:green">-- ensure that the id type is int
- </span>@jobId <span style="color:blue">INT
- AS
- BEGIN
- </span><span style="color:green">-- SET NOCOUNT ON;
- </span><span style="color:blue">SELECT </span>job_id<span style="color:gray">, </span>job_desc<span style="color:gray">, </span>min_lvl<span style="color:gray">, </span>max_lvl
- <span style="color:blue">FROM </span>dbo<span style="color:gray">.</span>jobs
- <span style="color:blue">WHERE </span>job_id <span style="color:gray">= </span>@jobId
- <span style="color:blue">GRANT EXECUTE ON </span>GetJobs <span style="color:blue">TO </span>pubs
- <span style="color:blue">END</span></span>
接着修改我们的Web程序使用参数化的存储过程进行数据查询。
- <span style="color:#333333"><span style="color:blue">using </span>(var com = <span style="color:blue">new </span><span style="color:#2b91af">SqlCommand</span>(<span style="color:#a31515">"GetJobs"</span>, con))
- {
- <span style="color:green">// Uses store procedure.
- </span>com.CommandType = <span style="color:#2b91af">CommandType</span>.StoredProcedure;
- <span style="color:green">// Pass jobId to store procedure.
- </span>com.Parameters.Add(<span style="color:#a31515">"@jobId"</span>, <span style="color:#2b91af">SqlDbType</span>.Int).Value = jobId;
- com.Connection.Open();
- gdvData.DataSource = com.ExecuteScalar();
- gdvData.DataBind();
- }</span>
现在我们通过参数化存储过程进行数据库查询,这里我们把之前添加的正则表达式校验注释掉。
图7 存储过程查询结果
大家看到当我们试图在URL中嵌入恶意的SQL语句时,参数化存储过程已经帮我们校验出传递给数据库的变量不是整形,而且使用存储过程的好处是我们还可以很方便地控制用户权限,我们可以给用户分配只读或可读写权限。
但我们想想真的有必要每个数据库操作都定义成存储过程吗?而且那么多的存储过程也不利于日常的维护。
参数化SQL语句
还是回到之前动态拼接SQL基础上,我们知道一旦有恶意SQL代码传递过来,而且被拼接到SQL语句中就会被数据库执行,那么我们是否可以在拼接之前进行判断呢?——命名SQL参数。
- <span style="color:#333333"><span style="color:blue">string </span>sql1 = <span style="color:blue">string</span>.Format(<span style="color:#a31515">"SELECT job_id, job_desc, min_lvl, max_lvl FROM jobs WHERE job_id = @jobId"</span>);
- <span style="color:blue">using </span>(var con = <span style="color:blue">new </span><span style="color:#2b91af">SqlConnection</span>(<span style="color:#2b91af">ConfigurationManager</span>.ConnectionStrings[<span style="color:#a31515">"SQLCONN1"</span>].ToString()))
- <span style="color:blue">using </span>(var com = <span style="color:blue">new </span><span style="color:#2b91af">SqlCommand</span>(sql1, con))
- {
- <span style="color:green">// Pass jobId to sql statement.
- </span>com.Parameters.Add(<span style="color:#a31515">"@jobId"</span>, <span style="color:#2b91af">SqlDbType</span>.Int).Value = jobId;
- com.Connection.Open();
- gdvData.DataSource = com.ExecuteReader();
- gdvData.DataBind();
- }</span>
图8 参数化SQL查询结果
这样我们就可以避免每个数据库操作(尤其一些简单数据库操作)都编写存储过程了,而且当用户具有数据库中jobs表的读权限才可以执行该SQL语句。
添加新架构
数据库架构是一个独立于数据库用户的非重复命名空间,您可以将架构视为对象的容器(类似于.NET中的命名空间)。
首先我们右击架构文件夹,然后新建架构。
图9 添加HumanResource架构
上面我们完成了在pubs数据库中添加HumanResource架构,接着把jobs表放到HumanResource架构中。
图 10 修改jobs表所属的架构
当我们再次执行以下SQL语句时,SQL Server提示jobs无效,这是究竟什么原因呢?之前还运行的好好的。
<span style="color:#333333"><span style="color:blue">SELECT </span>job_id<span style="color:gray">, </span>job_desc<span style="color:gray">, </span>min_lvl<span style="color:gray">, </span>max_lvl <span style="color:blue">FROM </span>jobs</span>
图 11 查询输出
当我们输入完整的表名“架构名.对象名”(HumanResource.jobs)时,SQL语句执行成功。
<span style="color:#333333"><span style="color:blue">SELECT </span>job_id<span style="color:gray">, </span>job_desc<span style="color:gray">, </span>min_lvl<span style="color:gray">, </span>max_lvl <span style="color:blue">FROM </span>HumanResource<span style="color:gray">.</span>jobs</span>
为什么之前我们执行SQL语句时不用输入完整表名dbo.jobs也可以执行呢?
这是因为默认的架构(default schema)是dbo,当只输入表名时,Sql Server会自动加上当前登录用户的默认的架构(default schema)——dbo。
由于我们使用自定义架构,这也降低了数据库表名被猜测出来的可能性。
LINQ to SQL
前面使用了存储过程和参数化查询,这两种方法都是非常常用的,而针对于.NET Framework的ORM框架也有很多,如:NHibernate,Castle和Entity Framework,这里我们使用比较简单LINQ to SQL。
图 12 添加jobs.dbml文件
- <span style="color:#333333">var dc = <span style="color:blue">new </span><span style="color:#2b91af">pubsDataContext</span>();
- <span style="color:blue">int </span>result;
- <span style="color:green">// Validates jobId is int or not.
- </span><span style="color:blue">if </span>(<span style="color:blue">int</span>.TryParse(jobId, <span style="color:blue">out </span>result))
- {
- gdvData.DataSource = dc.jobs.Where(p => p.job_id == result);
- gdvData.DataBind();
- }</span>
相比存储过程和参数化查询,LINQ to SQL我们只需添加jobs.dbml,然后使用LINQ对表进行查询就OK了。
总结
我们在本文中介绍了SQL Injection的基本原理,通过介绍什么是SQLInjection,怎样进行SQL Injection和如何防范SQL Injection。通过一些程序源码对SQL的攻击进行了细致的分析,使我们对SQL Injection机理有了一个深入的认识,作为一名Web应用开发人员,一定不要盲目相信用户的输入,而要对用户输入的数据进行严格的校验处理,否则的话,SQL Injection将会不期而至。
本文来源于:SQL注入案列讲解-变化吧
特别声明:以上文章内容仅代表作者本人观点,不代表变化吧观点或立场。如有关于作品内容、版权或其它问题请于作品发表后的30日内与变化吧联系。
- 赞助本站
- 微信扫一扫
-
- 加入Q群
- QQ扫一扫
-
评论