Browse Source

ZOOKEEPER-2693: DOS attack on wchp/wchc four letter words (4lw)

The previous patch is not valid anymore as design has changed. Update patch based on phunt 's feedback.
See ZOOKEEPER-2693 JIRA for details. This patch is still targeting for branch-3.5 / master. Patch for branch-3.4 will be created shortly.

The gist of the patch is:
* Introduced a new configuration option which encodes a white list of 4lw commands. A 4lw command that is not on the white list will not be enabled on server side.
* For branch-3.5, the default value of the configuration option is empty. We want users to use the alternative AdminServer anyway.

Author: Michael Han <hanm@apache.org>

Reviewers: Rakesh Radhakrishnan <rakeshrapache.org>, Mohammad Arshad <arshad@apache.org>, Flavio Junqueira <fpj@apache.org>, Edward Ribeiro <edward.ribeiro@gmail.com>, Abraham Fine <afine@apache.org>

Closes #179 from hanm/ZOOKEEPER-2693 and squashes the following commits:

1ffd114 [Michael Han] Tweak tests.
b4c421d [Michael Han] ZOOKEEPER-2693: DOS attack on wchp/wchc four letter words (4lw). Introduce a new configuration option 4lw.commands.whitelist that disables all 4lw (except srvr) by default to avoid the exploits reported on wchp/wchc commands.
Michael Han 8 years ago
parent
commit
5fe68506f2

+ 55 - 2
src/docs/src/documentation/content/xdocs/zookeeperAdmin.xml

@@ -1155,6 +1155,40 @@ server.3=zoo3:2888:3888</programlisting>
             </listitem>
             </listitem>
           </varlistentry>
           </varlistentry>
 
 
+          <varlistentry>
+            <term>4lw.commands.whitelist</term>
+
+            <listitem>
+              <para>(Java system property: <emphasis
+                      role="bold">zookeeper.4lw.commands.whitelist</emphasis>)</para>
+
+              <para><emphasis role="bold">New in 3.5.3:</emphasis>
+                A list of comma separated <ulink url="#sc_4lw">Four Letter Words</ulink>
+                commands that user wants to use. A valid Four Letter Words
+                command must be put in this list else ZooKeeper server will
+                not enable the command.
+                By default the whitelist only contains "srvr" command
+                which zkServer.sh uses. The rest of four letter word commands are disabled
+                by default.
+              </para>
+
+              <para>Here's an example of the configuration that enables stat, ruok, conf, and isro
+              command while disabling the rest of Four Letter Words command:</para>
+              <programlisting>
+                4lw.commands.whitelist=stat, ruok, conf, isro
+              </programlisting>
+
+              <para>If you really need enable all four letter word commands by default, you can use
+                the asterisk option so you don't have to include every command one by one in the list.
+                As an example, this will enable all four letter word commands:
+              </para>
+              <programlisting>
+                4lw.commands.whitelist=*
+              </programlisting>
+
+            </listitem>
+          </varlistentry>
+
         </variablelist>
         </variablelist>
         <para></para>
         <para></para>
       </section>
       </section>
@@ -1639,7 +1673,7 @@ server.3=zoo3:2888:3888</programlisting>
     <section id="sc_zkCommands">
     <section id="sc_zkCommands">
       <title>ZooKeeper Commands</title>
       <title>ZooKeeper Commands</title>
 
 
-      <section>
+      <section id="sc_4lw">
         <title>The Four Letter Words</title>
         <title>The Four Letter Words</title>
         <para>ZooKeeper responds to a small set of commands. Each command is
         <para>ZooKeeper responds to a small set of commands. Each command is
         composed of four letters. You issue the commands to ZooKeeper via telnet
         composed of four letters. You issue the commands to ZooKeeper via telnet
@@ -1650,7 +1684,16 @@ server.3=zoo3:2888:3888</programlisting>
         while "srvr" and "cons" give extended details on server and
         while "srvr" and "cons" give extended details on server and
         connections respectively.</para>
         connections respectively.</para>
 
 
-        <variablelist>
+        <para><emphasis role="bold">New in 3.5.3:</emphasis>
+          Four Letter Words need to be explicitly white listed before using.
+          Please refer <emphasis role="bold">4lw.commands.whitelist</emphasis>
+           described in <ulink url="#sc_clusterOptions">
+            cluster configuration section</ulink> for details.
+          Moving forward, Four Letter Words will be deprecated, please use
+          <ulink url="#sc_adminserver">AdminServer</ulink> instead.
+        </para>
+
+      <variablelist>
           <varlistentry>
           <varlistentry>
             <term>conf</term>
             <term>conf</term>
 
 
