SQL注入不行了?来看看DQL注入
现代的Web应用程序已经不太容易实现SQL注入,因为开发者通常都会使用成熟的框架和ORM。程序员只需要拿过来用即可,无需考虑太多SQL注入的问题,而在专业的框架下安全研究者们已经做了很多的防御,但是我们仍然会在一些意外的情况下发现一些注入漏洞。
在这种情况下,内置在ORM库中的SQL语言就特别让人感兴趣了。它是一个附加的抽象语言,在将语言的表达式转换为SQL的特定功能实现时是否也可能会存在漏洞呢?
介绍
ORM是一种对象关系映射的开发方式,将代码中的对象及其属性与数据库中的表和字段链接在一起。ORM映射允许将数据库关系表表示为普通对象,并将其视为对象。
ORM也允许您分离数据库和应用程序任务,因此开发者甚至不需要编写SQL查询,而只需对 对象执行操作,而操作相应的SQL查询将由ORM库生成。
为什么要使用ORM?
显然,使用ORM便无需手动编写数百个SQL查询,极大地简化了开发地过程,尤其是在大型项目中。但是与此同时,由库自动生成的查询会更加难以优化,并且库本身将增加成本。
ORM本身并不能防止注入,但是当正确使用时,它会支持比较安全地内置语句和参数化查询。
使用原则和DQL注入
有许多针对不同编程语言和框架的ORM库。本文主要介绍关于用PHP编写的Doctrine项目和利用Doctrine Query Language注入(以下简称DQL注入)。在流行的Symfony PHP框架中默认使用Doctrine。
您可以通过对PHP代码中的对象执行操作(使用QueryBuilder)以及手动执行DQL查询来使用Doctrine。也可以直接在SQL中执行原始查询。
DQL语言是一种基于HQL(Hibernate Java库中的Hibernate查询语言)的查询语言,并且是SQL的子集,但DQL也拥有了许多功能,可以帮助我们进行注入操作。
DQL支持通常的运算符,比如:SELECT,UPDATE,DELETE,但没有INSERT和UNION运算符LIMIT表达式的实现(必须使用setMaxResults方法)。ORM库的作者由于DQL的严格类型限制而未实现UNION运算符(而UNION则意味着可以选择非均匀数据)。
DQL还支持子查询和表达式JOIN,WHERE,ORDER BY,HAVING,IN等。
DQL语法文档:https : //www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/dql-doctrine-query-language.html
下面所列是可以在SELECT,WHERE和HAVING表达式之后使用的内置DQL函数的列表。您还可以在SELECT和GROUP BY表达式之后使用AVG,COUNT,MIN,MAX,SUM函数。
像在许多数据库中一样,您也可以在PHP中创建自己的用户定义函数实现,并使其可从DQL进行使用。
DQL 注入
下图是在代码中使用对象时,我们用来创建SQL查询以检索Doctrine中的数据的方法:
DQL查询和SQL查询之间的区别如下所示:
代码语言:javascript复制$dqlQuery = "SELECT p FROM AppEntityPost p WHERE id = '$query' ORDER BY p.publishedAt DESC";
$sqlQuery = "SELECT * FROM post WHERE id = '$query' ORDER BY publishedAt DESC";
很显然,在两种情况下,某些变量都与请求串联在一起。如果此数据来自用户输入,那我们就可以利用DQL注入。
DQL注入操作的原理与SQL注入的利用没有太大区别,但是我们需要知道的是,攻击者并不能完全控制将发送到数据库的查询语句是什么。DQL实际上是对模型进行操作,而不是对实际的数据库表进行操作,因此,攻击者并没有办法从还未在应用程序代码中定义相应模型的表中提取数据。
让我们看看创建这样一个恶意查询时发生了什么(从Post类方法调用QueryBuilder):
DQL查询将转换为抽象语法树,然后在连接的DBMS的语法中将其转换为SQL查询。
注入技巧
根据所使用的DBMS,查询的类型,注入上下文和设置(调试模式)的不同,可能会使用到不同的注入开发算法,例如基于布尔和基于错误。
- 基于布尔
子字符串函数和子查询允许逐个字符逐个暴力破解模型属性值:
代码语言:javascript复制1 or 1=(select 1 from AppEntityUser a where a.id=1 and substring(a.password,1,1)='$')
屏幕截图显示,我们获得了密码哈希的第一个字符(“ $”)的值。在SELECT运算符中,我们使用完整的模型名称User。没有简单的方法来获取所有模型的列表。
- 基于错误(SQLite)
使用SQLite DBMS时,还有一个功能– SQLite语言非常差,并且无论使用什么DBMS,DQL都提供相同的接口。因此,在SQLite中在没有任何本机函数的情况下,您必须使用PHP编写其实现。
它涉及函数udfSqrt,udfMod,udfLocate(对应的DQL函数:SQRT,MOD,LOCATE)。将错误的数据传递给这些函数时,在PHP级别而不是DBMS级别会发生异常,因此,如果显示错误,则整个SQL查询的结果可能会泄漏。
一个错误:
包含密码哈希的SQL查询结果:
显然,没有调试模式,应用程序不太可能显示此数据,但是仍然可以通过蛮力使用基于错误的注入(提取有关内部错误存在或不存在的一些信息)。
- 在ORDER BY之后注入
DQL语法不支持在ORDER BY和GROUP BY之后使用复杂的表达式和子查询,因此在这种情况下无法利用,解析器仅允许使用文字。
- IN后注入
可以将子查询作为IN表达式的参数传递,这为各种注入技术打开了大门,例如基于错误的技术:
代码语言:javascript复制$dqlQuery = "SELECT p FROM AppEntityPost p WHERE p.id IN (select sqrt(a.password) from AppEntityUser a where a.id=2)";
- UPDATE之后注入
UPDATE运算符允许攻击者将子查询的结果写入model属性的值,以便可以完全通过边通道提取数据(通过将秘密数据与公共数据一起写入表中):
代码语言:javascript复制UPDATE AppEntityPost p SET p.title = (SELECT u.password FROM AppEntityUser u WHERE u.id = 2), slug = testslug, summary = testsum, content = testcon WHERE id = 25
结论
许多开发人员已经习惯了框架为他们完成所有工作的事实,而不去过多的考虑担心代码的安全性。但使用ORM并不是防止SQL注入的万能药。开发人员还是有必要仔细验证和清除用户传输的数据并使用安全的语句。
您也可以在文档中阅读有关DQL中哪些方法安全的更多信息:https : //www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/security.html。