sql注入

来源:互联网 发布:java微服务架构有哪些 编辑:程序博客网 时间:2024/06/03 15:42

1  #{value};

Select  * from user where id=#{value};

如果id传入的值为10,在sql执行的时候会默认把10当作为字符串的形式!

  

字符串不是可执行的命令!所以可以防止sql注入!

2 %${value}% 

  表示为字符串的拼接

  

  sql执行的时候就会变为(没有引号了)

 

 

  我们可以将上面的变为防sql注入:如下所示

    

3  sql注入定义

SQL Injection:就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。

具体来说,它是利用现有应用程序,将(恶意)的SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。

4 接下让我们学习如何防止SQL Injection

总的来说有以下几点:

1.永远不要信任用户的输入,要对用户的输入进行校验,可以通过正则表达式,或限制长度,对单引号和双"-"进行转换等。

2.永远不要使用动态拼装SQL,可以使用参数化的SQL或者直接使用存储过程进行数据查询存取。

3.永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接。

4.不要把机密信息明文存放,请加密或者hash掉密码和敏感的信息。

5.应用的异常信息应该给出尽可能少的提示,最好使用自定义的错误信息对原始错误信息进行包装,把异常信息存放在独立的表中。


细解:

                 

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

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

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

现在让我们编写正则表达式来校验用户的输入吧,正则表达式定义如下:

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);}

现在我们完成了校验用的正则表达式,接下来让我们需要在页面中添加校验功能。

/// <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。

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

sqlinjection9

图6 添加校验查询结果

但使用正则表达式只能防范一些常见或已知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 INTASBEGIN--    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了。


如何防御SQL注入

对于服务器配置层面的防范,应该保证生产环境的Webserver是关闭错误信息的,比如PHP在生产环境的配置文件php.ini中的display_errors应该设置为Off,这样就关闭了错误提示,下面我们更多的从编码的角度来看看如何防范SQL注入。


上面用两个实例分析了SQL注入攻击的技巧,可以看到,但凡有SQL注入漏洞的程序,都是因为程序要接受来自客户端用户输入的变量或URL传递的参数,并且这个变量或参数是组成SQL语句的一部分,对于用户输入的内容或传递的参数,我们应该要时刻保持警惕,这是安全领域里的「外部数据不可信任」的原则,纵观Web安全领域的各种攻击方式,大多数都是因为开发者违反了这个原则而导致的,所以自然能想到的,就是从变量的检测、过滤、验证下手,确保变量是开发者所预想的。


1、检查变量数据类型和格式

如果你的SQL语句是类似where id={$id}这种形式,数据库里所有的id都是数字,那么就应该在SQL被执行前,检查确保变量id是int类型;如果是接受邮箱,那就应该检查并严格确保变量一定是邮箱的格式,其他的类型比如日期、时间等也是一个道理。总结起来:只要是有固定格式的变量,在SQL语句执行前,应该严格按照固定格式去检查,确保变量是我们预想的格式,这样很大程度上可以避免SQL注入攻击。


比如,我们前面接受username参数例子中,我们的产品设计应该是在用户注册的一开始,就有一个用户名的规则,比如5-20个字符,只能由大小写字母、数字以及一些安全的符号组成,不包含特殊字符。此时我们应该有一个check_username的函数来进行统一的检查。不过,仍然有很多例外情况并不能应用到这一准则,比如文章发布系统,评论系统等必须要允许用户提交任意字符串的场景,这就需要采用过滤等其他方案了。


2、过滤特殊符号

