diff --git a/.classpath b/.classpath index 467ef2f2..53ba604a 100644 --- a/.classpath +++ b/.classpath @@ -1,12 +1,19 @@ - + + + + + + + + - + diff --git a/.project b/.project index cc8af00e..f6e3bd91 100644 --- a/.project +++ b/.project @@ -20,4 +20,15 @@ org.eclipse.jdt.core.javanature org.eclipse.buildship.core.gradleprojectnature + + + 1738769978762 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs index e8895216..87b1185f 100644 --- a/.settings/org.eclipse.buildship.core.prefs +++ b/.settings/org.eclipse.buildship.core.prefs @@ -1,2 +1,13 @@ +arguments=--init-script /home/lyes/.config/Code/User/globalStorage/redhat.java/1.39.0/config_linux/org.eclipse.osgi/58/0/.cp/gradle/init/init.gradle --init-script /home/lyes/.config/Code/User/globalStorage/redhat.java/1.39.0/config_linux/org.eclipse.osgi/58/0/.cp/gradle/protobuf/init.gradle +auto.sync=false +build.scans.enabled=false +connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) connection.project.dir= eclipse.preferences.version=1 +gradle.user.home= +java.home=/usr/lib/jvm/jdk-23.0.1-oracle-x64 +jvm.arguments= +offline.mode=false +override.workspace.settings=true +show.console.view=true +show.executions.view=true diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..559aa4bd --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,5 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.classpath.outputOverlappingAnotherSource=ignore +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..c5f3f6b9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "interactive" +} \ No newline at end of file diff --git a/src/main/java/org/sasanlabs/service/vulnerability/sampleVulnerability/SampleVulnerability.java b/src/main/java/org/sasanlabs/service/vulnerability/sampleVulnerability/SampleVulnerability.java new file mode 100644 index 00000000..3f15c150 --- /dev/null +++ b/src/main/java/org/sasanlabs/service/vulnerability/sampleVulnerability/SampleVulnerability.java @@ -0,0 +1,108 @@ +package org.sasanlabs.service.vulnerability.sampleVulnerability; + +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.vulnerability.bean.GenericVulnerabilityResponseBean; +import org.sasanlabs.vulnerability.types.VulnerabilityType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * This is a sample vulnerability for helping developers in adding a new Vulnerability for + * VulnerableApp + * + * @author KSASAN preetkaran20@gmail.com + */ +/** + * {@code VulnerableAppRestController} annotation is similar to {@link + * org.springframework.stereotype.Controller} Annotation + */ +@VulnerableAppRestController( + /** + * "descriptionLabel" parameter of annotation is i18n label stored in {@link + * /VulnerableApp/src/main/resources/i18n/}. This descriptionLabel + * will be shown in the UI as the description of the Vulnerability. It helps students to + * learn about the vulnerability and can also include some of the useful references etc. + */ + descriptionLabel = "SAMPLE_VULNERABILITY", + /** + * "value" parameter of annotation is used to create the request mapping. e.g. for the below + * parameter value, /VulnerableApp/SampleVulnerability will be created as URI Path. + */ + value = "SampleVulnerability") +public class SampleVulnerability { + + /** + * {@code AttackVector} annotation is used to create the Hints section in the User Interface. + * This annotation can be mentioned multiple times in case the same vulnerability level + */ + @AttackVector( + /** + * "vulnerabilityExposed" parameter is used to depict the Vulnerability exposed by the + * level. For example say a level is exposing SQL_INJECTION. + */ + vulnerabilityExposed = VulnerabilityType.SAMPLE_VULNERABILITY, + /** + * "description" parameter of annotation is i18n label stored in {@link + * /VulnerableApp/src/main/resources/i18n/}. This description + * will be shown in the UI as hint to give some indication on how the level is handling + * input to help user to crack the level. + */ + description = "SAMPLE_VULNERABILITY_USER_INPUT_HANDLING_INJECTION", + + /** + * "payload" parameter of annotation is i18n label stored in {@link + * /VulnerableApp/src/main/resources/attackvectors/*.properties}. This payload will be + * shown in UI to help users find/exploit the vulnerability + */ + payload = "NOT_APPLICABLE") + /** + * This annotation is similar to {@link RequestMapping} SpringBoot annotation. It will map the + * endpoint to /VulnerableApp/SampleVulnerability/LEVEL_1 where LEVEL_1 is coming from the value + * parameter. + */ + @VulnerableAppRequestMapping( + /** + * "value" parameter is used to map the level to URI path + * /VulnerableApp/SampleVulnerability/${value}. + */ + value = LevelConstants.LEVEL_1, + + /** + * "htmlTemplate" is used to load the UI for the level for taking input from the user. + * It points to files in directory + * src/main/resource/static/templates/${VulnerabilityName} e.g. + * src/main/resource/static/templates/SampleVulnerability as ${htmlTemplate}.js, + * ${htmlTemplate}.css, ${htmlTemplate}.html. e.g. in this case it will be: + * src/main/resource/static/templates/SampleVulnerability/LEVEL_1/SampleVulnerability_Level1.js + * etc + * + *

CSS, JS and HTML are all loaded to render the UI. + */ + htmlTemplate = "LEVEL_1/SampleVulnerability") + public GenericVulnerabilityResponseBean sampleUnsecuredLevel(@RequestParam("name") String key) { + /** Add Business logic here */ + return new GenericVulnerabilityResponseBean<>("Not Implemented", true); + } + + /** For secured level there is no need for {@link AttackVector} annotation. */ + @VulnerableAppRequestMapping( + value = LevelConstants.LEVEL_2, + + // Can reuse the same UI template in case it doesn't change between levels + htmlTemplate = "LEVEL_1/SampleVulnerability", + /** + * "variant" parameter defines whether the level is secure or not and same is depicted + * in the UI as a closed lock and open lock icon. Default value of the variant is + * UNSECURE so in case a secure level is added, please add the variant as {@link + * Variant#SECURE} + */ + variant = Variant.SECURE) + public GenericVulnerabilityResponseBean sampleSecuredLevel(@RequestParam("name") String key) { + /** Add Business logic here */ + return new GenericVulnerabilityResponseBean<>("Not Implemented", true); + } +} \ No newline at end of file diff --git a/src/main/java/org/sasanlabs/service/vulnerability/xss/persistent/PersistentXSSInHTMLTagVulnerability.java b/src/main/java/org/sasanlabs/service/vulnerability/xss/persistent/PersistentXSSInHTMLTagVulnerability.java index 124dfb4f..6b5e134d 100644 --- a/src/main/java/org/sasanlabs/service/vulnerability/xss/persistent/PersistentXSSInHTMLTagVulnerability.java +++ b/src/main/java/org/sasanlabs/service/vulnerability/xss/persistent/PersistentXSSInHTMLTagVulnerability.java @@ -218,4 +218,54 @@ public ResponseEntity getVulnerablePayloadLevel7( post -> StringEscapeUtils.escapeHtml4(post)), HttpStatus.OK); } + //escape html and delete all the script and img tags + @VulnerableAppRequestMapping( + value = LevelConstants.LEVEL_8, + htmlTemplate = "LEVEL_1/PersistentXSS", + variant = Variant.SECURE) + public ResponseEntity getSecurePayloadLevel8( + @RequestParam Map queryParams) { + return new ResponseEntity<>( + this.getCommentsPayload( + queryParams, + LevelConstants.LEVEL_8, + post -> StringEscapeUtils.escapeHtml4( + post.replaceAll("(?i).*?", "") + .replaceAll("(?i)", ""))), + HttpStatus.OK); + } + //delete all the html tags + @VulnerableAppRequestMapping( + value = LevelConstants.LEVEL_9, + htmlTemplate = "LEVEL_1/PersistentXSS", + variant = Variant.SECURE) + public ResponseEntity getSecurePayloadLevel9( + @RequestParam Map queryParams) { + Function function = + (post) -> { + String sanitizedPost = post.replaceAll("<.*?>", ""); // Delete all the html balises + return StringEscapeUtils.escapeHtml4(sanitizedPost); + }; + return new ResponseEntity<>( + this.getCommentsPayload(queryParams, LevelConstants.LEVEL_9, function), + HttpStatus.OK); + } + //delete all the js calls + @VulnerableAppRequestMapping( + value = LevelConstants.LEVEL_10, + htmlTemplate = "LEVEL_1/PersistentXSS", + variant = Variant.SECURE) + public ResponseEntity getSecurePayloadLevel10( + @RequestParam Map queryParams) { + Function function = + (post) -> { + String sanitizedPost = StringEscapeUtils.escapeHtml4(post); + sanitizedPost = sanitizedPost.replaceAll("(?i)javascript:", ""); // Delete all the js calls + return StringEscapeUtils.escapeHtml4(sanitizedPost); + }; + return new ResponseEntity<>( + this.getCommentsPayload(queryParams, LevelConstants.LEVEL_10, function), + HttpStatus.OK); + } + } diff --git a/src/main/java/org/sasanlabs/service/vulnerability/xss/reflected/XSSInImgTagAttribute.java b/src/main/java/org/sasanlabs/service/vulnerability/xss/reflected/XSSInImgTagAttribute.java index dc2819c4..66088a4f 100644 --- a/src/main/java/org/sasanlabs/service/vulnerability/xss/reflected/XSSInImgTagAttribute.java +++ b/src/main/java/org/sasanlabs/service/vulnerability/xss/reflected/XSSInImgTagAttribute.java @@ -14,6 +14,15 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.util.HtmlUtils; +import org.springframework.http.HttpHeaders; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.io.File; +import java.nio.file.Files; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.net.URLConnection; /** * This class contains XSS vulnerabilities which are present in Image Tag attribute. @@ -201,4 +210,88 @@ public ResponseEntity getVulnerablePayloadLevelSecure( return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } } + + // Level 8: Protection avec Content Security Policy (CSP) + @AttackVector( + vulnerabilityExposed = VulnerabilityType.REFLECTED_XSS, + description = "XSS_CSP_PROTECTION") + @VulnerableAppRequestMapping( + value = LevelConstants.LEVEL_8, + variant = Variant.SECURE, + htmlTemplate = "LEVEL_1/XSS") + public ResponseEntity getVulnerablePayloadLevelSecure2(@RequestParam(PARAMETER_NAME) String imageLocation) { + HttpHeaders headers = new HttpHeaders(); + headers.add("Content-Security-Policy", "default-src 'self'; img-src 'self'"); + String vulnerablePayloadWithPlaceHolder = ""; + String payload = String.format(vulnerablePayloadWithPlaceHolder, HtmlUtils.htmlEscape(imageLocation)); + return new ResponseEntity<>(payload, headers, HttpStatus.OK); + } + + // Level 9: Protection avec validation du type MIME + @AttackVector( + vulnerabilityExposed = VulnerabilityType.REFLECTED_XSS, + description = "XSS_MIME_TYPE_VALIDATION") + @VulnerableAppRequestMapping( + value = LevelConstants.LEVEL_9, + variant = Variant.SECURE, + htmlTemplate = "LEVEL_1/XSS") + public ResponseEntity validateMimeType(@RequestParam(PARAMETER_NAME) String imageLocation) { + Path filePath = Paths.get(imageLocation); + try { + File file = filePath.toFile(); + String mimeType = Files.probeContentType(filePath); + if (mimeType == null) { + mimeType = URLConnection.guessContentTypeFromName(file.getName()); + } + if (mimeType != null && mimeType.startsWith("image/")) { + String vulnerablePayloadWithPlaceHolder = ""; + String payload = String.format(vulnerablePayloadWithPlaceHolder, imageLocation); + return new ResponseEntity<>(payload, HttpStatus.OK); + } + } catch (IOException e) { + return new ResponseEntity<>("Error detecting MIME Type", HttpStatus.INTERNAL_SERVER_ERROR); + } + return new ResponseEntity<>("Invalid MIME Type", HttpStatus.BAD_REQUEST); + } + + // Level 10: Protection avec hachage du chemin de l'image + @AttackVector( + vulnerabilityExposed = VulnerabilityType.REFLECTED_XSS, + description = "XSS_HASH_VALIDATION") + @VulnerableAppRequestMapping( + value = LevelConstants.LEVEL_10, + variant = Variant.SECURE, + htmlTemplate = "LEVEL_1/XSS") + public ResponseEntity getVulnerablePayloadLevelSecure4(@RequestParam(PARAMETER_NAME) String imageLocation) { + String hashedValue = hashSHA256(imageLocation); + if (hashedValue.equals("bd473875115034776f0fed141a0b6f8cbd46989e0ff1d52864f88a4e48882c75") || + hashedValue.equals("6374f10c07ebcebe4dcff4df7ea882ab4e5feeb9ba132c94cd38ac880eb1407b")) { + String vulnerablePayloadWithPlaceHolder = ""; + String payload = String.format(vulnerablePayloadWithPlaceHolder, imageLocation); + return new ResponseEntity<>(payload, HttpStatus.OK); + } + return new ResponseEntity<>("Invalid Image Hash", HttpStatus.BAD_REQUEST); + } + + private String hashSHA256(String input) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(input.getBytes()); + return bytesToHex(hash); + } catch (NoSuchAlgorithmException e) { + return ""; + } + } + + private String bytesToHex(byte[] bytes) { + StringBuilder hexString = new StringBuilder(); + for (byte b : bytes) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + return hexString.toString(); + } } diff --git a/src/main/resources/i18n/messages_en_US.properties b/src/main/resources/i18n/messages_en_US.properties index d73d7e89..305930cb 100755 --- a/src/main/resources/i18n/messages_en_US.properties +++ b/src/main/resources/i18n/messages_en_US.properties @@ -40,6 +40,9 @@ XSS_HTML_ESCAPE_ON_DIRECT_INPUT_AND_REMOVAL_OF_VALUES_WITH_PARENTHESIS_SRC_ATTRI XSS_QUOTES_AND_WITH_HTML_ESCAPE_ON_INPUT_SRC_ATTRIBUTE_IMG_TAG=HTML escaping is done on the Url Parameters and then inserted inside Quotes into the src attribute of Image Tag. XSS_HTML_ESCAPE_PLUS_FILTERING_ON_INPUT_SRC_ATTRIBUTE_IMG_TAG_BUT_NULL_BYTE_VULNERABLE=Url Parameters are HTML escaped, validated against whitelist of filenames and inserted into the src attribute of Image Tag, However validator for validating filenames is vulnerable with Null Byte Injection. XSS_QUOTES_AND_WITH_HTML_ESCAPE_PLUS_FILTERING_ON_INPUT_SRC_ATTRIBUTE_IMG_TAG=Url Parameters are HTML escaped, validated against whitelist of filenames and inserted inside Quotes into the src attribute of Image Tag. +XSS_CSP_PROTECTION="CSP is Used to prevent XSS" +XSS_MIME_TYPE_VALIDATION="MIME Type is tested to ensure that the file is PNG" +XSS_HASH_VALIDATION="Hash of the path is used to ensure that only the requested images are displayed" ## Html Tag Injection XSS_HTML_TAG_INJECTION=Html Tag based XSS attack. diff --git a/src/main/resources/static/templates/SampleVulnerability/LEVEL_1/SampleVulnerability.css b/src/main/resources/static/templates/SampleVulnerability/LEVEL_1/SampleVulnerability.css new file mode 100644 index 00000000..e5d57b25 --- /dev/null +++ b/src/main/resources/static/templates/SampleVulnerability/LEVEL_1/SampleVulnerability.css @@ -0,0 +1,16 @@ +#SampleVulnerability { + color: black; + text-align: center; +} + +#fetchDetails { + background: blueviolet; + display: inline-block; + padding: 8px 8px; + margin: 10px; + border: 2px solid transparent; + border-radius: 3px; + transition: 0.2s opacity; + color: #FFF; + font-size: 12px; +} \ No newline at end of file diff --git a/src/main/resources/static/templates/SampleVulnerability/LEVEL_1/SampleVulnerability.html b/src/main/resources/static/templates/SampleVulnerability/LEVEL_1/SampleVulnerability.html new file mode 100644 index 00000000..dddeea7d --- /dev/null +++ b/src/main/resources/static/templates/SampleVulnerability/LEVEL_1/SampleVulnerability.html @@ -0,0 +1,9 @@ +

+
+
+ This is a Sample Vulnerability. please add the UI components here. +
+ +
+
+
\ No newline at end of file diff --git a/src/main/resources/static/templates/SampleVulnerability/LEVEL_1/SampleVulnerability.js b/src/main/resources/static/templates/SampleVulnerability/LEVEL_1/SampleVulnerability.js new file mode 100644 index 00000000..78641721 --- /dev/null +++ b/src/main/resources/static/templates/SampleVulnerability/LEVEL_1/SampleVulnerability.js @@ -0,0 +1,23 @@ +function addingEventListenerToFetchData() { + document + .getElementById("fetchDetails") + .addEventListener("click", function () { + /** + * getUrlForVulnerabilityLevel() method provides url to call the Vulnerability Level + * of Sample Vulnerability. + * e.g. /VulnerableApp/SampleVulnerability/LEVEL_1 for LEVEL_1 + */ + let url = getUrlForVulnerabilityLevel(); + /** + * doGetAjaxCall() method is used to do the ajax get call to the Vulnerability Level + */ + doGetAjaxCall(fetchDataCallback, url + "?name=dummyInput", true); + }); +} +// Used to register event on the button or any other component +addingEventListenerToFetchData(); + +//Callback function to handle the response and render in the UI +function fetchDataCallback(data) { + document.getElementById("response").innerHTML = data.content; +}