Skip to content

Java ProcessBuilder RCE rule misses fluent command() invocation without intermediate variable #3743

@h4r7w3l1

Description

@h4r7w3l1

Describe the bug

The Java rule command-injection-process-builder produces a false negative and misses a common RCE pattern.

The rule correctly detects ProcessBuilder(...) constructor usage and direct command(...) calls on a ProcessBuilder variable. However, it does not account for fluent (chained) invocations where command() is called as part of a method chain starting from new ProcessBuilder().

As a result, command injection is not detected when:

  • ProcessBuilder is instantiated inline without being assigned to a local variable
  • command() is invoked within a fluent chain
  • a shell is executed with login flags (-l -c), not just -c

To Reproduce

Undetected vulnerable example:

@Service
public class BashExecutorService {

    public String run(final String command) {
        try {
            Process process = new ProcessBuilder()
                    .redirectErrorStream(true)
                    .command("/bin/bash", "-l", "-c", command)
                    .start();
            return "ok";
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

No finding is reported by command-injection-process-builder.


Detected example (works as expected):

public class TestExecutor {

    public String test(String userInput) {
        ProcessBuilder builder = new ProcessBuilder();
        // ruleid: command-injection-process-builder
        builder.command("bash", "-c", userInput);
        return "foo";
    }
}

Expected behavior
The rule should flag the fluent invocation case, as it is semantically equivalent to already detected patterns.

Any invocation of a shell (bash, sh, cmd) with -c (including cases with additional flags such as -l) where the command argument is a variable should be reported, regardless of whether ProcessBuilder is stored in a local variable or used inline.

Priority
How important is this to you?

  • P0: blocking me from making progress
  • P1: this will block me in the near future
  • P2: annoying but not blocking me

Additional Context
The root cause appears to be rule constraints that rely on:

pattern-inside: |
  $TYPE $PB = new ProcessBuilder(...);
  ...

This pattern never matches in the following real-world scenario:

new ProcessBuilder()
    .redirectErrorStream(true)
    .command("/bin/bash", "-l", "-c", command)
    .start();

Key differences from already covered cases:

  1. command() is invoked as part of a fluent chain, not as a standalone statement.
  2. ProcessBuilder is not assigned to a local variable before command() is called.
  3. A login shell is used (-l -c), while the rule primarily matches only -c.

Because of (1) and (2), the pattern-inside constraint never triggers, leading to a false negative. This usage pattern is common in Spring services and command execution helpers, so missing it reduces the practical effectiveness of the rule for Java RCE detection.

If useful, I can provide a minimal rule patch or test cases that demonstrate this gap.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions