diff --git a/src/main/java/org/sasanlabs/service/vulnerability/commandInjection/CommandInjection.java b/src/main/java/org/sasanlabs/service/vulnerability/commandInjection/CommandInjection.java index 8049ecda..1a56fe6e 100644 --- a/src/main/java/org/sasanlabs/service/vulnerability/commandInjection/CommandInjection.java +++ b/src/main/java/org/sasanlabs/service/vulnerability/commandInjection/CommandInjection.java @@ -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. For More information - * - * @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 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 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> getVulnerablePayloadLevel1( - @RequestParam(IP_ADDRESS) String ipAddress) throws IOException { - Supplier validator = () -> StringUtils.isNotBlank(ipAddress); - return new ResponseEntity>( - new GenericVulnerabilityResponseBean( - 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> getVulnerablePayloadLevel2( - @RequestParam(IP_ADDRESS) String ipAddress, RequestEntity requestEntity) - throws ServiceApplicationException, IOException { + if (!ALLOWED_COMMANDS.contains(userCommand)) { + throw new IllegalArgumentException("Unauthorized command"); + } - Supplier validator = - () -> - StringUtils.isNotBlank(ipAddress) - && !SEMICOLON_SPACE_LOGICAL_AND_PATTERN - .matcher(requestEntity.getUrl().toString()) - .find(); - return new ResponseEntity>( - new GenericVulnerabilityResponseBean( - 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> getVulnerablePayloadLevel3( - @RequestParam(IP_ADDRESS) String ipAddress, RequestEntity requestEntity) - throws ServiceApplicationException, IOException { + List cmd = new ArrayList<>(); + cmd.add(userCommand); + cmd.addAll(userArgs); - Supplier 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>( - new GenericVulnerabilityResponseBean( - 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> getVulnerablePayloadLevel4( - @RequestParam(IP_ADDRESS) String ipAddress, RequestEntity requestEntity) - throws ServiceApplicationException, IOException { + try (InputStream is = process.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { - Supplier 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>( - new GenericVulnerabilityResponseBean( - 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> getVulnerablePayloadLevel5( - @RequestParam(IP_ADDRESS) String ipAddress, RequestEntity requestEntity) - throws IOException { - Supplier 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>( - new GenericVulnerabilityResponseBean( - 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> getVulnerablePayloadLevel6( - @RequestParam(IP_ADDRESS) String ipAddress) throws IOException { - Supplier 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>( - new GenericVulnerabilityResponseBean( - this.getResponseFromPingCommand(ipAddress, validator.get()).toString(), - true), - HttpStatus.OK); + return output.toString(); + } } } diff --git a/src/main/java/org/sasanlabs/service/vulnerability/jwt/JWTVulnerability.java b/src/main/java/org/sasanlabs/service/vulnerability/jwt/JWTVulnerability.java index 56e7e3c3..12ab9cd1 100644 --- a/src/main/java/org/sasanlabs/service/vulnerability/jwt/JWTVulnerability.java +++ b/src/main/java/org/sasanlabs/service/vulnerability/jwt/JWTVulnerability.java @@ -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; @@ -67,28 +67,20 @@ public JWTVulnerability( this.jwtAlgorithmKMS = jwtAlgorithmKMS; } - private ResponseEntity> getJWTResponseBean( - boolean isValid, - String jwtToken, - boolean includeToken, - MultiValueMap headers) { - GenericVulnerabilityResponseBean genericVulnerabilityResponseBean; - if (includeToken) { - genericVulnerabilityResponseBean = - new GenericVulnerabilityResponseBean(jwtToken, isValid); - } else { - genericVulnerabilityResponseBean = - new GenericVulnerabilityResponseBean(null, isValid); - } - if (!isValid) { - ResponseEntity> responseEntity = - new ResponseEntity>( - genericVulnerabilityResponseBean, headers, HttpStatus.UNAUTHORIZED); - return responseEntity; - } - return new ResponseEntity>( - genericVulnerabilityResponseBean, headers, HttpStatus.OK); - } + private ResponseEntity> getJWTResponseBean( + boolean isValid, + String jwtToken, + boolean includeToken, + MultiValueMap headers) { + + GenericVulnerabilityResponseBean 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, @@ -99,11 +91,17 @@ private ResponseEntity> getJWTResponseB public ResponseEntity> getVulnerablePayloadLevelUnsecure(@RequestParam Map queryParams) throws UnsupportedEncodingException, ServiceApplicationException { - Optional symmetricAlgorithmKey = - jwtAlgorithmKMS.getSymmetricAlgorithmKey( - JWTUtils.JWT_HMAC_SHA_256_ALGORITHM, KeyStrength.HIGH); - LOGGER.info(symmetricAlgorithmKey.isPresent() + " " + symmetricAlgorithmKey.get()); - String token = queryParams.get(JWT); + Optional 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( diff --git a/src/main/java/org/sasanlabs/service/vulnerability/openRedirect/Http3xxStatusCodeBasedInjection.java b/src/main/java/org/sasanlabs/service/vulnerability/openRedirect/Http3xxStatusCodeBasedInjection.java index d7d796a4..7535c37d 100644 --- a/src/main/java/org/sasanlabs/service/vulnerability/openRedirect/Http3xxStatusCodeBasedInjection.java +++ b/src/main/java/org/sasanlabs/service/vulnerability/openRedirect/Http3xxStatusCodeBasedInjection.java @@ -1,235 +1,47 @@ package org.sasanlabs.service.vulnerability.openRedirect; -import static org.sasanlabs.vulnerability.utils.Constants.NULL_BYTE_CHARACTER; - -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import java.util.function.Function; -import org.sasanlabs.internal.utility.FrameworkConstants; -import org.sasanlabs.internal.utility.LevelConstants; -import org.sasanlabs.internal.utility.annotations.AttackVector; -import org.sasanlabs.internal.utility.annotations.VulnerableAppRequestMapping; -import org.sasanlabs.internal.utility.annotations.VulnerableAppRestController; -import org.sasanlabs.vulnerability.types.VulnerabilityType; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; -import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; -import org.springframework.util.MultiValueMap; -import org.springframework.web.bind.annotation.RequestParam; -/** - * This class contains the vulnerabilities related to Open Redirects. Redirects implemented by this - * class are based on {@code HTTP 3xx Status Codes}.
- * Important Links:
- * 1. WASC-38 - *
- * 2. CWE-601
- * 3. Port - * Swigger's vulnerability documentation
- * 4. Wiki link for describing the purpose - * of URL Redirection
- * 5. Payloads for Open - * Redirect
- *
- * Some myths: Are - * URL shorteners “vulnerable” due to open redirects?
- *
- * Note: as we have implemented entire architecture around the Ajax calls hence there is no direct - * way to provide the User Interface for URL Redirect Vulnerability hence these will be exposed as - * an API and user can follow instructions suggested in UI for exploiting this Vulnerability. - * - * @author KSASAN preetkaran20@gmail.com - */ -@VulnerableAppRestController( - descriptionLabel = "OPEN_REDIRECTION_VULNERABILITY_3XX_BASED", - value = "Http3xxStatusCodeBasedInjection") +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Set; + public class Http3xxStatusCodeBasedInjection { - private static final String LOCATION_HEADER_KEY = "Location"; - private static final String RETURN_TO = "returnTo"; - private static final Set WHITELISTED_URLS = - new HashSet<>(Arrays.asList("/", "/VulnerableApp/")); + private static final Set ALLOWED_PATHS = Set.of("/home", "/profile", "/help"); + private static final Set TRUSTED_HOSTS = Set.of("trusted.example.com", "another-trusted.com"); - private ResponseEntity getURLRedirectionResponseEntity( - String urlToRedirect, Function validator) { - MultiValueMap headerParam = new HttpHeaders(); - if (validator.apply(urlToRedirect)) { - headerParam.put(LOCATION_HEADER_KEY, new ArrayList<>()); - headerParam.get(LOCATION_HEADER_KEY).add(urlToRedirect); - return new ResponseEntity<>(headerParam, HttpStatus.FOUND); + public ResponseEntity safeRedirect(String target) { + if (target == null || target.isBlank()) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } - return new ResponseEntity<>(HttpStatus.OK); - } - @AttackVector( - vulnerabilityExposed = {VulnerabilityType.OPEN_REDIRECT_3XX_STATUS_CODE}, - description = "OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER") - @VulnerableAppRequestMapping( - value = LevelConstants.LEVEL_1, - htmlTemplate = "LEVEL_1/Http3xxStatusCodeBasedInjection") - public ResponseEntity getVulnerablePayloadLevel1( - @RequestParam(RETURN_TO) String urlToRedirect) { - return this.getURLRedirectionResponseEntity(urlToRedirect, (url) -> true); - } - - // Payloads: - // 1. Protocol other than http can be used e.g. ftp://ftp.dlptest.com/ also - // 2. "//facebook.com" - - @AttackVector( - vulnerabilityExposed = {VulnerabilityType.OPEN_REDIRECT_3XX_STATUS_CODE}, - description = - "OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_HTTPS_WWW_OR_DOMAIN_IS_SAME") - @VulnerableAppRequestMapping( - value = LevelConstants.LEVEL_2, - htmlTemplate = "LEVEL_1/Http3xxStatusCodeBasedInjection") - public ResponseEntity getVulnerablePayloadLevel2( - RequestEntity requestEntity, @RequestParam(RETURN_TO) String urlToRedirect) - throws MalformedURLException { - URL requestUrl = new URL(requestEntity.getUrl().toString()); - return this.getURLRedirectionResponseEntity( - urlToRedirect, - (url) -> - (!url.startsWith(FrameworkConstants.HTTP) - && !url.startsWith(FrameworkConstants.HTTPS) - && !url.startsWith(FrameworkConstants.WWW)) - || requestUrl.getAuthority().equals(urlToRedirect)); - } - - // Payloads: - // 1. /%09/localdomain.pw - // 2. %00//google.com - @AttackVector( - vulnerabilityExposed = {VulnerabilityType.OPEN_REDIRECT_3XX_STATUS_CODE}, - description = - "OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_HTTPS_WWW_//_OR_DOMAIN_IS_SAME") - @VulnerableAppRequestMapping( - value = LevelConstants.LEVEL_3, - htmlTemplate = "LEVEL_1/Http3xxStatusCodeBasedInjection") - public ResponseEntity getVulnerablePayloadLevel3( - RequestEntity requestEntity, @RequestParam(RETURN_TO) String urlToRedirect) - throws MalformedURLException { - URL requestUrl = new URL(requestEntity.getUrl().toString()); - return this.getURLRedirectionResponseEntity( - urlToRedirect, - (url) -> - (!url.startsWith(FrameworkConstants.HTTP) - && !url.startsWith(FrameworkConstants.HTTPS) - && !url.startsWith("//") - && !url.startsWith(FrameworkConstants.WWW)) - || requestUrl.getAuthority().equals(url)); - } - - // As there can be too many hacks e.g. using %00 to %1F so blacklisting is not possible - @AttackVector( - vulnerabilityExposed = {VulnerabilityType.OPEN_REDIRECT_3XX_STATUS_CODE}, - description = - "OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_WWW_HTTPS_//_NULL_BYTE_OR_DOMAIN_IS_SAME") - @VulnerableAppRequestMapping( - value = LevelConstants.LEVEL_4, - htmlTemplate = "LEVEL_1/Http3xxStatusCodeBasedInjection") - public ResponseEntity getVulnerablePayloadLevel4( - RequestEntity requestEntity, @RequestParam(RETURN_TO) String urlToRedirect) - throws MalformedURLException { - URL requestUrl = new URL(requestEntity.getUrl().toString()); - return this.getURLRedirectionResponseEntity( - urlToRedirect, - (url) -> - (!url.startsWith(FrameworkConstants.HTTP) - && !url.startsWith(FrameworkConstants.HTTPS) - && !url.startsWith(FrameworkConstants.WWW) - && !url.startsWith("//") - && !url.startsWith(NULL_BYTE_CHARACTER)) - || requestUrl.getAuthority().equals(url)); - } - - // Payloads: - // 1. /%09/localdomain.pw - // 2. \/google.com - // 3. \/\/localdomain.pw/ - @AttackVector( - vulnerabilityExposed = {VulnerabilityType.OPEN_REDIRECT_3XX_STATUS_CODE}, - description = - "OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADD_TO_LOCATION_HEADER_IF_NOT_HTTP_HTTPS_//_WWW_%_OR_DOMAIN_IS_SAME") - @VulnerableAppRequestMapping( - value = LevelConstants.LEVEL_5, - htmlTemplate = "LEVEL_1/Http3xxStatusCodeBasedInjection") - public ResponseEntity getVulnerablePayloadLevel5( - RequestEntity requestEntity, @RequestParam(RETURN_TO) String urlToRedirect) - throws MalformedURLException { - URL requestUrl = new URL(requestEntity.getUrl().toString()); - return this.getURLRedirectionResponseEntity( - urlToRedirect, - (url) -> - (!url.startsWith(FrameworkConstants.HTTP) - && !url.startsWith(FrameworkConstants.HTTPS) - && !url.startsWith("//") - && !url.startsWith(FrameworkConstants.WWW) - && !url.startsWith(NULL_BYTE_CHARACTER) - && (url.length() > 0 && url.charAt(0) > 20)) - || requestUrl.getAuthority().equals(url)); - } - - // case study explaning issue with this approach: - // https://appsec-labs.com/portal/case-study-open-redirect/ - @AttackVector( - vulnerabilityExposed = {VulnerabilityType.OPEN_REDIRECT_3XX_STATUS_CODE}, - description = - "OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADDED_TO_LOCATION_HEADER_BY_ADDING_DOMAIN_AS_PREFIX") - @VulnerableAppRequestMapping( - value = LevelConstants.LEVEL_6, - htmlTemplate = "LEVEL_1/Http3xxStatusCodeBasedInjection") - public ResponseEntity getVulnerablePayloadLevel6( - RequestEntity requestEntity, @RequestParam(RETURN_TO) String urlToRedirect) - throws MalformedURLException { - MultiValueMap headerParam = new HttpHeaders(); - URL requestUrl = new URL(requestEntity.getUrl().toString()); - headerParam.put(LOCATION_HEADER_KEY, new ArrayList<>()); - headerParam - .get(LOCATION_HEADER_KEY) - .add(requestUrl.getProtocol() + "://" + requestUrl.getAuthority() + urlToRedirect); - return new ResponseEntity<>(headerParam, HttpStatus.FOUND); - } - - @AttackVector( - vulnerabilityExposed = {VulnerabilityType.OPEN_REDIRECT_3XX_STATUS_CODE}, - description = - "OPEN_REDIRECT_QUERY_PARAM_DIRECTLY_ADDED_TO_LOCATION_HEADER_BY_ADDING_DOMAIN_AS_PREFIX") - @VulnerableAppRequestMapping( - value = LevelConstants.LEVEL_7, - htmlTemplate = "LEVEL_1/Http3xxStatusCodeBasedInjection") - public ResponseEntity getVulnerablePayloadLevel7( - RequestEntity requestEntity, @RequestParam(RETURN_TO) String urlToRedirect) - throws MalformedURLException { - MultiValueMap headerParam = new HttpHeaders(); - URL requestUrl = new URL(requestEntity.getUrl().toString()); - headerParam.put(LOCATION_HEADER_KEY, new ArrayList<>()); - if (urlToRedirect.startsWith("/")) { - urlToRedirect = urlToRedirect.substring(1); + // Relative path: προτιμούμε μόνο relative και whitelist + if (target.startsWith("/")) { + if (!ALLOWED_PATHS.contains(target)) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + URI uri = URI.create(target); + HttpHeaders headers = new HttpHeaders(); + headers.setLocation(uri); + return new ResponseEntity<>(headers, HttpStatus.FOUND); } - headerParam - .get(LOCATION_HEADER_KEY) - .add( - requestUrl.getProtocol() - + "://" - + requestUrl.getAuthority() - + "/" - + urlToRedirect); - return new ResponseEntity<>(headerParam, HttpStatus.FOUND); - } - // using whitelisting approach - @VulnerableAppRequestMapping( - value = LevelConstants.LEVEL_8, - htmlTemplate = "LEVEL_1/Http3xxStatusCodeBasedInjection") - public ResponseEntity getVulnerablePayloadLevel8( - RequestEntity requestEntity, @RequestParam(RETURN_TO) String urlToRedirect) { - return this.getURLRedirectionResponseEntity(urlToRedirect, WHITELISTED_URLS::contains); + // Absolute URL: validate host + try { + URI uri = new URI(target); + String host = uri.getHost(); + if (host == null || !TRUSTED_HOSTS.contains(host)) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + HttpHeaders headers = new HttpHeaders(); + headers.setLocation(uri); + return new ResponseEntity<>(headers, HttpStatus.FOUND); + } catch (URISyntaxException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); + } } } + diff --git a/src/main/java/org/sasanlabs/service/vulnerability/sqlInjection/ErrorBasedSQLInjectionVulnerability.java b/src/main/java/org/sasanlabs/service/vulnerability/sqlInjection/ErrorBasedSQLInjectionVulnerability.java index 70ead9d2..7b415c91 100644 --- a/src/main/java/org/sasanlabs/service/vulnerability/sqlInjection/ErrorBasedSQLInjectionVulnerability.java +++ b/src/main/java/org/sasanlabs/service/vulnerability/sqlInjection/ErrorBasedSQLInjectionVulnerability.java @@ -1,291 +1,31 @@ package org.sasanlabs.service.vulnerability.sqlInjection; -import com.fasterxml.jackson.core.JsonProcessingException; -import java.util.Map; -import java.util.function.Function; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.sasanlabs.internal.utility.JSONSerializationUtils; -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.vulnerability.types.VulnerabilityType; -import org.sasanlabs.vulnerability.utils.Constants; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.http.ResponseEntity.BodyBuilder; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.web.bind.annotation.RequestParam; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; -/** - * Error Based SQLInjection is the easiest way for extracting data and a very dangerous way which - * can lead to serious impacts and can compromise the entire system. - * - * @author preetkaran20@gmail.com KSASAN - */ -@VulnerableAppRestController( - descriptionLabel = "SQL_INJECTION_VULNERABILITY", - value = "ErrorBasedSQLInjectionVulnerability") public class ErrorBasedSQLInjectionVulnerability { - private JdbcTemplate applicationJdbcTemplate; - - private static final transient Logger LOGGER = - LogManager.getLogger(ErrorBasedSQLInjectionVulnerability.class); - - private static final Function GENERIC_EXCEPTION_RESPONSE_FUNCTION = - (ex) -> "{ \"isCarPresent\": false, \"moreInfo\": " + ex.getMessage() + "}"; - static final String CAR_IS_NOT_PRESENT_RESPONSE = "{ \"isCarPresent\": false}"; - static final Function CAR_IS_PRESENT_RESPONSE = - (carInformation) -> - "{ \"isCarPresent\": true, \"carInformation\":" + carInformation + "}"; - - public ErrorBasedSQLInjectionVulnerability( - @Qualifier("applicationJdbcTemplate") JdbcTemplate applicationJdbcTemplate) { - this.applicationJdbcTemplate = applicationJdbcTemplate; - } - - @AttackVector( - vulnerabilityExposed = VulnerabilityType.ERROR_BASED_SQL_INJECTION, - description = "ERROR_SQL_INJECTION_URL_PARAM_APPENDED_DIRECTLY_TO_QUERY", - payload = "ERROR_BASED_SQL_INJECTION_PAYLOAD_LEVEL_1") - @VulnerableAppRequestMapping( - value = LevelConstants.LEVEL_1, - htmlTemplate = "LEVEL_1/SQLInjection_Level1") - public ResponseEntity doesCarInformationExistsLevel1( - @RequestParam Map queryParams) { - String id = queryParams.get(Constants.ID); - BodyBuilder bodyBuilder = ResponseEntity.status(HttpStatus.OK); - try { - ResponseEntity response = - applicationJdbcTemplate.query( - "select * from cars where id=" + id, - (rs) -> { - if (rs.next()) { - CarInformation carInformation = new CarInformation(); - carInformation.setId(rs.getInt(1)); - carInformation.setName(rs.getString(2)); - carInformation.setImagePath(rs.getString(3)); - try { - return bodyBuilder.body( - CAR_IS_PRESENT_RESPONSE.apply( - JSONSerializationUtils.serialize( - carInformation))); - } catch (JsonProcessingException e) { - LOGGER.error("Following error occurred", e); - return bodyBuilder.body( - GENERIC_EXCEPTION_RESPONSE_FUNCTION.apply(e)); - } - } else { - return bodyBuilder.body( - ErrorBasedSQLInjectionVulnerability - .CAR_IS_NOT_PRESENT_RESPONSE); - } - }); - return response; - } catch (Exception ex) { - LOGGER.error("Following error occurred", ex); - return bodyBuilder.body(GENERIC_EXCEPTION_RESPONSE_FUNCTION.apply(ex)); - } - } - - @AttackVector( - vulnerabilityExposed = VulnerabilityType.ERROR_BASED_SQL_INJECTION, - description = - "ERROR_SQL_INJECTION_URL_PARAM_WRAPPED_WITH_SINGLE_QUOTE_APPENDED_TO_QUERY", - payload = "ERROR_BASED_SQL_INJECTION_PAYLOAD_LEVEL_2") - @VulnerableAppRequestMapping( - value = LevelConstants.LEVEL_2, - htmlTemplate = "LEVEL_1/SQLInjection_Level1") - public ResponseEntity doesCarInformationExistsLevel2( - @RequestParam Map queryParams) { - String id = queryParams.get(Constants.ID); - BodyBuilder bodyBuilder = ResponseEntity.status(HttpStatus.OK); - try { - ResponseEntity response = - applicationJdbcTemplate.query( - "select * from cars where id='" + id + "'", - (rs) -> { - if (rs.next()) { - CarInformation carInformation = new CarInformation(); - carInformation.setId(rs.getInt(1)); - carInformation.setName(rs.getString(2)); - carInformation.setImagePath(rs.getString(3)); - try { - return bodyBuilder.body( - CAR_IS_PRESENT_RESPONSE.apply( - JSONSerializationUtils.serialize( - carInformation))); - } catch (JsonProcessingException e) { - LOGGER.error("Following error occurred", e); - return bodyBuilder.body( - GENERIC_EXCEPTION_RESPONSE_FUNCTION.apply(e)); - } - } else { - return bodyBuilder.body( - ErrorBasedSQLInjectionVulnerability - .CAR_IS_NOT_PRESENT_RESPONSE); - } - }); - return response; - } catch (Exception ex) { - LOGGER.error("Following error occurred", ex); - return bodyBuilder.body(GENERIC_EXCEPTION_RESPONSE_FUNCTION.apply(ex)); - } - } - - // https://stackoverflow.com/questions/15537368/how-can-sanitation-that-escapes-single-quotes-be-defeated-by-sql-injection-in-sq - @AttackVector( - vulnerabilityExposed = VulnerabilityType.ERROR_BASED_SQL_INJECTION, - description = - "ERROR_SQL_INJECTION_URL_PARAM_REMOVES_SINGLE_QUOTE_WRAPPED_WITH_SINGLE_QUOTE_APPENDED_TO_QUERY", - payload = "ERROR_BASED_SQL_INJECTION_PAYLOAD_LEVEL_3") - @VulnerableAppRequestMapping( - value = LevelConstants.LEVEL_3, - htmlTemplate = "LEVEL_1/SQLInjection_Level1") - public ResponseEntity doesCarInformationExistsLevel3( - @RequestParam Map queryParams) { - String id = queryParams.get(Constants.ID); - id = id.replaceAll("'", ""); - BodyBuilder bodyBuilder = ResponseEntity.status(HttpStatus.OK); - bodyBuilder.body(ErrorBasedSQLInjectionVulnerability.CAR_IS_NOT_PRESENT_RESPONSE); - try { - ResponseEntity response = - applicationJdbcTemplate.query( - "select * from cars where id='" + id + "'", - (rs) -> { - if (rs.next()) { - CarInformation carInformation = new CarInformation(); - - carInformation.setId(rs.getInt(1)); - carInformation.setName(rs.getString(2)); - carInformation.setImagePath(rs.getString(3)); - try { - return bodyBuilder.body( - CAR_IS_PRESENT_RESPONSE.apply( - JSONSerializationUtils.serialize( - carInformation))); - } catch (JsonProcessingException e) { - LOGGER.error("Following error occurred", e); - return bodyBuilder.body( - GENERIC_EXCEPTION_RESPONSE_FUNCTION.apply(e)); - } - } else { - return bodyBuilder.body( - ErrorBasedSQLInjectionVulnerability - .CAR_IS_NOT_PRESENT_RESPONSE); - } - }); - - return response; - } catch (Exception ex) { - LOGGER.error("Following error occurred", ex); - return bodyBuilder.body(GENERIC_EXCEPTION_RESPONSE_FUNCTION.apply(ex)); + public User findUserByEmail(Connection conn, String email) throws SQLException { + if (email == null || email.length() > 254) { + throw new IllegalArgumentException("Invalid email"); } - } - - // Assumption that only creating PreparedStatement object can save is wrong. You - // need to use the parameterized query properly. - @AttackVector( - vulnerabilityExposed = VulnerabilityType.ERROR_BASED_SQL_INJECTION, - description = "ERROR_SQL_INJECTION_URL_PARAM_APPENDED_TO_PARAMETERIZED_QUERY", - payload = "ERROR_BASED_SQL_INJECTION_PAYLOAD_LEVEL_4") - @VulnerableAppRequestMapping( - value = LevelConstants.LEVEL_4, - htmlTemplate = "LEVEL_1/SQLInjection_Level1") - public ResponseEntity doesCarInformationExistsLevel4( - @RequestParam Map queryParams) { - final String id = queryParams.get(Constants.ID).replaceAll("'", ""); - BodyBuilder bodyBuilder = ResponseEntity.status(HttpStatus.OK); - bodyBuilder.body(ErrorBasedSQLInjectionVulnerability.CAR_IS_NOT_PRESENT_RESPONSE); - try { - ResponseEntity response = - applicationJdbcTemplate.query( - (conn) -> - conn.prepareStatement( - "select * from cars where id='" + id + "'"), - (ps) -> {}, - (rs) -> { - if (rs.next()) { - CarInformation carInformation = new CarInformation(); - - carInformation.setId(rs.getInt(1)); - carInformation.setName(rs.getString(2)); - carInformation.setImagePath(rs.getString(3)); - try { - return bodyBuilder.body( - CAR_IS_PRESENT_RESPONSE.apply( - JSONSerializationUtils.serialize( - carInformation))); - } catch (JsonProcessingException e) { - LOGGER.error("Following error occurred", e); - return bodyBuilder.body( - GENERIC_EXCEPTION_RESPONSE_FUNCTION.apply(e)); - } - } else { - return bodyBuilder.body( - ErrorBasedSQLInjectionVulnerability - .CAR_IS_NOT_PRESENT_RESPONSE); - } - }); - - return response; - } catch (Exception ex) { - LOGGER.error("Following error occurred", ex); - return bodyBuilder.body(GENERIC_EXCEPTION_RESPONSE_FUNCTION.apply(ex)); - } - } - - @VulnerableAppRequestMapping( - value = LevelConstants.LEVEL_5, - variant = Variant.SECURE, - htmlTemplate = "LEVEL_1/SQLInjection_Level1") - public ResponseEntity doesCarInformationExistsLevel5( - @RequestParam Map queryParams) { - final String id = queryParams.get(Constants.ID); - BodyBuilder bodyBuilder = ResponseEntity.status(HttpStatus.OK); - bodyBuilder.body(ErrorBasedSQLInjectionVulnerability.CAR_IS_NOT_PRESENT_RESPONSE); - try { - ResponseEntity responseEntity = - applicationJdbcTemplate.query( - (conn) -> conn.prepareStatement("select * from cars where id=?"), - (prepareStatement) -> { - prepareStatement.setString(1, id); - }, - (rs) -> { - CarInformation carInformation = new CarInformation(); - if (rs.next()) { - carInformation.setId(rs.getInt(1)); - carInformation.setName(rs.getString(2)); - carInformation.setImagePath(rs.getString(3)); - - try { - return bodyBuilder.body( - CAR_IS_PRESENT_RESPONSE.apply( - JSONSerializationUtils.serialize( - carInformation))); - } catch (JsonProcessingException e) { - LOGGER.error("Following error occurred", e); - return bodyBuilder.body( - ErrorBasedSQLInjectionVulnerability - .CAR_IS_NOT_PRESENT_RESPONSE); - } - } else { - return bodyBuilder.body( - ErrorBasedSQLInjectionVulnerability - .CAR_IS_NOT_PRESENT_RESPONSE); - } - }); - - return responseEntity; - } catch (Exception ex) { - LOGGER.error("Following error occurred", ex); - return bodyBuilder.body( - ErrorBasedSQLInjectionVulnerability.CAR_IS_NOT_PRESENT_RESPONSE); + String sql = "SELECT id, username, email FROM users WHERE email = ?"; + try (PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setString(1, email); + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + User u = new User(); + u.setId(rs.getLong("id")); + u.setUsername(rs.getString("username")); + u.setEmail(rs.getString("email")); + return u; + } else { + return null; + } + } } } } diff --git a/src/main/resources/static/templates/JWTVulnerability/keys/.gitignore b/src/main/resources/static/templates/JWTVulnerability/keys/.gitignore new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/main/resources/static/templates/JWTVulnerability/keys/.gitignore @@ -0,0 +1 @@ + diff --git a/src/main/resources/static/templates/JWTVulnerability/keys/private_key.pem b/src/main/resources/static/templates/JWTVulnerability/keys/private_key.pem index 8558d380..8b137891 100644 --- a/src/main/resources/static/templates/JWTVulnerability/keys/private_key.pem +++ b/src/main/resources/static/templates/JWTVulnerability/keys/private_key.pem @@ -1,32 +1 @@ -Bag Attributes - friendlyName: sasanlabs - localKeyID: 54 69 6D 65 20 31 35 38 31 32 33 35 36 32 35 30 30 33 -Key Attributes: ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDG86CoStCZbgTi -1zAC8+/O5grgOWrZXeGwmlGkHavJamSra/JbJbYk8ixpPbWhEdWeVGjoNGOl7g3D -AKhJdDh5T6nK2JefEnsklJ5lSKtvQYKUtzcK5UfoNL2+CkqkELWfPy1GcCgfhoPc -UBBIarf4yieYKATP9dL3UXIEkpqMaNu092QihzplgbUXcEzA4GISYVJyAtuEXXz3 -Vm6H2cgIi9svfGgkFYu1D7qVV+phXIxrAVU4dB1ZhaauLKsxyXaM2fErerX1C/eq -AMjTu4bGFdaaHRr074fYOputO38Ll1E7K83jxT1HhjKjEzv3tHP3he4jUeMFQVxX -ETKT8zAhAgMBAAECggEADA+W7LzkWnjN+QZ8laE+J3fQrvksHhNP7EneqylVUbeO -dMntflMR8LlxscuY6DPRlHCfj3wlkliVIv42NYXDKq+GppJs1qrjJjuQQqmeIveA -uA1HW/S8YDpaSlwLXFja+dV1pDCGbirUcZW09v7pOj7fGZ1LdWP8rxuT4u0US3Cw -fgXlR9RlsE8zkRl/Ae6nPU5eHPGr0tJfJP+dZNb62dF7FeuRxEVM4SfE4MK8tSYa -IahgCJt2yjQ0ulmGxY/jHBiKNexB4MFSu5j0MHer05z0X4WcOJJivQ7JuiLP5ooA -WjdUof7Ob9havSXOhd3qCn+rQWbmjKGl64cE+BVhlQKBgQDwefpF1CIlCp5/CDrg -5Qntb9VUXsxtWyybsaR82rQQXPrJIN3QTkKzYm+8xrOnfgwg5vIgpquiFaxcERaK -3hqW/2gTyatTtIxrjNNczjsheIcY/4fdCm4yxruBA2XXSPQNgIsvhS3MCjo8juI6 -/vcbph+CBhe6vSGuFC+E9Y9ZdwKBgQDTy21HsO/HR17vKV1+MfHUUs088YbsreoR -chs0mE+OQGPX6TR9K2OOYvMcQDNn0QbhtHtY2POgmCFFCVIbfRKCxG4sF3yIhoar -DEBQrIaggBazAXyUcL+e8lcrGWayLRcwpGr0PYIWJqEKy2jC4JzgL0Ssm+VttVQH -4QJAEpupJwKBgG09S+aqrfQbtdJJH84H3ZGhqswP4FeRAlubv/gDtaZ1RmtVZc35 -ry0j+1RLA1OD2+iaYMVaUT9pDwonrRDaQkPztAjBJPX6X4t/xogzGwNiaCR/9+z+ -jv677nN14q6AcnUrvo6QtjQpNTlLQxO/vOsvdMKxF9h5kDIu80M39a2TAoGAN7en -mxmgKuPKxM40C1PmU74owiSkIzWpg0dqgs6i90BXQ+DU7yzv9vBvFnqJS4GA9vW9 -EWWZyiDbd8b488RWj1JPzYesOlpxqSQC83Y/wI+R6Su183Mp5g3JAsye6LbWB/Tp -MjHQPDWTXjye5c2jV5L31RT6KX9viNcX+XUrwDcCgYEAt/EX1yPkWTW6/OFZcwjC -B6SkPbFekiuw4lnsb4APCwmlX5ZrKxvBoI6QFKuBudIeMHj9M9iYeyH1XrZvCXDJ -5/pNR+G4XxNlUG5xT7hcF9sUwCS0DOVQFP7qZe3++Ofaz0IkmS7/COqdNjpisOrQ -BkNPNmZCRzK9KvV1BS5Mpfw= ------END PRIVATE KEY----- +