一位研究人员在安全测试期间绕过了CloudflareWAF的SQLi过滤器,这意味着Cloudflare安装配置不正确,但Cloudflare目前不认为这是一个漏洞。也就是说,安全专业人员在为其应用程序部署安全保护时需要注意问题。对于那些试图绕过WAF的人来说,发现的一些漏洞也可能派上用场。攻击者可以使用一种或多种技术(取决于最终应用程序)绕过某些WAF,并通过滥用SQLi漏洞窃取数据。用于测试的Web应用程序的详细信息用于测试的Web应用程序的目的对于实际测试并不重要。除了在服务器上公开的/graphql端点和运行查询的PostgreSQL服务之外,写入查询的堆栈也不重要。GraphQL是一种处理查询的查询语言,在测试中,在后端运行适当的SQL查询并返回请求的数据。由于测试人员之前从未使用过或阅读过有关GraphQL的资料,因此遵循了官方教程https://graphql.org/learn/,有兴趣的也可以了解一下。查询的构成类似于JSON对象。应用程序向/graphql端点发送POST请求。请求文本包含如下GraphQL查询:X-MARK中未过滤的参数从GraphQL传递并在PostgreSQL存储过程中替换,允许我们注入在后端服务器上执行的SQL查询。这几乎就是我们现在必须了解的关于该应用程序的全部信息。在接下来的部分中,我将描述我在测试SQL注入应用程序时所做的一些观察,这些观察使我能够成功利用SQL注入,包括绕过CloudflareSQLi过滤器。观察1第一个重要的观察是服务器对有效电子邮件输入的响应(即当SQL查询返回数据时)与对无效电子邮件输入的响应不同。返回的HTTP代码始终是200,但响应文本不同:这将返回“OK”:这将返回“NOTOK”:乍一看这似乎并不多,但它实际上允许我验证中的特定数据数据库是存在的机制。这让我开始考虑利用这种机制来执行盲SQL注入攻击并使用脚本使其自动化。该脚本构建一个有效负载并与POST请求一起发送,这反过来又修改了在后端服务器上运行的SQL查询。步骤如下:我们有一组字符,称为字母表。这组字符包括所有可能的字符,这些字符可能是我们试图从数据库中提取的数据的一部分。假设该资源是需要检索的SQL资源。它可以是数据库名称、用户名、任何表中的单元格值等。将逐个字符地检索资源。我们试图检索的字符称为char。我们遍历字母表并将每个字母与char进行比较。如果匹配,我们记录结果并继续下一个字符。最后,我们将所有的字符拼接起来,得到了完整的资源。观察2第二个重要的观察是,当执行的SQL查询出现错误时,会显示错误信息,这对我来说容易多了。此错误消息不允许我执行基于错误的SQL注入,而是显示PostgreSQL在SQL服务器上执行的完整SQL查询。为什么这如此重要,我们很快就会看到。从错误消息中提取的SQL查询如下所示:作为“email”变量提交的用户输入反映在X-MARK上。这就是为什么拥有完整的SQL查询对开发过程很重要的原因有两个:用户输入可以在括号中看到,这条信息使有效负载生成过程变得容易得多,因为人们知道输入必须包含相同的开头圆括号与右圆括号相匹配,因此结束的SQL查询是有效的。用户输入反映在查询中的多个位置(第5、10、11行),给我带来麻烦的是第5行。如果X-MARK仅反映在WHERE中,事情会更容易条款。但在这种情况下,我必须确保我的输入不会弄乱表JOIN。这是确保生成和查询正确的表行所必需的,以便我可以获得所需的数据。注意:第8行的$1符号是SQL准备查询的位置参数,被HTTP请求的“name”变量参数替换。但它不易受到SQL注入攻击。第一次尝试我首先尝试查找当前数据库的名称。在PostgreSQL中执行此操作的SQL查询是:有很多事情需要考虑,而且开始时很疯狂,我必须从一开始就想办法绕过Cloudflare。绕过WAF的第一种方法首先,我必须先将current_database()的第一个字符与字符“a”进行比较。PostgreSQL的方式是:Cloudflare在“substr”函数上阻塞,所以诀窍是使用“left”或“right”函数。我正在使用“右”函数,因为“左”函数在我试图找出我找到的数据库名称中的所有字符时给我带来了一些麻烦。一个新的查询(将“a”与上一个资源的字符进行比较,如下所示:并且未被WAF检测到。注意:函数right(current_database(),N)返回数据库名称最右边的N个字符。所以,当找到最后一个字符时,比如X,下一次对该函数的调用应该是:因为我们已经知道我们必须关闭查询中的左括号(来自观察2),POST请求的文本如下所示(这里只有'email'变量):但是,记住后端SQL查询如何包含JOIN子句(来自观察2),我还向查询添加了一些额外的东西以确保SQL连接在后台正确执行。POST请求的文本如下所示:服务器上的后续SQL查询如下所示:很复杂,但思路是一样的:如果“a”是数据库名称最右边的字符,我们将得到一个“好的”来自服务器的响应。无论如何,在向服务器提交此请求后,我看到了恐惧CloudflareWAF的ed页面(配置错误),告诉我我的请求被阻止了。第二次尝试。在我再次尝试之前,我必须了解Cloudflare对我的查询有什么帮助。经过多次测试,我发现问题出在生成的服务器SQL查询中的FROM子句中的空格。这让我想到了第二个WAF旁路。第二种绕过WAF的方法此处使用的第二种方法这种WAF绕过技术消除了SQL查询中的空格,并将SQLFROM子句的部分内容括在括号中。变成:所以POST请求的结果文本变成:服务器上的后续SQL查询看起来像这样:这样,整个过程就可以自动找到数据库名称的整个值。同一过程还检索用户名(通过使用user函数)和数据库版本(通过使用version()函数)。但是存储在数据库表中的数据呢?检索此数据的通用查询以及我用来绕过上一篇文章中的方法的查询都不起作用。两者都被阻止:为什么我的查询被阻止?问题出在SELECT子句之后的FROM子句之后。以下查询将很好地通过(配置错误的)CloudflareWAFSQLi过滤器:一旦在查询末尾引入WHERE子句,WAF就会启动并阻止请求。我的最终目标是从任何表中检索数据。是时候挖兔子洞了。第三次尝试我在这里给出了一个早期失败的SQLi尝试,只是因为我希望这篇文章向人们展示渗透测试期间思维过程是如何展开的。为了使事情复杂化(即摆脱所有JOIN和FROM子句),我使用了一个简单的分号和注释技巧(;--)。计划是先检索数据库名称,然后根据它检索表中的数据:服务器上生成的SQL查询如下:不管怎样,这当然行不通,原因有二:第5行之后的所有内容会因为注释Ignored而被破坏,这不一定是限制性的,但我宁愿在FROM子句中执行我的SQLi。我收到以下错误:“绑定消息提供了1个参数,但准备好的语句需要0”。这是因为name变量被传递给准备语句,但第8行被忽略,因此新准备语句不需要该变量。第四次尝试我在这里给出了另一个较早的从表中检索数据的尝试,这使我更接近我的目标。在此之前我所知道的是以下负载将被WAF阻止:注意:我添加了LIMIT和OFFSET关键字以便从table1中仅检索一行。LIMIT表示我们只想检索一行,OFFSET表示在开始检索数据之前我们要跳过多少行。在这种情况下,OFFSET0意味着数据库应该跳过0行并返回table1中的第一行。这对于逐个检索表的所有行很有用。WAF绕过的第三种方式回顾从数据库服务器生成的错误中检索到的SQL查询,我注意到FROM子句的使用可能不是必需的。table1表在使用AS关键字的查询中被别名为t1,它的任何列都可以基于t1被引用。这使得可以像这样查询table1的column1列:这通过(错误配置的)CloudflareWAF工作正常,因此POST请求文字中的有效负载转换如下:服务器上的后续SQL查询如下所示:工作正常,但限制只能从table1(或table2)中获取数据,因为这些是服务器SQL查询中唯一的别名表,请继续上次成功的尝试。最后一次尝试好吧,如果我想从数据库中检索任何我想要的数据,我不得不放弃WAF绕过的第三种方法。在这一点上,似乎没有办法避免FROM子句。此外,似乎无法在不被Cloudflare的WAF检测到的情况下将FROM子句成功隐藏到有效负载中。看来我要找的答案不在SQL查询中。我不得不退后一步。进入GraphQL我们已经看到发送的请求文本是一个GraphQL查询,然后将其转换为SQL查询。所以我的下一次尝试是改变GraphQL查询并设法隐藏其中的FROM子句,这有望转化为服务器上的工作SQL查询。如上所述,GraphQL查询的结构类似于JSON对象。JSON中的数据以名称/值对的形式存储在字典中,它们都是字符串。GraphQL查询需要字符串键,但允许任意参数。这些规则适用于GraphQL:数据以逗号分隔;大括号括起对象;方括号括起数组;所以GraphQL查询参数可以类似于以下任何一种:这让我开始思考:如果我将对象的值作为数组传递而不是字符串传递,后端服务器上的SQL查询会发生什么。简而言之,我想破坏SQL注入查询并将FROM子句移动到不同的对象以欺骗Cloudflare。输入该请求会绕过WAF,我会从数据库中收到错误的SQL查询错误,并向我显示完整的SQL查询结果。注入点的查询如下所示:注意到SELECTcolumn1之后的逗号(,)了吗?这是我绕过SQLi过滤的凭据。将GraphQL查询参数的值作为数组传递给后端SQL服务器中的字符串。字符串只是由逗号和空格字符分隔的数组项的串联!此时,SQL查询是错误的,但我可以注释掉逗号并获得一个有效的、绕过waff的请求,该请求从我选择的任何数据库表中检索我想要的任何数据。这是最终POST请求的文本:以及在SQL服务器上生成的有效SQL查询:成功!为了简化表检索过程,我用Python编写了一个脚本来自动执行该过程。该脚本的伪代码如下:这就是我如何利用GraphQL进行SQL注入,绕过配置错误的CloudflareWAF实例,并能够在后端检索整个数据库。正如我在开头提到的,这种绕过技术的组合不适用于正确配置的CloudflareWAF。缓解缓解数据库SQL注入的最安全方法是准备语句。
