java.spring.security.injection.tainted-system-command.tainted-system-command
semgrep
Author
unknown
Download Count*
License
Detected user input entering a method which executes a system command. This could result in a command injection vulnerability, which allows an attacker to inject an arbitrary system command onto the server. The attacker could download malware onto or steal data from the server. Instead, use ProcessBuilder, separating the command into individual arguments, like this: new ProcessBuilder("ls", "-al", targetDirectory)
. Further, make sure you hardcode or allowlist the actual command so that attackers can't run arbitrary commands.
Run Locally
Run in CI
Defintion
rules:
- id: tainted-system-command
languages:
- java
severity: ERROR
mode: taint
pattern-propagators:
- pattern: (StringBuilder $STRB).append($INPUT)
from: $INPUT
to: $STRB
label: CONCAT
requires: INPUT
pattern-sources:
- patterns:
- pattern-either:
- pattern-inside: |
$METHODNAME(..., @$REQ(...) $TYPE $SOURCE,...) {
...
}
- pattern-inside: |
$METHODNAME(..., @$REQ $TYPE $SOURCE,...) {
...
}
- metavariable-regex:
metavariable: $TYPE
regex: ^(?!(Integer|Long|Float|Double|Char|Boolean|int|long|float|double|char|boolean))
- metavariable-regex:
metavariable: $REQ
regex: (RequestBody|PathVariable|RequestParam|RequestHeader|CookieValue|ModelAttribute)
- focus-metavariable: $SOURCE
label: INPUT
- patterns:
- pattern-either:
- pattern: $X + $SOURCE
- pattern: $SOURCE + $Y
- pattern: String.format("...", ..., $SOURCE, ...)
- pattern: String.join("...", ..., $SOURCE, ...)
- pattern: (String $STR).concat($SOURCE)
- pattern: $SOURCE.concat(...)
- pattern: $X += $SOURCE
- pattern: $SOURCE += $X
label: CONCAT
requires: INPUT
pattern-sinks:
- patterns:
- pattern-either:
- pattern: |
(Process $P) = new Process(...);
- pattern: |
(ProcessBuilder $PB).command(...);
- patterns:
- pattern-either:
- pattern: |
(Runtime $R).$EXEC(...);
- pattern: |
Runtime.getRuntime(...).$EXEC(...);
- metavariable-regex:
metavariable: $EXEC
regex: (exec|loadLibrary|load)
- patterns:
- pattern: |
(ProcessBuilder $PB).command(...).$ADD(...);
- metavariable-regex:
metavariable: $ADD
regex: (add|addAll)
- patterns:
- pattern-either:
- patterns:
- pattern-inside: |
$BUILDER = new ProcessBuilder(...);
...
- pattern: $BUILDER.start(...)
- pattern: |
new ProcessBuilder(...). ... .start(...);
requires: CONCAT
message: "Detected user input entering a method which executes a system command.
This could result in a command injection vulnerability, which allows an
attacker to inject an arbitrary system command onto the server. The
attacker could download malware onto or steal data from the server.
Instead, use ProcessBuilder, separating the command into individual
arguments, like this: `new ProcessBuilder(\"ls\", \"-al\",
targetDirectory)`. Further, make sure you hardcode or allowlist the actual
command so that attackers can't run arbitrary commands."
metadata:
cwe:
- "CWE-78: Improper Neutralization of Special Elements used in an OS
Command ('OS Command Injection')"
owasp:
- A01:2017 - Injection
- A03:2021 - Injection
category: security
technology:
- java
- spring
confidence: HIGH
references:
- https://www.stackhawk.com/blog/command-injection-java/
- https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html
- https://github.com/github/codeql/blob/main/java/ql/src/Security/CWE/CWE-078/ExecUnescaped.java
cwe2022-top25: true
cwe2021-top25: true
subcategory:
- vuln
likelihood: HIGH
impact: HIGH
license: Commons Clause License Condition v1.0[LGPL-2.1-only]
vulnerability_class:
- Command Injection
Examples
tainted-system-command.java
package org.sasanlabs.service.vulnerability.commandInjection;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.sasanlabs.internal.utility.LevelConstants;
import org.sasanlabs.internal.utility.Variant;
import org.sasanlabs.internal.utility.annotations.AttackVector;
import org.sasanlabs.internal.utility.annotations.VulnerableAppRequestMapping;
import org.sasanlabs.internal.utility.annotations.VulnerableAppRestController;
import org.sasanlabs.service.exception.ServiceApplicationException;
import org.sasanlabs.service.vulnerability.bean.GenericVulnerabilityResponseBean;
import org.sasanlabs.vulnerability.types.VulnerabilityType;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestParam;
/**
* This class contains vulnerabilities related to Command Injection. <a
* href="https://owasp.org/www-community/attacks/Command_Injection">For More information</a>
*
* @author KSASAN preetkaran20@gmail.com
*/
@VulnerableAppRestController(
descriptionLabel = "COMMAND_INJECTION_VULNERABILITY",
value = "CommandInjection")
public class CommandInjection {
private static final String IP_ADDRESS = "ipaddress";
private static final Pattern SEMICOLON_SPACE_LOGICAL_AND_PATTERN = Pattern.compile("[;& ]");
private static final Pattern IP_ADDRESS_PATTERN =
Pattern.compile("\\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.|$)){4}\\b");
StringBuilder getResponseFromPingCommand(String ipAddress, boolean isValid) throws IOException {
boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows");
StringBuilder stringBuilder = new StringBuilder();
if (isValid) {
Process process;
if (!isWindows) {
process =
// deepruleid: tainted-system-command
new ProcessBuilder(new String[] {"sh", "-c", "ping -c 2 " + ipAddress})
.redirectErrorStream(true)
.start();
} else {
process =
// deepruleid: tainted-system-command
new ProcessBuilder(new String[] {"cmd", "/c", "ping -n 2 " + ipAddress})
.redirectErrorStream(true)
.start();
}
try (BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(process.getInputStream()))) {
bufferedReader.lines().forEach(val -> stringBuilder.append(val).append("\n"));
}
}
return stringBuilder;
}
@AttackVector(
vulnerabilityExposed = VulnerabilityType.COMMAND_INJECTION,
description = "COMMAND_INJECTION_URL_PARAM_DIRECTLY_EXECUTED")
@VulnerableAppRequestMapping(value = LevelConstants.LEVEL_1, htmlTemplate = "LEVEL_1/CI_Level1")
public ResponseEntity<GenericVulnerabilityResponseBean<String>> getVulnerablePayloadLevel1(
@RequestParam(IP_ADDRESS) String ipAddress) throws IOException {
Supplier<Boolean> validator = () -> StringUtils.isNotBlank(ipAddress);
boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows");
StringBuilder stringBuilder = new StringBuilder();
if (isValid) {
Process process;
if (!isWindows) {
// ruleid: tainted-system-command
process =
new ProcessBuilder(new String[] {"sh", "-c", "ping -c 2 " + ipAddress})
.redirectErrorStream(true)
.start();
} else {
// ruleid: tainted-system-command
process =
new ProcessBuilder(new String[] {"cmd", "/c", "ping -n 2 " + ipAddress})
.redirectErrorStream(true)
.start();
}
try (BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(process.getInputStream()))) {
bufferedReader.lines().forEach(val -> stringBuilder.append(val).append("\n"));
}
}
return new ResponseEntity<GenericVulnerabilityResponseBean<String>>(
new GenericVulnerabilityResponseBean<String>(
stringBuilder.toString(),
true),
HttpStatus.OK);
}
@AttackVector(
vulnerabilityExposed = VulnerabilityType.COMMAND_INJECTION,
description =
"COMMAND_INJECTION_URL_PARAM_DIRECTLY_EXECUTED_IF_SEMICOLON_SPACE_LOGICAL_AND_NOT_PRESENT")
@VulnerableAppRequestMapping(value = LevelConstants.LEVEL_2, htmlTemplate = "LEVEL_1/CI_Level1")
public ResponseEntity<GenericVulnerabilityResponseBean<String>> getVulnerablePayloadLevel2(
@RequestParam(IP_ADDRESS) String ipAddress, RequestEntity<String> requestEntity)
throws ServiceApplicationException, IOException {
Supplier<Boolean> validator =
() ->
StringUtils.isNotBlank(ipAddress)
&& !SEMICOLON_SPACE_LOGICAL_AND_PATTERN
.matcher(requestEntity.getUrl().toString())
.find();
return new ResponseEntity<GenericVulnerabilityResponseBean<String>>(
new GenericVulnerabilityResponseBean<String>(
// todoruleid: tainted-system-command
// Indirection, needs interproc taint
this.getResponseFromPingCommand(ipAddress, validator.get()).toString(),
true),
HttpStatus.OK);
}
// Case Insensitive
@AttackVector(
vulnerabilityExposed = VulnerabilityType.COMMAND_INJECTION,
description =
"COMMAND_INJECTION_URL_PARAM_DIRECTLY_EXECUTED_IF_SEMICOLON_SPACE_LOGICAL_AND_%26_%3B_NOT_PRESENT")
@VulnerableAppRequestMapping(value = LevelConstants.LEVEL_3, htmlTemplate = "LEVEL_1/CI_Level1")
public ResponseEntity<GenericVulnerabilityResponseBean<String>> getVulnerablePayloadLevel3(
@RequestParam(IP_ADDRESS) String ipAddress, RequestEntity<String> requestEntity)
throws ServiceApplicationException, IOException {
Supplier<Boolean> validator =
() ->
StringUtils.isNotBlank(ipAddress)
&& !SEMICOLON_SPACE_LOGICAL_AND_PATTERN
.matcher(requestEntity.getUrl().toString())
.find()
&& !requestEntity.getUrl().toString().contains("%26")
&& !requestEntity.getUrl().toString().contains("%3B");
return new ResponseEntity<GenericVulnerabilityResponseBean<String>>(
new GenericVulnerabilityResponseBean<String>(
this.getResponseFromPingCommand(ipAddress, validator.get()).toString(),
true),
HttpStatus.OK);
}
// e.g Attack
// http://localhost:9090/vulnerable/CommandInjectionVulnerability/LEVEL_3?ipaddress=192.168.0.1%20%7c%20cat%20/etc/passwd
@AttackVector(
vulnerabilityExposed = VulnerabilityType.COMMAND_INJECTION,
description =
"COMMAND_INJECTION_URL_PARAM_DIRECTLY_EXECUTED_IF_SEMICOLON_SPACE_LOGICAL_AND_%26_%3B_CASE_INSENSITIVE_NOT_PRESENT")
@VulnerableAppRequestMapping(value = LevelConstants.LEVEL_4, htmlTemplate = "LEVEL_1/CI_Level1")
public ResponseEntity<GenericVulnerabilityResponseBean<String>> getVulnerablePayloadLevel4(
@RequestParam(IP_ADDRESS) String ipAddress, RequestEntity<String> requestEntity)
throws ServiceApplicationException, IOException {
Supplier<Boolean> validator =
() ->
StringUtils.isNotBlank(ipAddress)
&& !SEMICOLON_SPACE_LOGICAL_AND_PATTERN
.matcher(requestEntity.getUrl().toString())
.find()
&& !requestEntity.getUrl().toString().toUpperCase().contains("%26")
&& !requestEntity.getUrl().toString().toUpperCase().contains("%3B");
return new ResponseEntity<GenericVulnerabilityResponseBean<String>>(
new GenericVulnerabilityResponseBean<String>(
this.getResponseFromPingCommand(ipAddress, validator.get()).toString(),
true),
HttpStatus.OK);
}
// Payload: 127.0.0.1%0Als
@AttackVector(
vulnerabilityExposed = VulnerabilityType.COMMAND_INJECTION,
description =
"COMMAND_INJECTION_URL_PARAM_DIRECTLY_EXECUTED_IF_SEMICOLON_SPACE_LOGICAL_AND_%26_%3B_%7C_CASE_INSENSITIVE_NOT_PRESENT")
@VulnerableAppRequestMapping(value = LevelConstants.LEVEL_5, htmlTemplate = "LEVEL_1/CI_Level1")
public ResponseEntity<GenericVulnerabilityResponseBean<String>> getVulnerablePayloadLevel5(
@RequestParam(IP_ADDRESS) String ipAddress, RequestEntity<String> requestEntity)
throws IOException {
Supplier<Boolean> validator =
() ->
StringUtils.isNotBlank(ipAddress)
&& !SEMICOLON_SPACE_LOGICAL_AND_PATTERN
.matcher(requestEntity.getUrl().toString())
.find()
&& !requestEntity.getUrl().toString().toUpperCase().contains("%26")
&& !requestEntity.getUrl().toString().toUpperCase().contains("%3B")
& !requestEntity
.getUrl()
.toString()
.toUpperCase()
.contains("%7C");
return new ResponseEntity<GenericVulnerabilityResponseBean<String>>(
new GenericVulnerabilityResponseBean<String>(
this.getResponseFromPingCommand(ipAddress, validator.get()).toString(),
true),
HttpStatus.OK);
}
@VulnerableAppRequestMapping(
value = LevelConstants.LEVEL_6,
htmlTemplate = "LEVEL_1/CI_Level1",
variant = Variant.SECURE)
public ResponseEntity<GenericVulnerabilityResponseBean<String>> getVulnerablePayloadLevel6(
@RequestParam(IP_ADDRESS) String ipAddress) throws IOException {
Supplier<Boolean> validator =
() ->
StringUtils.isNotBlank(ipAddress)
&& (IP_ADDRESS_PATTERN.matcher(ipAddress).matches()
|| ipAddress.contentEquals("localhost"));
return new ResponseEntity<GenericVulnerabilityResponseBean<String>>(
new GenericVulnerabilityResponseBean<String>(
this.getResponseFromPingCommand(ipAddress, validator.get()).toString(),
true),
HttpStatus.OK);
}
public static void test1(@RequestParam(IP_ADDRESS) String ipAddress) {
String args = "ping -c 2 " + ipAddress + "test";
Process process;
process = new ProcessBuilder(new String[] {"sh", "-c", args});
// ruleid: tainted-system-command
process.start();
}
public static void test2(@RequestParam String input) {
String latlonCoords = input;
Runtime rt = Runtime.getRuntime();
// ok: tainted-system-command
Process exec = rt.exec(new String[] {
"c:\\path\to\latlon2utm.exe",
latlonCoords }); // safe bc args are seperated
}
public static void test3(@RequestParam String input) {
StringBuilder stringBuilder = new StringBuilder(100);
stringBuilder.append(input);
stringBuilder.append("test2");
Runtime rt = Runtime.getRuntime();
// ruleid: tainted-system-command
Process exec = rt.exec(stringBuilder);
}
public static void test4(@RequestParam String input) {
String test1 = "test";
String comb = test1.concat(input);
Runtime rt = Runtime.getRuntime();
// ruleid: tainted-system-command
Process exec = rt.exec(comb);
}
public static void test5(@RequestParam String input) {
String test1 = "test";
String comb = String.format("%s%s", test1, input);
Runtime rt = Runtime.getRuntime();
// ruleid: tainted-system-command
Process exec = rt.exec(comb);
}
public static String run(@RequestParam(defaultValue = "I love Linux!") String input) {
ProcessBuilder processBuilder = new ProcessBuilder();
String cmd = "/usr/games/cowsay '" + input + "'";
System.out.println(cmd);
// ruleid: tainted-system-command
processBuilder.command("bash", "-c", cmd);
StringBuilder output = new StringBuilder();
try {
Process process = processBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
output.append(line + "\n");
}
} catch (Exception e) {
e.printStackTrace();
}
return output.toString();
}
}
Short Link: https://sg.run/epY0