@@ -2125,6 +2168,16 @@ server.3=zoo3:2888:3888</programlisting>
             usage limit that would cause the system to swap.</para>
             usage limit that would cause the system to swap.</para>
           </listitem>
           </listitem>
         </varlistentry>
         </varlistentry>
+
+        <varlistentry>
+          <term>Publicly accessible deployment</term>
+          <listitem>
+            <para>
+              A ZooKeeper ensemble is expected to operate in a trusted computing environment.
+              It is thus recommended to deploy ZooKeeper behind a firewall.
+            </para>
+          </listitem>
+        </varlistentry>
       </variablelist>
       </variablelist>
     </section>
     </section>
 
 

+ 17 - 4
src/java/main/org/apache/zookeeper/server/NIOServerCnxn.java

@@ -47,6 +47,7 @@ import org.apache.zookeeper.server.NIOServerCnxnFactory.SelectorThread;
 import org.apache.zookeeper.server.command.CommandExecutor;
 import org.apache.zookeeper.server.command.CommandExecutor;
 import org.apache.zookeeper.server.command.FourLetterCommands;
 import org.apache.zookeeper.server.command.FourLetterCommands;
 import org.apache.zookeeper.server.command.SetTraceMaskCommand;
 import org.apache.zookeeper.server.command.SetTraceMaskCommand;
+import org.apache.zookeeper.server.command.NopCommand;
 import org.slf4j.Logger;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.LoggerFactory;
 
 