对于无法确定固定格式的变量,一定要进行特殊符号过滤或转义处理。以PHP为例,通常是采用addslashes函数,它会在指定的预定义字符前添加反斜杠转义,这些预定义的字符是:单引号 (') 双引号 (") 反斜杠 (\) NULL。


来看2条SQL语句:

$uid = isset($_GET['uid']) ? $_GET['uid'] : 0;$uid = addslashes(uid);$sql = "SELECT uid,username FROM user WHERE uid='{$uid}'";

以及

$uid = isset($_GET['uid']) ? $_GET['uid'] : 0;$uid = addslashes(uid);$sql = "SELECT uid,username FROM user WHERE uid={$uid}";

上面两个查询语句都经过了php的addslashes函数过滤转义,但在安全性上却大不相同,在MySQL中,对于int类型字段的条件查询,上面个语句的查询效果完全一样,由于第一句SQL的变量被单引号包含起来,SQL注入的时候,黑客面临的首要问题是必须要先闭合前面的单引号,这样才能使后面的语句作为SQL执行,并且还要注释掉原SQL语句中的后面的单引号,这样才可以成功注入,由于代码里使用了addslashes函数,黑客的攻击会无从下手,但第二句没有用引号包含变量,那黑客也不用考虑去闭合、注释,所以即便同样采用addslashes转义,也还是存在SQL攻击漏洞。


对于PHP程序+MySQL构架的程序,在动态的SQL语句中,使用单引号把变量包含起来配合addslashes函数是应对SQL注入攻击的有效手段,但这做的还不够,像上面的2条SQL语句,根据「检查数据类型」的原则,uid都应该经过intval函数格式为int型,这样不仅能有效避免第二条语句的SQL注入漏洞,还能使得程序看起来更自然,尤其是在NoSQL(如MongoDB)中,变量类型一定要与字段类型相匹配才可以。


从上面可以看出,第二个SQL语句是有漏洞的,不过由于使用了addslashes函数,你会发现黑客的攻击语句也存在不能使用特殊符号的条件限制,类似where username='plhwin'这样的攻击语句是没法执行的,但是黑客可以将字符串转为16进制编码数据或使用char函数进行转化,同样能达到相同的目的,如果对这部分内容感兴趣,可以点击这里查看。而且由于SQL保留关键字,如「HAVING」、「ORDER BY」的存在,即使是基于黑白名单的过滤方法仍然会有或多或少问题,那么是否还有其他方法来防御SQL注入呢?


3、绑定变量,使用预编译语句

MySQL的mysqli驱动提供了预编译语句的支持,不同的程序语言,都分别有使用预编译语句的方法,我们这里仍然以PHP为例,编写userinfo2.php代码:

<?phpheader('Content-type:text/html; charset=UTF-8');$username = isset($_GET['username']) ? $_GET['username'] : '';$userinfo = array();if($username){//使用mysqli驱动连接demo数据库$mysqli = new mysqli("localhost", "root", "root", 'demo');//使用问号替代变量位置$sql = "SELECT uid,username FROM user WHERE username=?";$stmt = $mysqli->prepare($sql);//绑定变量$stmt->bind_param("s", $username);$stmt->execute();$stmt->bind_result($uid, $username);while ($stmt->fetch()) {    $row = array();    $row['uid'] = $uid;    $row['username'] = $username;    $userinfo[] = $row;}}echo '<pre>',print_r($userinfo, 1),'</pre>';

从上面的代码可以看到,我们程序里并没有使用addslashes函数,但是浏览器里运行 http://localhost/test/userinfo2.php?username=plhwin' AND 1=1-- hack 里得不到任何结果,说明SQL漏洞在这个程序里并不存在。


实际上,绑定变量使用预编译语句是预防SQL注入的最佳方式,使用预编译的SQL语句语义不会发生改变,在SQL语句中,变量用问号?表示,黑客即使本事再大,也无法改变SQL语句的结构,像上面例子中,username变量传递的plhwin' AND 1=1-- hack 参数,也只会当作username字符串来解释查询,从根本上杜绝了SQL注入攻击的发生。


数据库信息加密安全

相信大家都还对2011年爆出的CSDN拖库事件记忆犹新,这件事情导致CSDN处在风口浪尖被大家痛骂的原因就在于他们竟然明文存储用户的密码,这引发了科技界对用户信息安全尤其是密码安全的强烈关注,我们在防范SQL注入的发生的同时,也应该未雨绸缪,说不定下一个被拖库的就是你,谁知道呢。


在Web开发中,传统的加解密大致可以分为三种:

1、对称加密:

即加密方和解密方都使用相同的加密算法和密钥,这种方案的密钥的保存非常关键,因为算法是公开的,而密钥是保密的,一旦密匙泄露,黑客仍然可以轻易解密。常见的对称加密算法有:AES、DES等。


2、非对称加密:

即使用不同的密钥来进行加解密,密钥被分为公钥和私钥,用私钥加密的数据必须使用公钥来解密,同样用公钥加密的数据必须用对应的私钥来解密,常见的非对称加密算法有:RSA等。


3、不可逆加密:

利用哈希算法使数据加密之后无法解密回原数据,这样的哈希算法常用的有:md5、SHA-1等。


在我们上面登录系统的示例代码中,$md5password = md5($password); 从这句代码可以看到采用了md5的不可逆加密算法来存储密码,这也是多年来业界常用的密码加密算法,但是这仍然不安全。为什么呢?


这是因为md5加密有一个特点:同样的字符串经过md5哈希计算之后生成的加密字符串也是相同的,由于业界采用这种加密的方式由来已久,黑客们也准备了自己强大的md5彩虹表来逆向匹配加密前的字符串,这种用于逆向反推MD5加密的彩虹表在互联网上随处可见,在Google里使用md5 解密作为关键词搜索,一下就能找到md5在线破解网站,把我们插入用户数据时候的MD5加密字符串e10adc3949ba59abbe56e057f20f883e填入进去,瞬间就能得到加密前的密码:123456。当然也并不是每一个都能成功,但可以肯定的是,这个彩虹表会越来越完善。


所以,我们有迫切的需求采用更好的方法对密码数据进行不可逆加密,通常的做法是为每个用户确定不同的密码加盐(salt)后,再混合用户的真实密码进行md5加密,如以下代码:

<?php//用户注册时候设置的password$password = $_POST['password'];//md5加密,传统做法直接将加密后的字符串存入数据库,但这不够,我们继续改良$passwordmd5 = md5($password);//为用户生成不同的密码盐,算法可以根据自己业务的需要而不同$salt = substr(uniqid(rand()), -6);//新的加密字符串包含了密码盐$passwordmd5 = md5($passwordmd5.$salt);

小结

1、不要随意开启生产环境中Webserver的错误显示。
2、永远不要信任来自用户端的变量输入,有固定格式的变量一定要严格检查对应的格式,没有固定格式的变量需要对引号等特殊字符进行必要的过滤转义。
3、使用预编译绑定变量的SQL语句。
4、做好数据库帐号权限管理。
5、严格加密处理用户的机密信息。

编辑于 2016-01-05

根本上杜绝的方案太多太简单了,SQL注入这种攻击手段说白了和直接把门踹开偷东西一样Low,这么多年一爆再爆我也很震精。不过想想其实都是一群小白拿着几十年前的小白写的东西改改用才会这样的。

有人非说我没回答问题,OK,根本的手段就是参数化查询或者做词法分析。


@pansz
的答案的确是说到了一个关键,也就是如果支持文本式的指令,那这事儿是没有办法杜绝的,只能在外面套上一个壳,也就是词法分析。


但是现代数据库却不是只提供了SQL文本查询,参数化查询也是标准API之一,当然还有存储过程,这些高级的API都可以有效的杜绝SQL注入这种问题。在MySQL都支持存储过程的今天,还有这么多注入漏洞实在是让人费解。

不使用支持SQL的数据库过于偏颇了,几乎所有的关系型数据库都支持SQL查询,但也提供了安全的查询方式。


常见攻击方式

一般说来,在Web安全领域,常见的攻击方式大概有以下几种:
1、SQL注入攻击
2、跨站脚本攻击 - XSS
3、跨站伪造请求攻击 - CSRF
4、文件上传漏洞攻击
5、分布式拒绝服务攻击 - DDOS