为MySQL添加uid
(2016-01-24 14:05:41)
标签:
mysqluid |
分类: mysql源码修改 |
最近需要进行数据库的国家3级安全测试,其中就有一项是用户UID唯一标识。
MySQL本身并没有uid这个概念,使用host和user作为账户的唯一标识,但这种方式无法区分被删除后重建的账户与之前账户的区别。
例如账户u1@'%'删除后重新建了一个u1@'%',那么从mysql的角度根本无法区分这2个账户的区别。
为了解决这个问题,做了如下几种尝试:
1. 不修改源码,给user表建一个触发器,当新增时同时往另一张新表user_id(host, user, uid)进行插入,其中uid为自增列;删除时从user_id中删除对应的记录。
但一创建mysql就报错了,所不允许给系统表建立触发器。通过查看代码可知,创建触发器时会进行检查,如果目标表是mysql库下的,直接就报错。具体代码可以看sql/sql_trigger.cc的mysql_create_or_drop_trigger函数。
那我们是不是可以通过修改代码去掉这个检查呢?
答案是可以去掉这个检查,但没有意义。因为create user/grant/drop
user等账户管理SQL和普通的DML语句走的逻辑代码是完全不一样的,账户管理SQL的处理逻辑并没有对触发器的检查。
所以触发器方案无效。
2. 在创建新用户的时候是不是可以通过调用DML的insert语句接口往user_id表中插入一行数据?
注:想使用DML的insert接口是因为只有那个入口的代码逻辑才会处理自增列赋值。
答案也是否定的,改完后的代码直接出core了,因为mysql要求进行sql操作前需要一次性对需要锁定的表进行上锁,但账户管理SQL和DML的insert接口都会进行表上锁操作,强行整合到一起的话代码改动太大,稳定性没有保证,所以调用DML的insert语句接口的方案也是无效。
3. 修改源码,在创建和删除账户的代码处直接对user_id进行数据写入或删除
实际上mysql的账户管理SQL经过各种逻辑判断后也是直接对user表进行数据写入或删除,所以可以参照这个代码逻辑额外再多维护一张user_id表。
账户新增的代码入口为
sql/sql_acl.cc的mysql_create_user函数,大致的处理流程如下:
a.
打开并获取权限相关的表 open_grant_tables
注: 在open_grant_tables中,需要将
user_id一起处理
b. 创建或修改账户
replace_user_table
在这个函数中会准备出新用户的一行数据,并最终调用
table->file->ha_write_row(table->record[0]) 进行数据行写入
所以在这个函数中我们需要在准备user表的新增数据行时,同时也准备user_id表对应的数据行。
由于这里无法直接调用表自增逻辑,所以可以存储时间戳,本质上来说主要确保每次存储的uid值不相同就可以了,而时间戳正好可以满足这个需求。
如下是准备user_id表一行数据的代码:
if (uid_table) {
time_t
rawtime;
rawtime =
time(0);
time_long =
(long)rawtime;
buff_len =
(uint)sprintf(buff, "%ld", time_long);
if (buff_len
>= 512)
buff_len = 511;
buff[buff_len] = '\0';
uid_table->use_all_columns();
uid_table->field[0]->store(combo.host.str,combo.host.length,
system_charset_info);
uid_table->field[1]->store(combo.user.str,combo.user.length,
system_charset_info);
uid_table->field[2]->store(buff,buff_len,
system_charset_info);
}
最后通过如下代码进行持久化
if (!old_row_exists && uid_table) {
//write uid
uid_table->file->ha_write_row(uid_table->record[0]);
}
当然也可以通过grant语句新建账户,代码入口为sql/sql_acl.cc的
mysql_xxx_grant,他们最终都是调用replace_user_table进行新账户的创建的。
账户删除的代码入口为sql/sql_acl.cc的mysql_drop_user,大致的处理流程为:
a. 打开并获取权限相关的表 open_grant_tables
b. 进行用户删除,主要的函数调用路径为:
handle_grant_data->
handle_grant_table,最终通过modify_grant_table进行行记录删除
我们可以在
handle_grant_table中添加对user_id表的处理,大致的代码如下:
if (result && drop) {
TABLE *uid_table = tables[7].table;
uid_table->use_all_columns();
uid_table->field[0]->store(host_str,
user_from->host.length, system_charset_info);
uid_table->field[1]->store(user_str,
user_from->user.length, system_charset_info);
key_prefix_length=
(uid_table->key_info->key_part[0].store_length +
uid_table->key_info->key_part[1].store_length);
key_copy(user_key, uid_table->record[0], uid_table->key_info,
key_prefix_length);
error=
uid_table->file->ha_index_read_idx_map(uid_table->record[0],
0,
user_key, (key_part_map)3,
MySQL本身并没有uid这个概念,使用host和user作为账户的唯一标识,但这种方式无法区分被删除后重建的账户与之前账户的区别。
例如账户u1@'%'删除后重新建了一个u1@'%',那么从mysql的角度根本无法区分这2个账户的区别。
为了解决这个问题,做了如下几种尝试:
1. 不修改源码,给user表建一个触发器,当新增时同时往另一张新表user_id(host, user, uid)进行插入,其中uid为自增列;删除时从user_id中删除对应的记录。
2. 在创建新用户的时候是不是可以通过调用DML的insert语句接口往user_id表中插入一行数据?
3. 修改源码,在创建和删除账户的代码处直接对user_id进行数据写入或删除
a. 打开并获取权限相关的表
b. 进行用户删除,主要的函数调用路径为: