Skip to content
Open
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 @@ -2,190 +2,49 @@

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
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;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
* 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");
private static final Set<String> ALLOWED_COMMANDS = Set.of("date", "uptime", "whoami");

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 =
new ProcessBuilder(new String[] {"sh", "-c", "ping -c 2 " + ipAddress})
.redirectErrorStream(true)
.start();
} else {
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 stringBuilder;
}
public String runSafeCommand(String userCommand, List<String> userArgs)
throws IOException, InterruptedException {

@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);
return new ResponseEntity<GenericVulnerabilityResponseBean<String>>(
new GenericVulnerabilityResponseBean<String>(
this.getResponseFromPingCommand(ipAddress, validator.get()).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 {
if (!ALLOWED_COMMANDS.contains(userCommand)) {
throw new IllegalArgumentException("Unauthorized command");
}

Supplier<Boolean> validator =
() ->
StringUtils.isNotBlank(ipAddress)
&& !SEMICOLON_SPACE_LOGICAL_AND_PATTERN
.matcher(requestEntity.getUrl().toString())
.find();
return new ResponseEntity<GenericVulnerabilityResponseBean<String>>(
new GenericVulnerabilityResponseBean<String>(
this.getResponseFromPingCommand(ipAddress, validator.get()).toString(),
true),
HttpStatus.OK);
}
for (String arg : userArgs) {
if (arg == null || !arg.matches("[A-Za-z0-9._-]+")) {
throw new IllegalArgumentException("Invalid argument");
}
}

// 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 {
List<String> cmd = new ArrayList<>();
cmd.add(userCommand);
cmd.addAll(userArgs);

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);
}
ProcessBuilder pb = new ProcessBuilder(cmd);
pb.redirectErrorStream(true);
Process process = pb.start();

// 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 {
try (InputStream is = process.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {

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);
}
StringBuilder output = new StringBuilder();
String line;

@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"));
while ((line = br.readLine()) != null) {
output.append(line).append("\n");
}

return new ResponseEntity<GenericVulnerabilityResponseBean<String>>(
new GenericVulnerabilityResponseBean<String>(
this.getResponseFromPingCommand(ipAddress, validator.get()).toString(),
true),
HttpStatus.OK);
return output.toString();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.sasanlabs.service.vulnerability.jwt;

import static org.sasanlabs.service.vulnerability.jwt.bean.JWTUtils.GENERIC_BASE64_ENCODED_PAYLOAD;

import java.io.UnsupportedEncodingException;
import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
Expand Down Expand Up @@ -67,28 +67,20 @@ public JWTVulnerability(
this.jwtAlgorithmKMS = jwtAlgorithmKMS;
}

private ResponseEntity<GenericVulnerabilityResponseBean<String>> getJWTResponseBean(
boolean isValid,
String jwtToken,
boolean includeToken,
MultiValueMap<String, String> headers) {
GenericVulnerabilityResponseBean<String> genericVulnerabilityResponseBean;
if (includeToken) {
genericVulnerabilityResponseBean =
new GenericVulnerabilityResponseBean<String>(jwtToken, isValid);
} else {
genericVulnerabilityResponseBean =
new GenericVulnerabilityResponseBean<String>(null, isValid);
}
if (!isValid) {
ResponseEntity<GenericVulnerabilityResponseBean<String>> responseEntity =
new ResponseEntity<GenericVulnerabilityResponseBean<String>>(
genericVulnerabilityResponseBean, headers, HttpStatus.UNAUTHORIZED);
return responseEntity;
}
return new ResponseEntity<GenericVulnerabilityResponseBean<String>>(
genericVulnerabilityResponseBean, headers, HttpStatus.OK);
}
private ResponseEntity<GenericVulnerabilityResponseBean<String>> getJWTResponseBean(
boolean isValid,
String jwtToken,
boolean includeToken,
MultiValueMap<String, String> headers) {

GenericVulnerabilityResponseBean<String> genericVulnerabilityResponseBean =
includeToken
? new GenericVulnerabilityResponseBean<>(jwtToken, isValid)
: new GenericVulnerabilityResponseBean<>(null, isValid);

HttpStatus status = isValid ? HttpStatus.OK : HttpStatus.UNAUTHORIZED;
return new ResponseEntity<>(genericVulnerabilityResponseBean, headers, status);
}

@AttackVector(
vulnerabilityExposed = VulnerabilityType.CLIENT_SIDE_VULNERABLE_JWT,
Expand All @@ -99,11 +91,17 @@ private ResponseEntity<GenericVulnerabilityResponseBean<String>> getJWTResponseB
public ResponseEntity<GenericVulnerabilityResponseBean<String>>
getVulnerablePayloadLevelUnsecure(@RequestParam Map<String, String> queryParams)
throws UnsupportedEncodingException, ServiceApplicationException {
Optional<SymmetricAlgorithmKey> symmetricAlgorithmKey =
jwtAlgorithmKMS.getSymmetricAlgorithmKey(
JWTUtils.JWT_HMAC_SHA_256_ALGORITHM, KeyStrength.HIGH);
LOGGER.info(symmetricAlgorithmKey.isPresent() + " " + symmetricAlgorithmKey.get());
String token = queryParams.get(JWT);
Optional<SymmetricAlgorithmKey> symmetricAlgorithmKey =
jwtAlgorithmKMS.getSymmetricAlgorithmKey(
JWTUtils.JWT_HMAC_SHA_256_ALGORITHM, KeyStrength.HIGH);

// Ασφαλής επαλήθευση ότι υπάρχει το κλειδί
if (symmetricAlgorithmKey == null || symmetricAlgorithmKey.isEmpty()) {
LOGGER.warn("Symmetric algorithm key not available for LEVEL_1");
throw new ServiceApplicationException("Required symmetric key not available");
}
LOGGER.info("Symmetric algorithm key loaded for LEVEL_1");
String token = queryParams.get(JWT);
if (token != null) {
boolean isValid =
jwtValidator.customHMACValidator(
Expand Down
Loading