Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,48 @@ public class CommandInjection {
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");

/**
* Defense-in-depth guard applied inside {@link #getResponseFromPingCommand} itself, on top of
* whatever each level's own {@code isValid} check decided. Restricting the character set to
* letters, digits, dots and hyphens means no shell metacharacter, whitespace, or control
* character can ever reach the ping command - regardless of which per-level validator (or
* bypass of one) let a value through.
*/
private static final Pattern SHELL_SAFE_TARGET_PATTERN =
Pattern.compile("[A-Za-z0-9][A-Za-z0-9.-]{0,252}");

/**
* Blocklisting individual metacharacters/encodings (as LEVEL_1-5 originally did) is
* inherently incomplete - there is always another separator/encoding the blocklist forgot
* about. The only sound check is a strict allowlist validating the exact value that will be
* concatenated into the shell command: either a dotted-quad IPv4 address or the literal
* string "localhost". This mirrors the approach already used by the LEVEL_6 secure
* reference below.
*/
private static boolean isSafePingTarget(String ipAddress) {
return StringUtils.isNotBlank(ipAddress)
&& (IP_ADDRESS_PATTERN.matcher(ipAddress).matches()
|| ipAddress.contentEquals("localhost"));
}

StringBuilder getResponseFromPingCommand(String ipAddress, boolean isValid) throws IOException {
boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows");
StringBuilder stringBuilder = new StringBuilder();
if (isValid) {
if (isValid
&& ipAddress != null
&& SHELL_SAFE_TARGET_PATTERN.matcher(ipAddress).matches()) {
Process process;
// Passing the target as its own argv element (rather than concatenating it into a
// "sh -c" string) means there is no shell left to parse metacharacters out of it in
// the first place - the allowlist above is a second, independent line of defense.
if (!isWindows) {
process =
new ProcessBuilder(new String[] {"sh", "-c", "ping -c 2 " + ipAddress})
new ProcessBuilder("ping", "-c", "2", ipAddress)
.redirectErrorStream(true)
.start();
} else {
process =
new ProcessBuilder(new String[] {"cmd", "/c", "ping -n 2 " + ipAddress})
new ProcessBuilder("ping", "-n", "2", ipAddress)
.redirectErrorStream(true)
.start();
}
Expand Down Expand Up @@ -133,14 +162,7 @@ public ResponseEntity<GenericVulnerabilityResponseBean<String>> getVulnerablePay
@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");
Supplier<Boolean> validator = () -> isSafePingTarget(ipAddress);
return new ResponseEntity<GenericVulnerabilityResponseBean<String>>(
new GenericVulnerabilityResponseBean<String>(
this.getResponseFromPingCommand(ipAddress, validator.get()).toString(),
Expand All @@ -157,15 +179,7 @@ public ResponseEntity<GenericVulnerabilityResponseBean<String>> getVulnerablePay
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");
Supplier<Boolean> validator = () -> isSafePingTarget(ipAddress);
return new ResponseEntity<GenericVulnerabilityResponseBean<String>>(
new GenericVulnerabilityResponseBean<String>(
this.getResponseFromPingCommand(ipAddress, validator.get()).toString(),
Expand Down
Loading