我们已经准备好了,你呢?

2026我们与您携手共赢,为您的企业形象保驾护航!

0x00 前言

年初,我顺利从猪厂毕业,随后加入了一家初创企业,并负责组建了名为“一个人的信息安全部”。这家公司和许多类似的小企业一样,并未设立专职的数据库管理员(DBA)负责数据库维护,所以常常有人会不定期地突然前来,如同求助于长辈一般,请求访问XX数据库。

这种情况偶尔一两次尚能容忍,但若频繁发生,数据库中的账户数量将不断攀升,而账户授权的工作也将变得异常繁琐。尤其是,还可能面临“MySQL数据库从删除到服务器离线”的棘手问题,员工离职时删除账户同样会带来诸多不便。财阀们面对此类问题通常游刃有余,只需购置相应设备即可轻松解决。然而,对于立志实现“零成本”的初创企业而言,这却是一大挑战,否则便无法充分展现其价值所在。

0x01 调研

起初,我们计划对开源的MySQL代理工具进行二次开发,因此着手收集相关资料。偶然间发现,mysql-proxy提供了6个钩子,用户可以通过Lua脚本调用它们,这意味着我们能够自主编写Lua脚本,从而掌控用户的操作流程。

代理服务器在接收到来自客户端的连接请求后,便会启动并执行这一特定函数。

在MySQL服务器发出握手响应之际,该函数将被触发执行。

当客户终端传输身份验证数据之际,系统将自动触发相应的函数。

当MySQL执行认证操作并返回相应的认证结果时,该操作会被触发执行。

当用户向服务器端发送一条SQL指令,该指令将触发相应的函数执行。

在MySQL执行查询并返回结果的过程中,会触发相应的函数调用。

显而易见,借助上述两个钩子函数,我们能够有效完成对MySQL数据库的认证、授权以及审计任务。

0x02 设计

我们的宗旨在于进行认证、授权以及审计工作。其中,函数具备执行认证和授权的能力,同时也能实现审计的相关功能。这一过程相对简单,只需获取用户发送的SQL指令并将其记录入消息队列即可。在此,我选择将其直接存储在redis的列表中。

函数的实现相对较为复杂,它不仅要对用户提交的单次操作进行严格的检查,还需获取相应的授权数据,以便在用户接入MySQL数据库时,能够以我们设定的特定身份进行登录。

1.png

用户登录后,后端运行的lua脚本会启动公司内部专用的即时通讯软件的消息接口,向用户发送一个一次性密码。同时,这个密码还会被记录到redis数据库中。

用户通过mysql客户端与指定的mysql-proxy建立连接后,系统将触发钩子函数,首先验证用户输入的密码。接着,系统将向redis发送请求,获取当前数据库中与owner角色相关的三个角色的授权名单。随后,系统会检查这三个名单中是否包含当前用户,若发现用户名,则根据其对应的角色将用户导向数据库。

认证授权流程完成后,用户将依据上一步骤中指定的role权限访问后端mysql数据库,而其执行的所有SQL指令则会被自动捕获并传递至钩子函数,进而被记录至redis的队列系统。

0x03 代码

