开发反模式 - SQL注入

一、目标:编写SQL动态查询

  SQL常常和程序代码一起使用。我们通常所说的SQL动态查询,是指将程序中的变量和基本SQL语句拼接成一个完整的查询语句。

  string sql = SELECT * FROM Person WHERE Id = $Id

  我们期望$Id是一个整型,因此当数据库接收到这个请求时,$Id的值就是查询语句的一部分。

  SQL动态查询是有效利用数据库很自然的方法。当你使用程序内的变量来指定如何进行查询时,就是将SQL作为连接程序和数据库的桥梁。程序和数据库之间通过这种方式进行“对话”。

  然而,要让程序按照你想要的方式执行并不难,难的是让程序变得安全,不执行你不想让它执行的操作。但软件在收到SQL注入攻击时,通常都无法保证安全。

二、反模式:将未经验证的输入作为代码执行

  当往SQL查询的字符串中插入别的内容,而这些被插入的内容以你不希望的方式修改了查询语句的语法时,SQL注入就成功了。如,对于上面所写的SQL语句,$Id的值变为"123; DELETE FROM Person"。那么最终的查询语句会变成这样:

  SELECT * ROM Person WHERE Id = 123; DELETE FORM Person

  如果你的程序真的执行了这样一行SQL语句,那么悲剧了,Person表里的数据就完全被清空了。

   1、对Web安全的严重威胁

  当攻击者能够使用SQL注入操控你的SQL查询时,它就变成了一个巨大的威胁。假设你的数据库中修改密码的SQL语句是这样写的:

  UPDATE Account SET Password = SHA2(‘$password‘) WHERE AccountId = 123;

  那么聪明的攻击者会猜测你请求参数对应在SQL语句中的作用,并且精心选择每个参数对应的值:

  http://www.xxx.com/setpassword?password=123456&userid=123 OR TRUE

  如果你的程序真的被攻击者所绕过了,那么你的数据库将会执行如下SQL语句:

  UPDATE Account SET Password = 123456 WHERE AccountId = 123 OR TRUE

  Account表中,所有用户的密码都被改成了123456。

  有数不尽的方法选择一个恶意的字符串来改变SQL语句的行为。它只受制于攻击者的想象力和你保护SQL语句的能力。

  2、寻找治愈良方

  有很多文章,声称某一种技术室对抗SQL注入的万能药。而事实上,这些技术都被证明无法阻挡所有类型的SQL注入。因此你需要在不同的情况下,将所有这些技术组合起来使用。

  (1).转义

  防止SQL语句包含任何不匹配的引号是最古老的方法,就是对所有的引号字符进行转义操作,使它们不至于成为字符串的结束符。在标准SQL语句中,可以使用两个连续的单引号来表示一个单引号字符:

  SELECT * FROM Person WHERE Name = ‘O‘‘Hare‘

  大多数数据库还支持使用反斜杠对单引号进行转义操作:

  SELECT * FROM Person WHERE Name = ‘O\‘Hare‘

  这么做的原理是,将应用程序中的数据插入到SQL语句之前就进行转换,大多数SQL的编程接口都会提供一个简便的函数来做这个操作。

  这样做了之后,所有的字符串都会被包上引号,如果对上面修改账号的SQL语句这样操作的话,SQL语句会变成这样:

  UPDATE Account SET Password = SHA2(‘123456‘)
  WHERE AccountId = ‘123 OR TRUE‘

  在SQL中,是没有办法让一个数值列直接和一个带有数字的字符串进行比较的,不管是哪种数据库,这都不可以。在标准SQL中,将字符串转换为数字时,必须明确使用CASE()函数,因此上面的SQL语句只是报个错误,还不至于全部用户账号的密码都被修改了。

  (2).查询参数

  一个经常被认为是防止SQL语句注入的万能解决方案是使用"参数化查询",不同于在SQL语句中插入动态内容,查询参数的做法是在准备查询语句的时候,在对应参数的地方使用参数占位符。随后,在执行这个预先准备好的查询时提供一个参数。

  cmd.CommandText = "Update Person Set Name = ‘Ado.net‘ WHERE Id = @Id";    //设置操作语句
  cmd.Parameters.Add("@Id", SqlDbType.Int);    //添加参数
  cmd.Parameters["@Id"].Value = 1;    //设置参数值

  大多数开发人员都推荐这个方案,因为你不需要对动态内容进行转义,或者担心有缺陷的转义函数。

  事实上,查询参数这个方法的确是对付SQL注入一个强劲有效的解决方案。但这并不是一个通用的方案,因为查询参数总是被视为一个字面值。

  多个值的列表不可以当成单一参数:

  cmd.CommandText = "Update Person Set Name = ‘Ado.net‘ WHERE Id IN (@Id)";    //设置操作语句
  cmd.Parameters.Add("@Id", SqlDbType.String);    //添加参数
  cmd.Parameters["@Id"].Value = "1,2,3";    //设置参数值

  这个做法会导致数据库认为传入的是一个包含数字和逗号的字符串,处理过程将和一系列整数作为参数进行查询并不一样。

  真正生成到数据库中执行的SQL语句为:

  Update Person Set Name = ‘Ado.net‘ WHERE Id IN (‘1,2,3‘)

  表名无法作为参数:

  cmd.CommandText = "SELECT * FROM @Table;    //设置操作语句
  cmd.Parameters.Add("@Table", SqlDbType.String);    //添加参数
  cmd.Parameters["@Table"].Value = "Person";    //设置参数值

  这么做是想将一个字符串插入表名所在的位置,但只会得到一个语法错误的提示。

  真正生成到数据库执行的SQL语句如下:

  SELECT * FROM ‘Person‘

  列名无法作为参数:

  cmd.CommandText = "SELECT * FROM Person ORDER BY @Column;    //设置操作语句
  cmd.Parameters.Add("@Column", SqlDbType.String);    //添加参数
  cmd.Parameters["@Column"].Value = "Id";    //设置参数值

  真正生成到数据库执行的SQL语句如下:

  SELECT * FROM Person ORDER BY ‘Id‘

  SQL关键字不能作为参数:

  cmd.CommandText = "SELECT * FROM Person ORDER BY Id  @Sort;    //设置操作语句
  cmd.Parameters.Add("@Sort", SqlDbType.String);    //添加参数
  cmd.Parameters["@Sort"].Value = "DESC";    //设置参数值

  参数将被当做字面字符串插入而非SQL关键字。在这个例子中,会返回语法错误的提示:

  SELECT * FROM Person ORDER BY Id ‘DESC‘

  (3).存储过程

  存储过程,是很多程序员生成可以抵御SQL注入攻击的方法。通常来说,存储过程包含固定的SQL语句,这些语句是在定义这个存储过程的时候被解析的。

  然而,存储过程也是使用SQL动态查询的,无法绝对保证完全杜绝SQL注入。不过存储过程的确是有强大的防止SQL注入的作用。不过,如果你依然是在存储过程中拼接SQL语句,存储过程也一样能够注入。

  假设你的存储过程如下(这在比较复杂一些的操作时经常出现的):