@@ -478,12 +479,11 @@ public class NIOServerCnxn extends ServerCnxn {
     {
     {
         // We take advantage of the limited size of the length to look
         // 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
         // for cmds. They are all 4-bytes which fits inside of an int
-        String cmd = FourLetterCommands.getCmdMapView().get(len);
-        if (cmd == null) {
+        if (!FourLetterCommands.isKnown(len)) {
             return false;
             return false;
         }
         }
-        LOG.info("Processing " + cmd + " command from "
-                + sock.socket().getRemoteSocketAddress());
+
+        String cmd = FourLetterCommands.getCommandString(len);
         packetReceived();
         packetReceived();
 
 
         /** cancel the selection key to remove the socket handling
         /** cancel the selection key to remove the socket handling
@@ -505,6 +505,19 @@ public class NIOServerCnxn extends ServerCnxn {
 
 
         final PrintWriter pwriter = new PrintWriter(
         final PrintWriter pwriter = new PrintWriter(
                 new BufferedWriter(new SendBufferWriter()));
                 new BufferedWriter(new SendBufferWriter()));
+
+        // ZOOKEEPER-2693: don't execute 4lw if it's not enabled.
+        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;
+        }
+
+        LOG.info("Processing " + cmd + " command from "
+                + sock.socket().getRemoteSocketAddress());
+
         if (len == FourLetterCommands.setTraceMaskCmd) {
         if (len == FourLetterCommands.setTraceMaskCmd) {
             incomingBuffer = ByteBuffer.allocate(8);
             incomingBuffer = ByteBuffer.allocate(8);
             int rc = sock.read(incomingBuffer);
             int rc = sock.read(incomingBuffer);

+ 18 - 4
src/java/main/org/apache/zookeeper/server/NettyServerCnxn.java

@@ -41,6 +41,7 @@ import org.apache.zookeeper.proto.ReplyHeader;
 import org.apache.zookeeper.proto.WatcherEvent;
 import org.apache.zookeeper.proto.WatcherEvent;
 import org.apache.zookeeper.server.command.CommandExecutor;
 import org.apache.zookeeper.server.command.CommandExecutor;
 import org.apache.zookeeper.server.command.FourLetterCommands;
 import org.apache.zookeeper.server.command.FourLetterCommands;
+import org.apache.zookeeper.server.command.NopCommand;
 import org.apache.zookeeper.server.command.SetTraceMaskCommand;
 import org.apache.zookeeper.server.command.SetTraceMaskCommand;
 import org.jboss.netty.buffer.ChannelBuffer;
 import org.jboss.netty.buffer.ChannelBuffer;
 import org.jboss.netty.buffer.ChannelBuffers;
 import org.jboss.netty.buffer.ChannelBuffers;
@@ -267,17 +268,30 @@ public class NettyServerCnxn extends ServerCnxn {
     {
     {
         // We take advantage of the limited size of the length to look
         // 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
         // for cmds. They are all 4-bytes which fits inside of an int
-        String cmd = FourLetterCommands.getCmdMapView().get(len);
-        if (cmd == null) {
+        if (!FourLetterCommands.isKnown(len)) {
             return false;
             return false;
         }
         }
+
+        String cmd = FourLetterCommands.getCommandString(len);
+
         channel.setInterestOps(0).awaitUninterruptibly();
         channel.setInterestOps(0).awaitUninterruptibly();
-        LOG.info("Processing " + cmd + " command from "
-                + channel.getRemoteAddress());
         packetReceived();
         packetReceived();
 
 
         final PrintWriter pwriter = new PrintWriter(
         final PrintWriter pwriter = new PrintWriter(
                 new BufferedWriter(new SendBufferWriter()));
                 new BufferedWriter(new SendBufferWriter()));
+
+        // ZOOKEEPER-2693: don't execute 4lw if it's not enabled.
+        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;
+        }
+
+        LOG.info("Processing " + cmd + " command from "
+                + channel.getRemoteAddress());
+
        if (len == FourLetterCommands.setTraceMaskCmd) {
        if (len == FourLetterCommands.setTraceMaskCmd) {
             ByteBuffer mask = ByteBuffer.allocate(8);
             ByteBuffer mask = ByteBuffer.allocate(8);
             message.readBytes(mask);
             message.readBytes(mask);

+ 81 - 5
src/java/main/org/apache/zookeeper/server/command/FourLetterCommands.java

@@ -18,10 +18,15 @@
 
 
 package org.apache.zookeeper.server.command;
 package org.apache.zookeeper.server.command;
 
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import java.nio.ByteBuffer;
 import java.nio.ByteBuffer;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Arrays;
 
 
 /**
 /**
  * This class contains constants for all the four letter commands
  * This class contains constants for all the four letter commands
@@ -153,11 +158,82 @@ public class FourLetterCommands {
      */
      */
     public final static int telnetCloseCmd = 0xfff4fffd;
     public final static int telnetCloseCmd = 0xfff4fffd;
 
 
-    final static HashMap<Integer, String> cmd2String =
-        new HashMap<Integer, String>();
+    private static final String ZOOKEEPER_4LW_COMMANDS_WHITELIST = "zookeeper.4lw.commands.whitelist";
+
+    private static final Logger LOG = LoggerFactory.getLogger(FourLetterCommands.class);
+
+    private static final Map<Integer, String> cmd2String = new HashMap<Integer, String>();
+
+    private static final Set<String> whiteListedCommands = new HashSet<String>();
+
+    private static boolean whiteListInitialized = false;
+
+    // @VisibleForTesting
+    public static void resetWhiteList() {
+        whiteListInitialized = false;
+        whiteListedCommands.clear();
+    }
+
+    /**
+     * Return the string representation of the specified command code.
+     */
+    public static String getCommandString(int command) {
+        return cmd2String.get(command);
+    }
+
+    /**
+     * Check if the specified command code is from a known command.
+     *
+     * @param command The integer code of command.
+     * @return true if the specified command is known, false otherwise.
+     */
+    public static boolean isKnown(int command) {
+        return cmd2String.containsKey(command);
+    }
+
+    /**
+     * Check if the specified command is enabled.
+     *
+     * In ZOOKEEPER-2693 we introduce a configuration option to only
+     * allow a specific set of white listed commands to execute.
+     * A command will only be executed if it is also configured
+     * in the white list.
+     *
+     * @param command The command string.
+     * @return true if the specified command is enabled
+     */
+    public static boolean isEnabled(String command) {
+        if (whiteListInitialized) {
+            return whiteListedCommands.contains(command);
+        }
+
+        String commands = System.getProperty(ZOOKEEPER_4LW_COMMANDS_WHITELIST);
+        if (commands != null) {
+            String[] list = commands.split(",");
+            for (String cmd : list) {
+                if (cmd.trim().equals("*")) {
+                    for (Map.Entry<Integer, String> entry : cmd2String.entrySet()) {
+                        whiteListedCommands.add(entry.getValue());
+                    }
+                    break;
+                }
+                if (!cmd.trim().isEmpty()) {
+                    whiteListedCommands.add(cmd.trim());
+                }
+            }
+        }
 
 
-    public static Map<Integer, String> getCmdMapView() {
-        return Collections.unmodifiableMap(cmd2String);
+        // 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")) {
+            whiteListedCommands.add("isro");
+        }
+        // zkServer.sh depends on "srvr".
+        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));
+        return whiteListedCommands.contains(command);
     }
     }
 
 
     // specify all of the commands that are available
     // specify all of the commands that are available

+ 41 - 0
src/java/main/org/apache/zookeeper/server/command/NopCommand.java

@@ -0,0 +1,41 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zookeeper.server.command;
+
+import java.io.PrintWriter;
+
+import org.apache.zookeeper.server.ServerCnxn;
+
+/**
+ * A command that does not do anything except reply to client with predefined message.
+ * It is used to inform clients who execute none white listed four letter word commands.
+ */
+public class NopCommand extends AbstractFourLetterCommand {
+    private String msg;
+
+    public NopCommand(PrintWriter pw, ServerCnxn serverCnxn, String msg) {
+        super(pw, serverCnxn);
+        this.msg = msg;
+    }
+
+    @Override
+    public void commandRun() {
+        pw.println(msg);
+    }
+}

+ 3 - 0
src/java/test/org/apache/zookeeper/ZKTestCase.java

@@ -51,6 +51,9 @@ public class ZKTestCase {
             // accidentally attempting to start multiple admin servers on the
             // accidentally attempting to start multiple admin servers on the
             // same port.
             // same port.
             System.setProperty("zookeeper.admin.enableServer", "false");
             System.setProperty("zookeeper.admin.enableServer", "false");
+            // ZOOKEEPER-2693 disables all 4lw by default.
+            // Here we enable the 4lw which ZooKeeper tests depends.
+            System.setProperty("zookeeper.4lw.commands.whitelist", "*");
             testName = method.getName();
             testName = method.getName();
             LOG.info("STARTING " + testName);
             LOG.info("STARTING " + testName);
         }
         }

+ 251 - 0
src/java/test/org/apache/zookeeper/test/FourLetterWordsWhiteListTest.java