local password = 引入("mysql.password")模块后所获取的密码
局部变量proto被定义为assert函数的返回值,该函数调用了require函数,并传入了"mysql.proto"作为参数。
assert(require("redis"))
--字符串切割
function string:split(sep) 
局部分隔符设为sep,若sep未定义,则默认为冒号";",同时fields初始化为一个空对象。
定义局部模式为:使用字符串格式化方法,其中括号内不包含指定的分隔符,表达式为([^%s]+)。
在执行过程中,使用gsub函数对文本进行模式匹配,并将匹配到的字符作为新字段添加至fields数组中,具体操作是将匹配到的字符c赋值给fields数组中索引为#fields + 1的位置。
return fields 
end 
function read_query( packet )
若数据包的字节值等于代理的查询命令码,则
local con = proxy.connection
初始化本地Redis连接,连接至IP地址为'your_redis_ip'的服务器,端口号为6379。
--获取ip对应的域名
redis:select('3')
局部域等于从Redis获取由服务器目标名称的指定部分分割后的第二个元素。
--将执行的sql语句放入redis队列中
redis:select('2')
Redis:向'mysql_command_queue'键中插入一条数据,该数据为当前日期和时间,格式为"%Y-%m-%d%H:%M:%S"。
客户端的源地址为:.. " " .. con.client.src.address .. "" .. ,用户名为:.. " " .. con.client.username .. " " .. 。
该域名为"["后跟"packet:sub(2)"所表示的内容"]"。
若数据包的第二部分内容等于“SELECT 1”,则
代理查询:向列表中添加编号为1的数据包。
end
end
end
function read_auth()
local names = {}
开发者、管理员、所有者这三个角色的权限依次提升。
local roles = {1号角色为'开发者', 2号角色为'大师', 3号角色为'拥有者'}
local con = proxy.connection
local s = con.server
local role = ''
--认证
本地的Redis连接已建立,通过指定IP地址'your_redis_ip'和端口号6379与Redis服务器进行连接。
局部变量local pass被赋予值,该值是通过调用redis的get方法,并传入con.client.username作为参数获得的。
如果经过密码混淆函数处理后的结果,即对`s.scramble_buffer`进行混淆处理,并使用`password.hash(pass)`得到的哈希值进行混淆,与`con.client.scrambled_password`存储的混淆密码不匹配,那么……
--认证失败返回错误信息
代理响应的类型设置为代理的MySQLd数据包错误类型。
代理响应的错误信息为:“密码输入有误!”
执行代理发送结果操作
end
Redis执行删除操作,针对客户端的用户名键值对,具体命令为del,参数为con.client.username。
--获取ip对应的域名
redis:select('3')
局部域变量定义为从Redis中获取的值,该值对应于由服务器目标名称通过冒号分割后的第二个部分。
redis:select('1')
--获取用户对于当前数据库的role
for i,v inipairs(roles) do
进行“domain:role”的检索,获取对应的人员名单,然后将该名单分割成表格形式。
执行命令获取域名与版本号组合的键值,然后通过逗号分隔该键值对应的字符串列表。
遍历names数组中的每个元素,对每个元素执行循环操作,其中k为索引,name为当前元素。
若用户名与客户端的用户名相匹配,
role = v
break
end
end
end
--无授权信息返回错误信息
if role == '' then
proxy.response.type = proxy.MYSQLD_PACKET_ERR
代理响应的错误信息设定为:“未经授权的访问!”
return proxy.PROXY_SEND_RESULT
end
--最新mysql-proxy加入的新属性
local protocol_41_default_capabilities 等于 8、512 以及 32768 的总和。
proxy.queries:append(1, 
proto.to_response_packet({ 
username = role,  
response = password对s.scramble_buffer进行加密,同时结合password.hash函数处理“your_role_password”得到的哈希值。
--最新mysql-proxy加入的新属性
服务器功能参数设置为协议41默认功能能力。
}) 
)
return proxy.PROXY_SEND_QUERY
end

0x04 效果

在终端输入以下命令,以执行对MySQL数据库的连接操作:使用用户名为“test”的身份,连接至指定的MySQL代理服务器IP地址“your_mysql-proxy_ip”,并指定端口号为“your_mysql-proxy_port”。

Enter password:

欢迎来到MySQL监控界面。在此界面中,输入的指令以分号或反斜杠加g结尾。

您的MySQL连接标识为30341。

服务器版本为:5.7.12,基于MySQL社区版,遵循GPL协议。

版权所有(C)2000年、2018年,甲骨文公司及其关联公司。保留一切权利。

Oracle是Oracle公司及其/或其关联公司的注册商标。

关联企业。其他名称可能是各自所有者的商标。

owners.

输入“help;”或“\h”以获取帮助信息,若需清除当前输入的语句,请输入“\c”。

mysql> select user();

+-------------------------+

| user()                  |

+-------------------------+

| developer@your_ip|

+-------------------------+

1 row in set (0.01 sec)

明显地,当通过用户名test登录到mysql-proxy后,在最终连接到mysql服务器时,用户的身份已经发生了改变。

0x05 总结

在非业务场景下,开发运维人员会进行数据库的连接操作,例如在公司环境中进行数据库的接入。

管理脚本需对每个mysql-proxy进程实施状态监控,并承担起其启动与关闭的任务,同时还要将它们的域名解析为IP地址,并将这些信息存储至redis数据库中。

赋予脚本读取特定yaml文件的权限,并将该文件内含的授权规范同步至redis数据库。

在各个数据库系统中,仅需创建三个账户,即owner账户,而yaml配置文件则负责指定用户应以哪种角色身份登录至MySQL。

mysql-proxy需要使用源码编译安装。

启动mysql-proxy的命令为:

运行mysql-proxy时,指定其代理地址为mysql-proxy_ip:mysql-proxy_port,并设置后端连接地址为mysql_ip:mysql_port,同时限制最大打开文件数为1024,用户身份为root,日志文件存储在/var/log/mysql-proxy目录下,日志级别为debug,并加载自定义的Lua脚本your_lua_file。

参考文章

二维码
扫一扫在手机端查看

本文链接:https://www.by928.com/shan-xi/10012.html     转载请注明出处和本文链接!请遵守 《网站协议》
我们凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求,请立即点击咨询我们或拨打咨询热线: 13761152229,我们会详细为你一一解答你心中的疑难。

项目经理在线

我们已经准备好了,你呢?

2020我们与您携手共赢,为您的企业形象保驾护航!

在线客服
联系方式

热线电话

13761152229

上班时间

周一到周五

公司电话

二维码
微信
线