摘要
以用户或者外部不可信来源的输入动态构造SQL查询的命令,将可能改变SQL查询语句本来的语义,从而导致执行任意的SQL命令。
缺陷描述
SQL injection 错误在以下情况下发生:
1. 数据从一个不可信赖的数据源进入程序。
- 在这种情况下, 静态扫描工具无法确定数据源是否可信赖。
2. 数据用于动态地构造一个 SQL 查询。
示例1 以下代码动态地构造并执行了一个 SQL 查询,该查询可以搜索与指定名称相匹配的项。该查询仅会显示条目所有者与被授予权限的当前用户一致的条目。
...
String userName = ctx.getAuthenticatedUserName();
String itemName = request.getParameter("itemName");
String query = "SELECT * FROM items WHERE owner = '"
+ userName + "' AND itemname = '"
+ itemName + "'";
ResultSet rs = stmt.execute(query);
...
这一代码所执行的查询遵循如下方式:
SELECT * FROM items
WHERE owner =
AND itemname =
但是,由于这个查询是动态构造的,由一个不变的基查询字符串和一个用户输入字符串连接而成,因此只有在 itemName
不包含单引号字符时,才会正确执行这一查询。如果一个用户名为 wiley
的攻击者为 itemName
输入字符串“name' OR 'a'='a
”,那么查询就会变成:
SELECT * FROM items
WHERE owner = 'wiley'
AND itemname = 'name' OR 'a'='a';
附加条件 OR 'a'='a'
会使 where
从句永远评估为 true
,因此该查询在逻辑上将等同于一个更为简化的查询:
SELECT * FROM items;
这种查询的简化会使攻击者绕过查询只返回经过验证的用户所拥有的条目的要求;而现在的查询则会直接返回所有储存在 items
表中的条目,不论它们的所有者是谁。
示例2 这个例子指出了将不同的恶意数值传递给在“示例1”中构造和执行的查询时所带来的各种影响。如果一个用户名为 wiley
在 itemName
中输入字符串“name'; DELETE FROM items; --
”,则该查询将会变为以下两个:
SELECT * FROM items
WHERE owner = 'wiley'
AND itemname = 'name';
DELETE FROM items;
--'
众多数据库服务器,其中包括 Microsoft(R) SQL Server 2000,都可以一次性执行多条用分号分隔的 SQL 指令。对于那些不允许运行用分号分隔的批量指令的数据库服务器,比如 Oracle 和其他数据库服务器,攻击者输入的这个字符串只会导致错误;但是在那些支持这种操作的数据库服务器上,攻击者可能会通过执行多条指令而在数据库上执行任意命令。
注意成对的连字符 (--
);这在大多数数据库服务器上都表示下面的语句将作为注释使用,而不能加以执行 [4]。在这种情况下,注释字符的作用就是删除修改的查询指令中遗留的最后一个单引号。对于那些不允许按照这种方式使用注释的数据库,通常攻击者会按“示例1” 的方式进行攻击。如果攻击者输入字符串“name'); DELETE FROM items; SELECT * FROM items WHERE 'a'='a
”,则会创建以下三个有效指令:
SELECT * FROM items
WHERE owner = 'wiley'
AND itemname = 'name'
DELETE FROM items;
SELECT * FROM items WHERE 'a'='a';
有些人认为在移动世界中,典型的 Web 应用程序漏洞(如 SQL injection)是无意义的 -- 为什么用户要攻击自己?但是,谨记移动平台的本质是从各种来源下载并在相同设备上运行的应用程序。恶意软件在银行应用程序附近运行的可能性很高,它们会强制扩展移动应用程序的攻击面(包括跨进程通信)。
示例3 以下代码对示“示例1” 进行调整,使其适用于 Android 平台。
...
PasswordAuthentication pa = authenticator.getPasswordAuthentication();
String userName = pa.getUserName();
String itemName = this.getIntent().getExtras().getString("itemName");
String query = "SELECT * FROM items WHERE owner = '"
+ userName + "' AND itemname = '"
+ itemName + "'";
SQLiteDatabase db = this.openOrCreateDatabase("DB", MODE_PRIVATE, null);
Cursor c = db.rawQuery(query, null);
...
修复建议:
造成 SQL injection 攻击的根本原因在于攻击者可以改变 SQL 查询的上下文,使程序员原本要作为数据解析的数值,被篡改为命令了。当构造一个 SQL 查询时,程序员应当清楚,哪些输入的数据将会成为命令的一部分,而哪些仅仅是作为数据。参数化 SQL 指令可以防止直接窜改上下文,避免几乎所有的 SQL injection 攻击。参数化 SQL 指令是用常规的 SQL 字符串构造的,但是当需要加入用户输入的数据时,它们就需要使用捆绑参数,这些捆绑参数是一些占位符,用来存放随后插入的数据。换言之,捆绑参数可以使程序员清楚地分辨数据库中的数据,即其中有哪些输入可以看作命令的一部分,哪些输入可以看作数据。这样,当程序准备执行某个指令时,它可以详细地告知数据库,每一个捆绑参数所使用的运行时的值,而不会被解析成对该命令的修改。
可以将“示例1” 改写成使用参数化 SQL 指令(替代用户输入连续的字符串),如下所示:
String userName = ctx.getAuthenticatedUserName();
String itemName = request.getParameter("itemName");
String query = "SELECT * FROM items WHERE itemname=? AND owner=?";
PreparedStatement stmt = conn.prepareStatement(query);
stmt.setString(1, itemName);
stmt.setString(2, userName);
ResultSet results = stmt.execute();
...
下面是 Android 的等同内容:
...
PasswordAuthentication pa = authenticator.getPasswordAuthentication();
String userName = pa.getUserName();
String itemName = this.getIntent().getExtras().getString("itemName");
String query = "SELECT * FROM items WHERE itemname=? AND owner=?";
SQLiteDatabase db = this.openOrCreateDatabase("DB", MODE_PRIVATE, null);
Cursor c = db.rawQuery(query, new Object[]{itemName, userName});
...
更加复杂的情况常常出现在报表生成代码中,因为这时需要通过用户输入来改变 SQL 指令的命令结构,比如在 WHERE 条件子句中加入动态的约束条件。不要因为这一需求,就无条件地接受连续的用户输入,从而创建查询语句字符串。当必须要根据用户输入来改变命令结构时,可以使用间接的方法来防止 SQL injection 攻击:创建一个合法的字符串集合,使其对应于可能要加入到 SQL 指令中的不同元素。在构造一个指令时,可使用来自用户的输入,以便从应用程序控制的值集合中进行选择。
注意事项:
1. 使用参数化 SQL 指令的一个常见错误是使用由用户控制的字符串来构造 SQL 指令。这显然背离了使用参数化 SQL 指令的初衷。如果不能确定用来构造参数化指令的字符串是否由应用程序控制,请不要因为它们不会直接作为 SQL 指令执行,就假定它们是安全的。务必彻底地检查 SQL 指令中使用的所有由用户控制的字符串,确保它们不会修改查询的含意。
2. 防范 SQL injection 攻击的另外一种常用方式是使用存储过程。虽然存储过程可以阻止某些类型的 SQL injection 攻击,但是对于绝大多数攻击仍无能为力。存储过程有助于避免 SQL injection 的常用方式是限制可作为参数传入的指令类型。但是,有许多方法都可以绕过这一限制,许多危险的表达式仍可以传入存储过程。所以再次强调,存储过程在某些情况下可以避免这种攻击,但是并不能完全保护您的应用系统抵御 SQL injection 的攻击。
框架及场景参考
(一)SQL Injection: Hibernate
Hibernate是一种ORMapping
框架,内部可以使用原生SQL还有HQL语言进行SQL操作。
所谓的HQL注入,就是指在Hibernate中没有对数据进行有效的验证导致恶意数据进入应用程序中造成的。
请看这段代码:
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
response.setContentType(“text/html”);
Configuration configuration = new Configuration();
configuration.configure();
SessionFactory sessionFactory = configuration.buildSessionFactory();
String input = request.getParameter(“sqlin”);
String hqlString = “from classes where name = ‘” + input + “’”;
System.out.println(hqlString);
Session session = sessionFactory.openSession();
List
for(Classes classes : classesList){
response.getWriter().write(classes.getName());
response.getWriter().print(“
”);
}
session.close();
}
Input参数即可造成注入。
不过在Hibernate中,一般都是在createQuery中使用PDO,使用setString填充占位符进行sql语句的拼接,如果是这样的话,自然就不存在SQL注入,但是不排除有人像上面的图片中的写法。
此外,还需注意HQL注入对万能密码、知道表名列名的情况下进行盲注等情况的发生。
(二)SQL Injection: iBatis Data Map
使用 iBatis Data Map 可以指定 SQL 指令中的动态参数,通常使用 #
字符来定义它们,如:
变量名称周围的 #
字符表示 iBatis 将使用 userName
变量创建参数化查询。但是,iBatis 还允许使用 $
字符将变量直接连接到 SQL 指令,使其易受 SQL injection 攻击。
示例4 以下代码动态地构造并执行了一个 SQL 查询,该查询可以搜索与指定名称相匹配的项。该查询仅会显示条目所有者与被授予权限的当前用户一致的条目。
但是,由于这个查询是动态构造的,由一个不变的基查询字符串和一个用户输入字符串连接而成,因此只有在 itemName
不包含单引号字符时,才会正确执行这一查询。如果一个用户名为 wiley
的攻击者为 itemName
输入字符串“name' OR 'a'='a
”,那么查询就会变成:
SELECT * FROM items
WHERE owner = 'wiley'
AND itemname = 'name' OR 'a'='a';
附加条件 OR 'a'='a'
会使 where 从句永远评估为 true,因此该查询在逻辑上将等同于一个更为简化的查询:
SELECT * FROM items;
这种查询的简化会使攻击者绕过查询只返回经过验证的用户所拥有的条目的要求;而现在的查询则会直接返回所有储存在 items
表中的条目,不论它们的所有者是谁。
(三)SQL Injection: JDO
使用 Java 数据对象 (JDO) 执行通过不可信来源的输入构建的动态 SQL 或 JDOQL 指令,会让攻击者有机会篡改指令的含义或者执行任意 SQL 命令。
示例5 以下代码动态地构造并执行了一个 SQL 查询,该查询可以搜索与指定名称相匹配的项。该查询仅会显示条目所有者与被授予权限的当前用户一致的条目。
...
String userName = ctx.getAuthenticatedUserName();
String itemName = request.getParameter("itemName");
String sql = "SELECT * FROM items WHERE owner = '"
+ userName + "' AND itemname = '"
+ itemName + "'";
Query query = pm.newQuery(Query.SQL, sql);
query.setClass(Person.class);
List people = (List)query.execute();
...
这一代码所执行的查询遵循如下方式:
SELECT * FROM items
WHERE owner =
AND itemname =
但是,由于这个查询是动态构造的,由一个不变的基查询字符串和一个用户输入字符串连接而成,因此只有在 itemName
不包含单引号字符时,才会正确执行这一查询。如果一个用户名为 wiley
的攻击者为 itemName
输入字符串“name' OR 'a'='a
”,那么查询就会变成:
SELECT * FROM items
WHERE owner = 'wiley'
AND itemname = 'name' OR 'a'='a';
附加条件 OR 'a'='a'
会使 where 从句永远评估为 true,因此该查询在逻辑上将等同于一个更为简化的查询:
SELECT * FROM items;
这种查询的简化会使攻击者绕过查询只返回经过验证的用户所拥有的条目的要求;而现在的查询则会直接返回所有储存在 items
表中的条目,不论它们的所有者是谁。
(四)SQL Injection: MyBatis Mapper
使用 MyBatis Mapper XML 文件可以指定 SQL 指令中的动态参数,通常使用 #
字符来定义它们,如:
变量名称周围带有括号的 #
字符表示 MyBatis 将使用 userName
变量创建参数化查询。但是,MyBatis 还允许使用 $
字符将变量直接连接到 SQL 指令,使其易受 SQL injection 攻击。
示例6 以下代码动态地构造并执行了一个 SQL 查询,该查询可以搜索与指定名称相匹配的项。该查询仅会显示条目所有者与被授予权限的当前用户一致的条目。
但是,由于这个查询是动态构造的,由一个不变的基查询字符串和一个用户输入字符串连接而成,因此只有在 itemName
不包含单引号字符时,才会正确执行这一查询。如果一个用户名为 wiley
的攻击者为 itemName
输入字符串“name' OR 'a'='a
”,那么查询就会变成:
SELECT * FROM items
WHERE owner = 'wiley'
AND itemname = 'name' OR 'a'='a';
附加条件 OR 'a'='a'
会使 where 从句永远评估为 true,因此该查询在逻辑上将等同于一个更为简化的查询:
SELECT * FROM items;
这种查询的简化会使攻击者绕过查询只返回经过验证的用户所拥有的条目的要求;而现在的查询则会直接返回所有储存在 items
表中的条目,不论它们的所有者是谁。
(五)SQL Injection: Persistence
使用 Java J2EE Persistence API 来执行通过不可信来源的输入构建的动态 SQL 语句会使得攻击者能够篡改语句的含义或者执行任意 SQL 命令。
示例7 以下代码动态地构造并执行了一个 SQL 查询,该查询可以搜索与指定名称相匹配的项。该查询仅会显示条目所有者与被授予权限的当前用户一致的条目。
...
String userName = ctx.getAuthenticatedUserName();
String itemName = request.getParameter("itemName");
String query = "SELECT * FROM items WHERE owner = '"
+ userName + "' AND itemname = '"
+ itemName + "'";
List items = entManager.createQuery(query).getResultList();
...
这一代码所执行的查询遵循如下方式:
SELECT * FROM items
WHERE owner =
AND itemname =
但是,由于这个查询是动态构造的,由一个不变的基查询字符串和一个用户输入字符串连接而成,因此只有在 itemName
不包含单引号字符时,才会正确执行这一查询。如果一个用户名为 wiley
的攻击者为 itemName
输入字符串“name' OR 'a'='a
”,那么查询就会变成:
SELECT * FROM items
WHERE owner = 'wiley'
AND itemname = 'name' OR 'a'='a';
附加条件 OR 'a'='a'
会使 where 从句永远评估为 true,因此该查询在逻辑上将等同于一个更为简化的查询:
SELECT * FROM items;
这种查询的简化会使攻击者绕过查询只返回经过验证的用户所拥有的条目的要求;而现在的查询则会直接返回所有储存在 items
表中的条目,不论它们的所有者是谁。
本文由端玛科技有限公司编写整理,转载请说明出处。端玛科技是专门从事于应用软件安全风险评估、风险消除、培训、教育和软件安全生命开发周期SDL咨询的软件安全公司,关注我们,学习更多应用安全相关知识。