CREATE PROC SelectAccount
@name varchar(50),
@password varchar(50)
AS
DECLARE @sql varchar(1000);
SET @sql = ‘SELECT * FROM Account WHERE Name = ‘‘‘ + @name + ‘‘‘ AND Password = ‘‘‘ + @password + ‘‘‘‘
EXEC (@sql)

  如果在存储过程当中拼接SQL语句,那么注入方式如下:

--正常登录
EXECUTE SelectAccount ‘admin‘,‘123456‘
--SQL注入,无需账号行数不返回0
EXECUTE SelectAccount ‘a‘‘ or 1=1 --‘,‘‘;

  返回结果:

  

  事实上几乎所有的数据库应用程序都动态地构建SQL语句。如果你使用拼接字符串的形式或者将变量插入到字符串中的方法来构建哪怕一句SQL语句,那这一句查询语句就会让应用程序暴露在SQL注入攻击的威胁之下。

三、解决方案:不相信任何人

  没有哪一种技术能使SQL代码变得安全,你应该学习下面所描述的所有技术,并在合理的地方使用它们。

  1、过滤输入内容

  你应该将所有不合法的字符从用户输入中剔除掉,而不是纠结于是否有些输入包含了有危险的内容。也就是说,如果你需要一个整数,那就只使用输入中的整数部分。根据你所使用的开发语言不同,方法也不尽相同。

  2、参数化动态内容

  如果查询中的变化部分是一些简单的类型,你应该使用查询参数将其和SQL表达式分离。

  参数化动态内容之后,一个参数只能被替换成一个值。如果你是在RDMBS解析完SQL语句之后才插入这个参数值,没有那种SQL注入的攻击能够改变一个参数化了的查询的语法结构。即使攻击者尝试使用带有恶意的参数值,注入123 OR TRUE,RDBMS会将这个字符串当成一个完整的值插入。最坏的情况下,这个查询没办法返回任何记录,它不会返回错误的行。

  3、给动态输入的值加引号

  查询参数通常来说是最好的解决方案,但在有些很特殊的情况下,参数的占位符会导致查询优化器无法正确选择使用哪个索引来进行优化。要规避参数查询对索引的影响,直接将变量内容插入到SQL语句中会是更好的方法,不要去理会查询参数。一旦你决定这么做了,就一定要小心地引用字符串。你需要确信你插入的字符串是经过严格测试、不会带有安全隐患的。

  4、将用户与代码隔离

  查询参数和转移字符能帮助你将字符串类型的值插入到SQL表达式中,但这些技术在需要插入表/列名或者SQL关键字的时候不起作用。你需要另一项技术来使得这些部分也能动态化。

  对应的解决方案是,将请求参数作为索引值去查找预先定义好的值,然后用这些预先定义好的值来组织SQL查询语句。

  (1)、预定义值     如在一个数据字典中存储如下值:       up => "ASC" ,down => "DESC"

  (2)、定义默认值     定义一个默认值,当用户选择的值不在数据字典中时,使用默认值。     比如当用户输入的值不是up也不为down,那么就使用ASC。这样的字符串就是安全的了。

  下面给出一个简单的SQL注入过滤函数:

        public string NoSqlHack(string Inner)
        {
            if (!string.IsNullOrEmpty(Inner))
            {
                //特殊的字符
                Inner = Inner.Replace("<", "");
                Inner = Inner.Replace(">", "");
                Inner = Inner.Replace("*", "");
                Inner = Inner.Replace("-", "");
                Inner = Inner.Replace("?", "");
                Inner = Inner.Replace("‘", "‘‘");
                Inner = Inner.Replace(",", "");
                Inner = Inner.Replace("/", "");
                Inner = Inner.Replace(";", "");
                Inner = Inner.Replace("*/", "");
                Inner = Inner.Replace("\r\n", "");
                Inner = Inner.Replace(" ", "");

                return Inner;
            }
            else
            {
                return string.Empty;
            }
        }

  另外,后端一定要验证,以防止用户POST请求。

  SQL注入绕过某些字符过滤:

  1,避免使用被阻止的字符,即不使用这些字符仍然达到攻击目的。

  A,如果注入一个数字数据字段,就不需要使用单引号。

  B,输入注释符号被阻止使用,我们可以设计注入的数据,既不破坏周围的查询语法。

  比如, http://www.xxx.net/article.asp?id=1‘ 这里存在注入,过滤了注释符合,我们可以输入 http://www.xxx.net/article.asp?id=1‘ or ‘a‘=‘a

  目的其实很简单,就是把后面的单引号给闭合掉。

  C,在一个MSSQL注入中注入批量查询的时候,不必使用分号分隔符。

  只要纠正所有批量查询的语法,无论你是否使用分号,查询的解析器依然能正确的去解释它们的。

  2,避免使用简单确认

  一些输入确认机制使用一个简单的黑名单,组织或删除任何出现在这个名单中的数据,比如防注入程序。

  这一般要看这个机制是否做的足够的好了,黑名单是否足够能确保安全。如果只是简单的黑名单,那也有机会突破的。

  A,如果select关键词被阻止或删除

  我们可以输入:

  SeLeCt       注意大小写

  selselectect    还记得ewebeditor是怎么过滤asp的么?

  %53%45%4c%45%43%54                        URL编码

  %2553%2545%254c%2545%2543%2554    对上面的每个%后加了一个25

  3,使用SQL注释符

  A,使用注释来冒充注入的数据中的空格。

  select/*alocne*/username,password/*alocne*/from/*alocne*/admin

  /*alocne*/来冒充空格

  B,使用注释来避开某些注入的确认过滤。

  SEL/*alocne*/ECT username,password fr/*alocne*/om admin

  4,处理被阻止的字符串

  比如,程序阻止了admin,因为怕攻击者注入admin表单中的数据。

  我们可以这样

  A,oracle数据库: ‘adm‘||‘in‘

  B,MSSQL数据库: ‘adm‘+‘in‘

  C,MYSQL数据库: concat (‘adm‘,‘in‘)

  D,oracle中如果单引号被阻止了,还可以用chr函数

  sleect password from admin where username = char(97) || chr(100) || chr(109) || chr(105) || chr(110)

时间: 2024-10-11 22:32:54

开发反模式 - SQL注入的相关文章

开发反模式(GUID) - 伪键洁癖

一.目标:整理数据 有的人有强迫症,他们会为一系列数据的断档而抓狂. 一方面,Id为3这一行确实发生过一些事情,为什么这个查询不返回Id为3的这一行?这条记录数据丢失了吗?那个Column到底是什么?我要为这条数据的丢失负责吗? 二.反模式:填充角落 大多数人对于断档的第一反应就是想要填补其中的空缺.对此,可能有两种做法: 1.不按照顺序分配编号 你可能想要在插入新行时,通过遍历表,将找到的第一个未分配的主键编号分配给新行,来代替原来自动分配的伪主键机制.随着你不断地插入新行,断档就被填补起来了

开发反模式 - 明文密码

一.目标:恢复或重置密码 每个有密码的程序都会碰到用户忘记密码的情况,现今大多数程序都通过E-mail的回馈机制让用户恢复或者重置密码.这个解决方案有一个前提,这个服务有一个前提,就是这个用户能够访问他在注册时留下的邮箱. 二.反模式:使用明文存储密码 在这种恢复密码的解决方案中,很常见的一个错误是允许用户申请系统发送一封带有明文密码的邮件.这是数据库设计上一个可怕的漏洞,并且会导致一系列安全问题,可能会使得未取得授权的人获得系统访问权限. 1.存储密码 首先我们设计一张表如下: 类似于这张表,

