暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

[MYSQL] 离谱! 用shell实现mysql_config_editor功能. mysql免密登录不再安全了!

原创 大大刺猬 2024-03-06
387

导读

mysql的日常使用中, 可能会遇到 配置免密登录的环境. mysql的免密是在客户端配置的, 将IP端口,账号密码等信息加密保存在~/.mylogin.cnf文件中. 使用起来十分方便. 只需要指定名称即可连接到指定服务器, 比如 mysql --login-path=root

配置也比较简单, 可使用如下命令配置

mysql_config_editor set --login-path=root -h127.0.0.1 -P3314 -uroot -p

但是要交互的输入密码就很烦. 不支持STDIN输入密码. 这让自动化脚本就不那么自动了, 虽然也可以使用expect之类的自动输入密码, 但有的环境没有这个包. 说白了, 用起来就不那么舒服了.

所以决定自己写个mysql_config_editor.

  1. C语言版, 官方写了, 改改也能用, 但还要编译才能跑, 比较麻烦.
  2. python版, 也有大佬写了. 但python没得内置的加密包, 还得调用第三方包, 或者调用openssl来做, 但也很麻烦
  3. 那就使用shell来实现吧.
    尽管shell读写二进制数据很难受… 十分地难受.

原理解析

通过c/python版的源码我们可以得知 mysql_config_editor加密后的格式如下

对象 大小(字节) 描述
flag 4 填充符
key 20 key(不是realkey)
linesize 4 记录这一行数据的大小
data linesize 加密后的数据
n 依次类推就不细看了
linesize 4 记录这一行数据的大小
data linesize 加密后的数据

使用key创建一个AES KEY (20字节, 亦或. 是不是很眼熟-_- 很mysql连接用到的加密很像)

既然KEY也放在文件里面的, 那我们就可以根据这个KEY使用openssl解密出相关的内容了.
注: 加密模式为ecb (my_aes_128_ecb). 不用关心, 都交给openssl去做. 填充字符啥的, 也不用关心, openssl都会去做的.

为了考虑兼容性, 这里没有使用xxd, 而是使用的od, 怕有些环境没得vim… openssl的话是每个环境都有的(除非你没得ssh)

由于shell不支持二进制数据变量, 所以得存到临时文件, 要使用变量的话, 可以转为HEX 保存.
不多说了, 直接看例子吧

使用例子

脚本见文末, 本脚本是为了其它自动化脚本服务的, 所以没有写接口参数, 没得--help之类的功能. 主打一个能用就行.

解密

解密就比较简单了, 直接读取key, 然后生成AES KEY (官方保存的是原始的KEY). 根据这个AES KEY做AES解密. 就可以得到数据了.
用法如下

sh mysql_config_editor.sh decode 你的mylogin.cnf文件

例子如下:

08:38:03 [root@ddcw21 ~]#sh mysql_config_editor.sh decode ~/.mylogin.cnf [root] user = "root" password = "123456" host = "127.0.0.1" port = 3314 08:38:17 [root@ddcw21 ~]#

这个其实也可以使用 mysql_config_editor print --all 来查看的, 但密码是加密的.

加密

加密的话, 也比较简单, 就是生成随机的KEY, 写入文件, 然后根据这个KEY生成AES KEY去加密剩下的数据即可.
自动填充的数据都是由openssl实现的, 所以没啥好关注的.
用法如下:

sh mysql_config_editor.sh encode ~/.mylogin.cnf 账号密码等信息的文本文件

例子如下:

08:42:29 [root@ddcw21 ~]#cat mylogin.txt [root] user = "root" password = "123456" host = "127.0.0.1" port = 3314 08:42:33 [root@ddcw21 ~]#sh mysql_config_editor.sh encode ~/.mylogin.cnf mylogin.txt FINISH. FILENAME: /root/.mylogin.cnf 08:42:49 [root@ddcw21 ~]#mysql --login-path=root -e 'select @@version' +-----------+ | @@version | +-----------+ | 8.0.28 | +-----------+

总结

有了这个工具后, 以后就能自动配置mysql免密登录. 玩意忘了密码, 还能查看免密文件记录的密码.
但能查看~/.mylogin.cnf中记录的密码了. 那mysql_config_editor还安全么…

附源码

墨天轮地址: https://www.modb.pro/doc/126086
github地址:https://github.com/ddcw/ddcw/tree/master/shells/mysql_config_editor
也可以直接复制如下shell源码:

#!/usr/bin/env bash #write by ddcw @https://github.com/ddcw #mysql_config_editor的shell版, 不用再交互了. 支持加密解密~/.mylogin.cnf #用法: #解密 sh mysql_config_editor.sh decode ~/.mylogin.cnf /tmp/t20240305_out.txt #加密 sh mysql_config_editor.sh encode ~/.mylogin.cnf /tmp/t20240305_out.txt #/tmp/t20240305_out.txt 格式如下: aa=' [root] user = "root" password = "123456" host = "127.0.0.1" port = 3314 socket = "/data/mysql_3314/run/mysql.sock" ' OP=$1 #操作, 只有decode, encode BFILE=$2 #二进制文件, 就是.mylogin.cnf TFILE=$3 #文本文件 tmpfile1="/tmp/.tmpfileformysql_config_editorbyddcw1" #临时文件, 用于中间交互数据的. tmpfile2="/tmp/.tmpfileformysql_config_editorbyddcw2" #临时文件,保存结果数据的 # 输入: 二进制密钥的十六进制字符串形式 # 输出: aes key 放到 tmpfile2 realkey() { keyhex=$1 # 传入的是二进制密钥的十六进制字符串形式 # 初始化一个空的十六进制字符串表示的密钥 rkey_hex="" for (( i=0; i<16; i++ )); do rkey_hex+="00" done for (( i=0; i<${#keyhex}; i+=2 )); do byte_hex=${keyhex:$i:2} dec_val=$((16#$byte_hex)) index=$((i/2%16)) index_hex_start=$((index*2)) rkey_byte_hex=${rkey_hex:$index_hex_start:2} rkey_byte_dec=$((16#$rkey_byte_hex)) xor_byte_dec=$((rkey_byte_dec^dec_val)) rkey_hex=$(printf "%s%.2x%s" "${rkey_hex:0:$index_hex_start}" "$xor_byte_dec" "${rkey_hex:$index_hex_start+2}") done echo $rkey_hex > ${tmpfile2} } #${BFILE} start读n字节到 ${tmpfile2} readsize(){ start=$1 size=$2 dd if=${BFILE} bs=1 skip=${start} count=${size} of=${tmpfile2} 2>/dev/null } #读hex readhex(){ start=$1 size=$2 readsize ${start} ${size} dd if=${BFILE} bs=1 skip=${start} count=${size} 2>/dev/null | od -An -tx1 | tr -d '\n ' | sed ':a;N;$!ba;s/\n//g' > ${tmpfile2} } #读int32 readuint32(){ start=$1 size=4 od -An -t u4 -j ${start} -N ${size} ${BFILE} | awk '{print $1}' > ${tmpfile2} } openssl_decode(){ key=$1 hex_string=$2 : > ${tmpfile1} for (( i=0; i<${#hex_string}; i+=2 )); do byte="\\x${hex_string:$i:2}" printf "$byte" >> "${tmpfile1}" done openssl enc -aes-128-ecb -d -K ${key} -in ${tmpfile1} -out ${tmpfile2} } openssl_encode(){ key=$1 #HEX hex_string=$2 : > ${tmpfile2} : > ${tmpfile1} for (( i=0; i<${#hex_string}; i+=2 )); do byte="\\x${hex_string:$i:2}" printf "$byte" >> "${tmpfile1}" done printf '\n' >> "${tmpfile1}" #要换行, 不然识别不了 openssl enc -aes-128-ecb -K ${key} -in ${tmpfile1} -out ${tmpfile2} } write_hex(){ hex_string=$1 filename=$2 for (( i=0; i<${#hex_string}; i+=2 )); do byte="\\x${hex_string:$i:2}" printf "$byte" >> ${filename} done } write_int32(){ int_value=$1 printf "\\x$(printf '%08x' $int_value | cut -c7-8)" >> ${BFILE} printf "\\x$(printf '%08x' $int_value | cut -c5-6)" >> ${BFILE} printf "\\x$(printf '%08x' $int_value | cut -c3-4)" >> ${BFILE} printf "\\x$(printf '%08x' $int_value | cut -c1-2)" >> ${BFILE} } encode(){ #echo -e "${TFILE} --> ${BFILE}" : > ${BFILE} key=$(head -c 20 /dev/urandom | od -v -A n -t x1 | tr -d ' \n ') realkey ${key} real_key=`cat ${tmpfile2}` #写个0 填充 write_int32 0 write_hex ${key} ${BFILE} #循环处理TFILE文件了 while IFS= read -r line || [[ -n "$line" ]]; do if [ -z "$line" ]; then continue #跳过空行 fi byte_count=$(echo -n "$line" | wc -c) #实际大小 ((byte_count++)) value=$(echo -n "$line" | od -v -A n -t x1 | tr -d ' \n') pad_len=$(( (byte_count/ 16 + 1) * 16 )) #进1 write_int32 ${pad_len} #写大小 4bytes openssl_encode ${real_key} ${value} #写数据 cat ${tmpfile2} >> ${BFILE} done < ${TFILE} echo "FINISH. FILENAME: ${BFILE}" chmod 600 ${BFILE} } decode(){ echo "" #echo -e "${BFILE} --> ${TFILE}" #: > ${TFILE} offset=4 #开始4字节不管了 readhex ${offset} 20 offset=$[ ${offset} + 20 ] realkey `cat ${tmpfile2}` real_key=`cat ${tmpfile2}` #echo "real_key" ${real_key} maxsize=`stat -c "%s" ${BFILE}` while [ 1 -eq 1 ];do #echo "OFFSET START: ${offset}" readuint32 ${offset} 4 offset=$[ ${offset} + 4 ] keysize=`cat ${tmpfile2}` #echo "SIZE: ${keysize}" if [ "${keysize}" == "" ] || [ ${offset} -ge ${maxsize} ];then break fi readhex ${offset} ${keysize} offset=$[ ${offset} + ${keysize} ] hexstring=`cat ${tmpfile2}` #echo "real_key: ${real_key} hexstring: ${hexstring}" openssl_decode "${real_key}" "${hexstring}" #cat ${tmpfile2} >> ${TFILE} cat ${tmpfile2} #echo "OFFSET STOP: ${offset}" done #cat ${TFILE} echo "" } if [ "${OP^^}" == "DECODE" ];then if [ ! -f ${BFILE} ];then echo "${BFILE} not exists" exit 1 fi decode elif [ "${OP^^}" == "ENCODE" ];then if [ ! -f ${TFILE} ];then echo "${TFILE} not exists" exit 1 fi if [ -f ${BFILE} ];then mv ${BFILE} /tmp/.mylogin.cnf.backbyddcw_`date +%s` #存在的话, 就备份一手, 免得操作有问题 fi encode else echo "unknown ${@}" fi
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论