玩转大数据

Zookeeper 3.4.6的四字命令安全问题

2019-08-28  本文已影响0人  AlienPaul

问题背景

zookeeper为维护人员提供了方便的四字命令。可以返回zookeeper集群的信息,主机的配置,甚至可以关闭zookeeper状态。因此有时候需要通过配置来禁用这些命令。

目前集群中使用的是zookeeper 3.4.6。发现在zoo.cfg中做出如下配置:

4lw.commands.whitelist=ruok

经测试其他的四字命令仍然可以使用,该配置项没有效果。原因需要进一步研究。

Zookeeper3.4.6相关代码研究

org/apache/zookeeper/server/NettyServerCnxn.javacheckFourLetterWord方法

private boolean checkFourLetterWord(final Channel channel,
        ChannelBuffer message, final int len) throws IOException
{
    // We take advantage of the limited size of the length to look
    // for cmds. They are all 4-bytes which fits inside of an int
    String cmd = cmd2String.get(len);
    if (cmd == null) {
        return false;
    }
    channel.setInterestOps(0).awaitUninterruptibly();
    LOG.info("Processing " + cmd + " command from "
            + channel.getRemoteAddress());
    packetReceived();

    final PrintWriter pwriter = new PrintWriter(
            new BufferedWriter(new SendBufferWriter()));
    
    // 依次判断调用的是哪个四字命令
    if (len == ruokCmd) {
        RuokCommand ruok = new RuokCommand(pwriter);
        ruok.start();
        return true;
    } else if (len == getTraceMaskCmd) {
        TraceMaskCommand tmask = new TraceMaskCommand(pwriter);
        tmask.start();
        return true;
    } else if (len == setTraceMaskCmd) {
        ByteBuffer mask = ByteBuffer.allocate(4);
        message.readBytes(mask);

        bb.flip();
        long traceMask = mask.getLong();
        ZooTrace.setTextTraceLevel(traceMask);
        SetTraceMaskCommand setMask = new SetTraceMaskCommand(pwriter, traceMask);
        setMask.start();
        return true;
    } else if (len == enviCmd) {
        EnvCommand env = new EnvCommand(pwriter);
        env.start();
        return true;
    } else if (len == confCmd) {
        ConfCommand ccmd = new ConfCommand(pwriter);
        ccmd.start();
        return true;
    } else if (len == srstCmd) {
        StatResetCommand strst = new StatResetCommand(pwriter);
        strst.start();
        return true;
    } else if (len == crstCmd) {
        CnxnStatResetCommand crst = new CnxnStatResetCommand(pwriter);
        crst.start();
        return true;
    } else if (len == dumpCmd) {
        DumpCommand dump = new DumpCommand(pwriter);
        dump.start();
        return true;
    } else if (len == statCmd || len == srvrCmd) {
        StatCommand stat = new StatCommand(pwriter, len);
        stat.start();
        return true;
    } else if (len == consCmd) {
        ConsCommand cons = new ConsCommand(pwriter);
        cons.start();
        return true;
    } else if (len == wchpCmd || len == wchcCmd || len == wchsCmd) {
        WatchCommand wcmd = new WatchCommand(pwriter, len);
        wcmd.start();
        return true;
    } else if (len == mntrCmd) {
        MonitorCommand mntr = new MonitorCommand(pwriter);
        mntr.start();
        return true;
    } else if (len == isroCmd) {
        IsroCommand isro = new IsroCommand(pwriter);
        isro.start();
        return true;
    }
    return false;
}

发现该方法根本没有检查四字命令是否在白名单中。下面研究下同样方法在zookeeper 3.5.5中的实现。

Zookeeper3.5.5 四字命令白名单的实现

我们找到org/apache/zookeeper/server/NettyServerCnxn.javacheckFourLetterWord方法

/** Return if four letter word found and responded to, otw false **/
    private boolean checkFourLetterWord(final Channel channel, ByteBuf message, final int len) {
        
        // 之前的代码省略...
        
        if (!FourLetterCommands.isEnabled(cmd)) {
            LOG.debug("Command {} is not executed because it is not in the whitelist.", cmd);
            NopCommand nopCmd = new NopCommand(pwriter, this, cmd +
                    " is not executed because it is not in the whitelist.");
            nopCmd.start();
            return true;
        }
        // 以后的代码省略...
}

如果四字命令是在白名单中的,该方法会执行四字命令,并返回true。否则会返回false。

继续查看FourLetterCommandsisEnabled方法。

此处的whiteListedCommands为一个HashSet,第一次使用四字命令的时候才会读取配置文件,延迟初始化。

public synchronized static boolean isEnabled(String command) {
    // 如果whiteListedCommands已经初始化,则检查command是否在whiteListedCommands中
    if (whiteListInitialized) {
        return whiteListedCommands.contains(command);
    }


    // 获取系统的zookeeper.4lw.commands.whitelist变量值
    String commands = System.getProperty(ZOOKEEPER_4LW_COMMANDS_WHITELIST);
    if (commands != null) {
        // 白名单中多个命令以逗号分隔
        String[] list = commands.split(",");
        for (String cmd : list) {
            // 如果配置的是星号,说明运行运行所有的四字命令,将cmd2String的所有value加入到whiteListedCommands中
            if (cmd.trim().equals("*")) {
                for (Map.Entry<Integer, String> entry : cmd2String.entrySet()) {
                    whiteListedCommands.add(entry.getValue());
                }
                break;
            }
            
            // 其余情况,把属性中配置的命令加入whiteListedCommands
            if (!cmd.trim().isEmpty()) {
                whiteListedCommands.add(cmd.trim());
            }
        }
    }

    // 由于isro和srvr是zookeeper内部使用的,因此需要额外将他们添加进来。

    // It is sad that isro and srvr are used by ZooKeeper itself. Need fix this
    // before deprecating 4lw.
    if (System.getProperty("readonlymode.enabled", "false").equals("true")) {
        // 如果zookeeper是只读模式,增加isro命令到白名单
        whiteListedCommands.add("isro");
    }
    // zkServer.sh depends on "srvr".
    // 增加srvr命令的支持,zkServer脚本的status命令需要用到
    whiteListedCommands.add("srvr");
    whiteListInitialized = true;
    LOG.info("The list of known four letter word commands is : {}", Arrays.asList(cmd2String));
    LOG.info("The list of enabled four letter word commands is : {}", Arrays.asList(whiteListedCommands));
    
    //whiteListedCommands初始化完毕,返回结果,下次zookeeper重启前不会再重新生成whiteListedCommands
    return whiteListedCommands.contains(command);
}

isEnabled方法中使用了whiteListedCommands变量,该变量保存了允许使用的四字命令。但是4lw.commands.whitelist配置是什么时候被写入系统变量的?

org/apache/zookeeper/server/quorum/QuorumPeerConfig.java文件的parseProperties方法:

public void parseProperties(Properties zkProp)
    throws IOException, ConfigException {

    // ...
    
    if (...) {
        ...
    } 
    ...
    else {
        // QuorumPeerConfig一部分属性名称被设置为成员变量。除这些外其他的属性会被作为系统变量设置,加上zookeeper.前缀。当然4lw.commands.whitelist也被设置为了系统变量
        System.setProperty("zookeeper." + key, value);
    }
    // ...
}

解决方法

有两个方案可用:

  1. 修改zookeeper 3.4.6的源码,加入类似的功能。需要测试是否影响原有功能。
  2. 升级zookeeper 为3.5.5稳定版。但需要测试和其他组件的兼容性。

相关链接

官网四字命令解释

上一篇 下一篇

猜你喜欢

热点阅读