web开发中防止SQL注入

一.SQL注入简介 SQL注入是比较常见的网络攻击方式之一,它不是利用操作系统的BUG来实现攻击,而是针对程序员编写时的疏忽,通过SQL语句,实现无账号登录,甚至篡改数据库. 二.SQL注入攻击的总体思路 1.寻找到SQL注入的位置 2.判断服务器类型和后台数据库类型 3.针对不同的服务器和数据库特点进行SQL注入攻击 三.SQL注入攻击实例 比如在一个登录界面,要求输入用户名和密码: 可以这样输入实现免帐号登录: 用户名: 'or 1 = 1 – 密 码: 点登陆,如若没有做特殊处理,那么这个

SQL反模式学习笔记1 开篇

什么是“反模式” 反模式是一种试图解决问题的方法,但通常会同时引发别的问题. 反模式分类 (1)逻辑数据库设计反模式 在开始编码之前,需要决定数据库中存储什么信息以及最佳的数据组织方式和内在关联方式. 这包含了如何设计数据库的表.字段和关系. (2)物理数据库设计反模式 在确定了需要存储哪些数据之后,使用你所知的RDBMS关系型数据库技术特性尽可能高效地实现数据库管理. 这包含了定义表和索引,以及选择数据类型.也需要是要SQL的“数据定义语言”,比如Create Table语句. (3)查询反模

sql注入原理及基本认识

引言: 作为长期占据 OWASP Top 10 首位的注入,至于什么是OWASP可以参考一下百度百科OWASP SQL注入简介: 所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令.具体来说,它是利用现有应用程序,将(恶意的)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句. [1] 比如先前的很多影视网站

PHP如何防止SQL注入及开发安全 53

PHP如何防止SQL注入及开发安全 [php] function inject_check($sql_str) { $check=eregi('select|insert|update|delete|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile', $sql_str);     // 进行过滤 if($check){ echo "输入非法注入内容!"; exit(); }else{ return $sql_str; } } [/

第二天,导出文件sql,查询,视图view,聚合函数,反模式,字符串处理函数

//把数据库导出到脚本文件mysqldump -uroot -p1234 --databases abc > d:/a/abc.sql CREATE TABLE stud( id INT PRIMARY KEY, NAME VARCHAR(32) NOT NULL, score NUMERIC(4,1));//把所有名字都设成"Mike"了UPDATE stud SET NAME="Mike" //只设置分数>=70的记录的NameUPDATE stud

SQL反模式学习笔记6 支持可变属性【实体-属性-值】

2014-10-11 17:21:31 目标:支持可变属性 反模式:使用泛型属性表.这种设计成为实体-属性-值(EAV),也可叫做开放架构.名-值对. 优点:通过增加一张额外的表,可以有以下好处 (1)表中的列很少: (2)新增属性时,不需要新增列.不会影响现有表的结构: (3)存储的字段内容不会为空值. 缺点:(1)查询语句变得更加复杂: (2)使用EAV设计后,需要放弃传统的数据库设计所带来的方便之处,比如:无法保障数据完整性: (3)无法使用SQL的数据类型,比如对日期.金钱等格式内容都只

SQL反模式学习笔记3 单纯的树

2014-10-11 08:59:48 在树形结构中,实例被称为节点.每个节点都有多个子节点与一个父节点. 最上层的节点叫做根(root)节点,它没有父节点. 最底层的没有子节点的节点叫做叶(leaf). 中间的节点简单地称为非叶节点(nonleaf). 目标:分成存储于查询,比如:系统字典.组织机构.省份区域等树形结构数据或者以层级方式组织的数据. 反模式:总是依赖父节点,邻接表. 最简单的实现方式是添加ParentId字段,引用同一张表的主键ID. 邻接表维护树比较方便,但是查询很笨拙,如果