@@ -0,0 +1,251 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zookeeper.test;
+
+import java.io.IOException;
+
+import org.apache.zookeeper.TestableZooKeeper;
+import org.apache.zookeeper.common.X509Exception.SSLContextException;
+
+import static org.apache.zookeeper.client.FourLetterWordMain.send4LetterWord;
+
+import org.apache.zookeeper.server.command.FourLetterCommands;
+import org.junit.Assert;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FourLetterWordsWhiteListTest extends ClientBase {
+    protected static final Logger LOG =
+        LoggerFactory.getLogger(FourLetterWordsWhiteListTest.class);
+
+    /*
+     * ZOOKEEPER-2693: test white list of four letter words.
+     * For 3.5.x default white list is empty. Verify that is
+     * the case (except 'stat' command which is enabled in ClientBase
+     * which other tests depend on.).
+     */
+    @Test(timeout=30000)
+    public void testFourLetterWordsAllDisabledByDefault() throws Exception {
+        stopServer();
+        FourLetterCommands.resetWhiteList();
+        System.setProperty("zookeeper.4lw.commands.whitelist", "stat");
+        startServer();
+
+        // Default white list for 3.5.x is empty, so all command should fail.
+        verifyAllCommandsFail();
+
+        TestableZooKeeper zk = createClient();
+
+        verifyAllCommandsFail();
+
+        zk.getData("/", true, null);
+
+        verifyAllCommandsFail();
+
+        zk.close();
+
+        verifyFuzzyMatch("stat", "Outstanding");
+        verifyAllCommandsFail();
+    }
+
+    @Test(timeout=30000)
+    public void testFourLetterWordsEnableSomeCommands() throws Exception {
+        stopServer();
+        FourLetterCommands.resetWhiteList();
+        System.setProperty("zookeeper.4lw.commands.whitelist", "stat, ruok, isro");
+        startServer();
+        // stat, ruok and isro are white listed.
+        verifyFuzzyMatch("stat", "Outstanding");
+        verifyExactMatch("ruok", "imok");
+        verifyExactMatch("isro", "rw");
+
+        // Rest of commands fail.
+        verifyExactMatch("conf", generateExpectedMessage("conf"));
+        verifyExactMatch("cons", generateExpectedMessage("cons"));
+        verifyExactMatch("crst", generateExpectedMessage("crst"));
+        verifyExactMatch("dirs", generateExpectedMessage("dirs"));
+        verifyExactMatch("dump", generateExpectedMessage("dump"));
+        verifyExactMatch("envi", generateExpectedMessage("envi"));
+        verifyExactMatch("gtmk", generateExpectedMessage("gtmk"));
+        verifyExactMatch("stmk", generateExpectedMessage("stmk"));
+        verifyExactMatch("srst", generateExpectedMessage("srst"));
+        verifyExactMatch("wchc", generateExpectedMessage("wchc"));
+        verifyExactMatch("wchp", generateExpectedMessage("wchp"));
+        verifyExactMatch("wchs", generateExpectedMessage("wchs"));
+        verifyExactMatch("mntr", generateExpectedMessage("mntr"));
+    }
+
+    @Test(timeout=30000)
+    public void testISROEnabledWhenReadOnlyModeEnabled() throws Exception {
+        stopServer();
+        FourLetterCommands.resetWhiteList();
+        System.setProperty("zookeeper.4lw.commands.whitelist", "stat");
+        System.setProperty("readonlymode.enabled", "true");
+        startServer();
+        verifyExactMatch("isro", "rw");
+        System.clearProperty("readonlymode.enabled");
+    }
+
+    @Test(timeout=30000)
+    public void testFourLetterWordsInvalidConfiguration() throws Exception {
+        stopServer();
+        FourLetterCommands.resetWhiteList();
+        System.setProperty("zookeeper.4lw.commands.whitelist", "foo bar" +
+                " foo,,, " +
+                "bar :.,@#$%^&*() , , , , bar, bar, stat,        ");
+        startServer();
+
+        // Just make sure we are good when admin made some mistakes in config file.
+        verifyAllCommandsFail();
+        // But still, what's valid in white list will get through.
+        verifyFuzzyMatch("stat", "Outstanding");
+    }
+
+    @Test(timeout=30000)
+    public void testFourLetterWordsEnableAllCommandsThroughAsterisk() throws Exception {
+        stopServer();
+        FourLetterCommands.resetWhiteList();
+        System.setProperty("zookeeper.4lw.commands.whitelist", "*");
+        startServer();
+        verifyAllCommandsSuccess();
+    }
+
+    @Test(timeout=30000)
+    public void testFourLetterWordsEnableAllCommandsThroughExplicitList() throws Exception {
+        stopServer();
+        FourLetterCommands.resetWhiteList();
+        System.setProperty("zookeeper.4lw.commands.whitelist",
+                "ruok, envi, conf, stat, srvr, cons, dump," +
+                        "wchs, wchp, wchc, srst, crst, " +
+                        "dirs, mntr, gtmk, isro, stmk");
+        startServer();
+        verifyAllCommandsSuccess();
+    }
+
+
+    private void verifyAllCommandsSuccess() throws Exception {
+        verifyExactMatch("ruok", "imok");
+        verifyFuzzyMatch("envi", "java.version");
+        verifyFuzzyMatch("conf", "clientPort");
+        verifyFuzzyMatch("stat", "Outstanding");
+        verifyFuzzyMatch("srvr", "Outstanding");
+        verifyFuzzyMatch("cons", "queued");
+        verifyFuzzyMatch("dump", "Session");
+        verifyFuzzyMatch("wchs", "watches");
+        verifyFuzzyMatch("wchp", "");
+        verifyFuzzyMatch("wchc", "");
+
+        verifyFuzzyMatch("srst", "reset");
+        verifyFuzzyMatch("crst", "reset");
+
+        verifyFuzzyMatch("stat", "Outstanding");
+        verifyFuzzyMatch("srvr", "Outstanding");
+        verifyFuzzyMatch("cons", "queued");
+        verifyFuzzyMatch("gtmk", "306");
+        verifyFuzzyMatch("isro", "rw");
+
+        TestableZooKeeper zk = createClient();
+        String sid = getHexSessionId(zk.getSessionId());
+
+        verifyFuzzyMatch("stat", "queued");
+        verifyFuzzyMatch("srvr", "Outstanding");
+        verifyFuzzyMatch("cons", sid);
+        verifyFuzzyMatch("dump", sid);
+        verifyFuzzyMatch("dirs", "size");
+
+        zk.getData("/", true, null);
+
+        verifyFuzzyMatch("stat", "queued");
+        verifyFuzzyMatch("srvr", "Outstanding");
+        verifyFuzzyMatch("cons", sid);
+        verifyFuzzyMatch("dump", sid);
+
+        verifyFuzzyMatch("wchs", "watching 1");
+        verifyFuzzyMatch("wchp", sid);
+        verifyFuzzyMatch("wchc", sid);
+        verifyFuzzyMatch("dirs", "size");
+        zk.close();
+
+        verifyExactMatch("ruok", "imok");
+        verifyFuzzyMatch("envi", "java.version");
+        verifyFuzzyMatch("conf", "clientPort");
+        verifyFuzzyMatch("stat", "Outstanding");
+        verifyFuzzyMatch("srvr", "Outstanding");
+        verifyFuzzyMatch("cons", "queued");
+        verifyFuzzyMatch("dump", "Session");
+        verifyFuzzyMatch("wchs", "watch");
+        verifyFuzzyMatch("wchp", "");
+        verifyFuzzyMatch("wchc", "");
+
+        verifyFuzzyMatch("srst", "reset");
+        verifyFuzzyMatch("crst", "reset");
+
+        verifyFuzzyMatch("stat", "Outstanding");
+        verifyFuzzyMatch("srvr", "Outstanding");
+        verifyFuzzyMatch("cons", "queued");
+        verifyFuzzyMatch("mntr", "zk_server_state\tstandalone");
+        verifyFuzzyMatch("mntr", "num_alive_connections");
+        verifyFuzzyMatch("stat", "Connections");
+        verifyFuzzyMatch("srvr", "Connections");
+        verifyFuzzyMatch("dirs", "size");
+    }
+
+    private void verifyAllCommandsFail() throws Exception {
+        verifyExactMatch("ruok", generateExpectedMessage("ruok"));
+        verifyExactMatch("conf", generateExpectedMessage("conf"));
+        verifyExactMatch("cons", generateExpectedMessage("cons"));
+        verifyExactMatch("crst", generateExpectedMessage("crst"));
+        verifyExactMatch("dirs", generateExpectedMessage("dirs"));
+        verifyExactMatch("dump", generateExpectedMessage("dump"));
+        verifyExactMatch("envi", generateExpectedMessage("envi"));
+        verifyExactMatch("gtmk", generateExpectedMessage("gtmk"));
+        verifyExactMatch("stmk", generateExpectedMessage("stmk"));
+        verifyExactMatch("srst", generateExpectedMessage("srst"));
+        verifyExactMatch("wchc", generateExpectedMessage("wchc"));
+        verifyExactMatch("wchp", generateExpectedMessage("wchp"));
+        verifyExactMatch("wchs", generateExpectedMessage("wchs"));
+        verifyExactMatch("mntr", generateExpectedMessage("mntr"));
+        verifyExactMatch("isro", generateExpectedMessage("isro"));
+
+        // srvr is enabled by default due to the sad fact zkServer.sh uses it.
+        verifyFuzzyMatch("srvr", "Outstanding");
+    }
+
+    private String sendRequest(String cmd) throws IOException, SSLContextException {
+      HostPort hpobj = ClientBase.parseHostPortList(hostPort).get(0);
+      return send4LetterWord(hpobj.host, hpobj.port, cmd);
+    }
+
+    private void verifyFuzzyMatch(String cmd, String expected) throws IOException, SSLContextException {
+        String resp = sendRequest(cmd);
+        LOG.info("cmd " + cmd + " expected " + expected + " got " + resp);
+        Assert.assertTrue(resp.contains(expected));
+    }
+
+    private String generateExpectedMessage(String command) {
+        return command + " is not executed because it is not in the whitelist.";
+    }
+
+    private void verifyExactMatch(String cmd, String expected) throws IOException, SSLContextException {
+        String resp = sendRequest(cmd);
+        LOG.info("cmd " + cmd + " expected an exact match of " + expected + "; got " + resp);
+        Assert.assertTrue(resp.trim().equals(expected));
+    }
+}