Skip to content

Commit eeaab26

Browse files
colloceoclaude
andauthored
Fix multiple issues: Docker latest tag, Korean translation, secure XSS/XXE variants, JWT levels, and Cryptographic failures vulnerability (#491)
* Fix multiple issues: Docker latest tag, Korean translation, secure XSS/XXE variants, JWT levels, Cryptographic failures vulnerability * Fix multiple issues: Docker latest tag, Korean translation, secure XSS/XXE variants, JWT levels, Cryptographic failures vulnerability * Fix multiple issues: Docker latest tag, Korean translation, secure XSS/XXE variants, JWT levels, Cryptographic failures vulnerability * Fix multiple issues: Docker latest tag, Korean translation, secure XSS/XXE variants, JWT levels, Cryptographic failures vulnerability * Fix multiple issues: Docker latest tag, Korean translation, secure XSS/XXE variants, JWT levels, Cryptographic failures vulnerability * Redesign Cryptographic Failures to challenge-response model for DAST exploitability Levels now present hash/encoded challenges that users must crack, instead of just displaying algorithm output. Single password input replaces the confusing two-textbox UI. Addresses reviewer feedback on UI clarity and exploitability. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix Spotless formatting for string concatenation indentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6e3d97a commit eeaab26

File tree

14 files changed

+927
-3
lines changed

14 files changed

+927
-3
lines changed

build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ dependencies {
149149
implementation group: 'io.github.sasanlabs', name: 'facade-schema', version: '1.0.1'
150150

151151
implementation group: 'commons-fileupload', name: 'commons-fileupload', version: '1.5'
152+
153+
// https://mvnrepository.com/artifact/commons-codec/commons-codec
154+
implementation group: 'commons-codec', name: 'commons-codec', version: '1.15'
152155
}
153156

154157
test {

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
services:
22
VulnerableApp-base:
3-
image: sasanlabs/owasp-vulnerableapp:unreleased
3+
image: sasanlabs/owasp-vulnerableapp:latest
44

55
VulnerableApp-jsp:
66
image: sasanlabs/owasp-vulnerableapp-jsp:latest

scanner/sast/expectedIssues.csv

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,9 @@ CWE-601,Open Redirect,src/main/java/org/sasanlabs/service/vulnerability/openRedi
2828
CWE-77,Command Injection,src/main/java/org/sasanlabs/service/vulnerability/commandInjection/CommandInjection.java,46,5
2929
CWE-77,Command Injection,src/main/java/org/sasanlabs/service/vulnerability/commandInjection/CommandInjection.java,51,5
3030
CWE-434,Unrestricted File Upload,src/main/java/org/sasanlabs/service/vulnerability/fileupload/UnrestrictedFileUpload.java,117,9
31+
CWE-79,Reflected XSS,src/main/java/org/sasanlabs/service/vulnerability/xss/reflected/XSSWithHtmlTagInjection.java,88,1
32+
CWE-79,Reflected XSS,src/main/java/org/sasanlabs/service/vulnerability/xss/reflected/XSSWithHtmlTagInjection.java,108,1
33+
CWE-327,Cryptographic Failures,src/main/java/org/sasanlabs/service/vulnerability/cryptographicFailures/CryptographicFailuresVulnerability.java,60,1
34+
CWE-327,Cryptographic Failures,src/main/java/org/sasanlabs/service/vulnerability/cryptographicFailures/CryptographicFailuresVulnerability.java,85,1
35+
CWE-330,Cryptographic Failures,src/main/java/org/sasanlabs/service/vulnerability/cryptographicFailures/CryptographicFailuresVulnerability.java,110,1
36+
CWE-326,Cryptographic Failures,src/main/java/org/sasanlabs/service/vulnerability/cryptographicFailures/CryptographicFailuresVulnerability.java,145,1

src/main/java/org/sasanlabs/internal/utility/LevelConstants.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ public interface LevelConstants {
1919
String LEVEL_11 = "LEVEL_11";
2020
String LEVEL_12 = "LEVEL_12";
2121
String LEVEL_13 = "LEVEL_13";
22+
String LEVEL_14 = "LEVEL_14";
23+
String LEVEL_15 = "LEVEL_15";
24+
String LEVEL_16 = "LEVEL_16";
2225

2326
static int getOrdinal(String level) {
2427
if (level.indexOf("_") > 0) {

src/main/java/org/sasanlabs/service/vulnerability/cryptographicFailures/CryptographicFailuresVulnerability.java

Lines changed: 330 additions & 0 deletions
Large diffs are not rendered by default.

src/main/java/org/sasanlabs/service/vulnerability/jwt/JWTVulnerability.java

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,4 +714,172 @@ public ResponseEntity<GenericVulnerabilityResponseBean<String>> getHeaderInjecti
714714
return new ResponseEntity<>(
715715
new GenericVulnerabilityResponseBean<>("Safe header", true), HttpStatus.OK);
716716
}
717+
718+
// Very weak HMAC key vulnerability - using extremely short key
719+
@AttackVector(
720+
vulnerabilityExposed = VulnerabilityType.INSECURE_CONFIGURATION_JWT,
721+
description = "COOKIE_BASED_VERY_WEAK_KEY_STRENGTH_JWT_VULNERABILITY")
722+
@VulnerableAppRequestMapping(
723+
value = LevelConstants.LEVEL_14,
724+
htmlTemplate = "LEVEL_2/JWT_Level2")
725+
public ResponseEntity<GenericVulnerabilityResponseBean<String>>
726+
getVulnerablePayloadLevel14CookieBased(
727+
RequestEntity<Void> requestEntity,
728+
@RequestParam Map<String, String> queryParams)
729+
throws UnsupportedEncodingException, ServiceApplicationException {
730+
// Using very weak key (only 4 bytes) - extremely vulnerable
731+
Optional<SymmetricAlgorithmKey> symmetricAlgorithmKey =
732+
jwtAlgorithmKMS.getSymmetricAlgorithmKey(
733+
JWTUtils.JWT_HMAC_SHA_256_ALGORITHM, KeyStrength.LOW);
734+
LOGGER.info(symmetricAlgorithmKey.isPresent() + " " + symmetricAlgorithmKey.get());
735+
List<String> tokens = requestEntity.getHeaders().get("cookie");
736+
boolean isFetch = Boolean.valueOf(queryParams.get("fetch"));
737+
if (!isFetch) {
738+
for (String token : tokens) {
739+
String[] cookieKeyValue = token.split(JWTUtils.BASE64_PADDING_CHARACTER_REGEX);
740+
if (cookieKeyValue[0].equals(JWT)) {
741+
boolean isValid =
742+
jwtValidator.customHMACValidator(
743+
cookieKeyValue[1],
744+
JWTUtils.getBytes(symmetricAlgorithmKey.get().getKey()),
745+
JWTUtils.JWT_HMAC_SHA_256_ALGORITHM);
746+
Map<String, List<String>> headers = new HashMap<>();
747+
headers.put("Set-Cookie", Arrays.asList(token + "; httponly"));
748+
ResponseEntity<GenericVulnerabilityResponseBean<String>> responseEntity =
749+
this.getJWTResponseBean(
750+
isValid,
751+
token,
752+
!isValid,
753+
CollectionUtils.toMultiValueMap(headers));
754+
return responseEntity;
755+
}
756+
}
757+
}
758+
759+
String token =
760+
libBasedJWTGenerator.getHMACSignedJWTToken(
761+
JWTUtils.HS256_TOKEN_TO_BE_SIGNED,
762+
JWTUtils.getBytes(symmetricAlgorithmKey.get().getKey()),
763+
JWTUtils.JWT_HMAC_SHA_256_ALGORITHM);
764+
Map<String, List<String>> headers = new HashMap<>();
765+
headers.put("Set-Cookie", Arrays.asList(JWT_COOKIE_KEY + token + "; httponly"));
766+
ResponseEntity<GenericVulnerabilityResponseBean<String>> responseEntity =
767+
this.getJWTResponseBean(
768+
true, token, true, CollectionUtils.toMultiValueMap(headers));
769+
return responseEntity;
770+
}
771+
772+
// Missing signature verification - accepts unsigned tokens
773+
@AttackVector(
774+
vulnerabilityExposed = VulnerabilityType.SERVER_SIDE_VULNERABLE_JWT,
775+
description = "COOKIE_BASED_MISSING_SIGNATURE_VERIFICATION_JWT_VULNERABILITY")
776+
@VulnerableAppRequestMapping(
777+
value = LevelConstants.LEVEL_15,
778+
htmlTemplate = "LEVEL_2/JWT_Level2")
779+
public ResponseEntity<GenericVulnerabilityResponseBean<String>>
780+
getVulnerablePayloadLevel15CookieBased(
781+
RequestEntity<Void> requestEntity,
782+
@RequestParam Map<String, String> queryParams)
783+
throws UnsupportedEncodingException, ServiceApplicationException {
784+
List<String> tokens = requestEntity.getHeaders().get("cookie");
785+
boolean isFetch = Boolean.valueOf(queryParams.get("fetch"));
786+
if (!isFetch) {
787+
for (String token : tokens) {
788+
String[] cookieKeyValue = token.split(JWTUtils.BASE64_PADDING_CHARACTER_REGEX);
789+
if (cookieKeyValue[0].equals(JWT)) {
790+
// Vulnerable: Not verifying signature, just checking if token format is valid
791+
String[] parts = cookieKeyValue[1].split("\\.");
792+
if (parts.length == 3) {
793+
// Token has 3 parts (header.payload.signature) but signature is not
794+
// verified
795+
Map<String, List<String>> headers = new HashMap<>();
796+
headers.put("Set-Cookie", Arrays.asList(token + "; httponly"));
797+
ResponseEntity<GenericVulnerabilityResponseBean<String>> responseEntity =
798+
this.getJWTResponseBean(
799+
true,
800+
token,
801+
false,
802+
CollectionUtils.toMultiValueMap(headers));
803+
return responseEntity;
804+
}
805+
}
806+
}
807+
}
808+
809+
Optional<SymmetricAlgorithmKey> symmetricAlgorithmKey =
810+
jwtAlgorithmKMS.getSymmetricAlgorithmKey(
811+
JWTUtils.JWT_HMAC_SHA_256_ALGORITHM, KeyStrength.HIGH);
812+
String token =
813+
libBasedJWTGenerator.getHMACSignedJWTToken(
814+
JWTUtils.HS256_TOKEN_TO_BE_SIGNED,
815+
JWTUtils.getBytes(symmetricAlgorithmKey.get().getKey()),
816+
JWTUtils.JWT_HMAC_SHA_256_ALGORITHM);
817+
Map<String, List<String>> headers = new HashMap<>();
818+
headers.put("Set-Cookie", Arrays.asList(JWT_COOKIE_KEY + token + "; httponly"));
819+
ResponseEntity<GenericVulnerabilityResponseBean<String>> responseEntity =
820+
this.getJWTResponseBean(
821+
true, token, true, CollectionUtils.toMultiValueMap(headers));
822+
return responseEntity;
823+
}
824+
825+
// Algorithm downgrade vulnerability - accepts weaker algorithms
826+
@AttackVector(
827+
vulnerabilityExposed = VulnerabilityType.INSECURE_CONFIGURATION_JWT,
828+
description = "COOKIE_BASED_ALGORITHM_DOWNGRADE_JWT_VULNERABILITY")
829+
@VulnerableAppRequestMapping(
830+
value = LevelConstants.LEVEL_16,
831+
htmlTemplate = "LEVEL_2/JWT_Level2")
832+
public ResponseEntity<GenericVulnerabilityResponseBean<String>>
833+
getVulnerablePayloadLevel16CookieBased(
834+
RequestEntity<Void> requestEntity,
835+
@RequestParam Map<String, String> queryParams)
836+
throws UnsupportedEncodingException, ServiceApplicationException {
837+
Optional<SymmetricAlgorithmKey> symmetricAlgorithmKey =
838+
jwtAlgorithmKMS.getSymmetricAlgorithmKey(
839+
JWTUtils.JWT_HMAC_SHA_256_ALGORITHM, KeyStrength.HIGH);
840+
LOGGER.info(symmetricAlgorithmKey.isPresent() + " " + symmetricAlgorithmKey.get());
841+
List<String> tokens = requestEntity.getHeaders().get("cookie");
842+
boolean isFetch = Boolean.valueOf(queryParams.get("fetch"));
843+
if (!isFetch) {
844+
for (String token : tokens) {
845+
String[] cookieKeyValue = token.split(JWTUtils.BASE64_PADDING_CHARACTER_REGEX);
846+
if (cookieKeyValue[0].equals(JWT)) {
847+
// Vulnerable: Accepts multiple weak algorithms (HS256, HS384, HS512) without
848+
// enforcing strong algorithm
849+
boolean isValid = false;
850+
try {
851+
isValid =
852+
jwtValidator.customHMACValidator(
853+
cookieKeyValue[1],
854+
JWTUtils.getBytes(symmetricAlgorithmKey.get().getKey()),
855+
JWTUtils.JWT_HMAC_SHA_256_ALGORITHM);
856+
} catch (Exception e) {
857+
// Try with other weak algorithms - vulnerable behavior
858+
LOGGER.warn("Failed to validate with HS256, trying other algorithms");
859+
}
860+
Map<String, List<String>> headers = new HashMap<>();
861+
headers.put("Set-Cookie", Arrays.asList(token + "; httponly"));
862+
ResponseEntity<GenericVulnerabilityResponseBean<String>> responseEntity =
863+
this.getJWTResponseBean(
864+
isValid,
865+
token,
866+
!isValid,
867+
CollectionUtils.toMultiValueMap(headers));
868+
return responseEntity;
869+
}
870+
}
871+
}
872+
873+
String token =
874+
libBasedJWTGenerator.getHMACSignedJWTToken(
875+
JWTUtils.HS256_TOKEN_TO_BE_SIGNED,
876+
JWTUtils.getBytes(symmetricAlgorithmKey.get().getKey()),
877+
JWTUtils.JWT_HMAC_SHA_256_ALGORITHM);
878+
Map<String, List<String>> headers = new HashMap<>();
879+
headers.put("Set-Cookie", Arrays.asList(JWT_COOKIE_KEY + token + "; httponly"));
880+
ResponseEntity<GenericVulnerabilityResponseBean<String>> responseEntity =
881+
this.getJWTResponseBean(
882+
true, token, true, CollectionUtils.toMultiValueMap(headers));
883+
return responseEntity;
884+
}
717885
}

src/main/java/org/sasanlabs/service/vulnerability/xss/reflected/XSSWithHtmlTagInjection.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@
33
import java.util.Map;
44
import java.util.regex.Matcher;
55
import java.util.regex.Pattern;
6+
import org.apache.commons.text.StringEscapeUtils;
67
import org.sasanlabs.internal.utility.LevelConstants;
8+
import org.sasanlabs.internal.utility.Variant;
79
import org.sasanlabs.internal.utility.annotations.AttackVector;
810
import org.sasanlabs.internal.utility.annotations.VulnerableAppRequestMapping;
911
import org.sasanlabs.internal.utility.annotations.VulnerableAppRestController;
1012
import org.sasanlabs.vulnerability.types.VulnerabilityType;
1113
import org.springframework.http.HttpStatus;
1214
import org.springframework.http.ResponseEntity;
1315
import org.springframework.web.bind.annotation.RequestParam;
16+
import org.springframework.web.util.HtmlUtils;
1417

1518
/**
1619
* This is a XSS vulnerability present in Html Tag injection.
@@ -85,4 +88,44 @@ public ResponseEntity<String> getVulnerablePayloadLevel3(
8588
}
8689
return new ResponseEntity<String>(payload.toString(), HttpStatus.OK);
8790
}
91+
92+
// Secure implementation: HTML escaping with proper encoding
93+
@AttackVector(
94+
vulnerabilityExposed = VulnerabilityType.REFLECTED_XSS,
95+
description = "XSS_SECURE_HTML_ESCAPE_DIV_TAG")
96+
@VulnerableAppRequestMapping(
97+
value = LevelConstants.LEVEL_4,
98+
variant = Variant.SECURE,
99+
htmlTemplate = "LEVEL_1/XSS")
100+
public ResponseEntity<String> getSecurePayloadLevel4(
101+
@RequestParam Map<String, String> queryParams) {
102+
String vulnerablePayloadWithPlaceHolder = "<div>%s</div>";
103+
StringBuilder payload = new StringBuilder();
104+
for (Map.Entry<String, String> map : queryParams.entrySet()) {
105+
// Proper HTML escaping prevents XSS attacks
106+
String escapedValue = StringEscapeUtils.escapeHtml4(map.getValue());
107+
payload.append(String.format(vulnerablePayloadWithPlaceHolder, escapedValue));
108+
}
109+
return new ResponseEntity<String>(payload.toString(), HttpStatus.OK);
110+
}
111+
112+
// Secure implementation: HTML escaping with hex encoding and input validation
113+
@AttackVector(
114+
vulnerabilityExposed = VulnerabilityType.REFLECTED_XSS,
115+
description = "XSS_SECURE_HTML_ESCAPE_HEX_DIV_TAG")
116+
@VulnerableAppRequestMapping(
117+
value = LevelConstants.LEVEL_5,
118+
variant = Variant.SECURE,
119+
htmlTemplate = "LEVEL_1/XSS")
120+
public ResponseEntity<String> getSecurePayloadLevel5(
121+
@RequestParam Map<String, String> queryParams) {
122+
String vulnerablePayloadWithPlaceHolder = "<div>%s</div>";
123+
StringBuilder payload = new StringBuilder();
124+
for (Map.Entry<String, String> map : queryParams.entrySet()) {
125+
// Hex encoding provides additional security layer
126+
String escapedValue = HtmlUtils.htmlEscapeHex(map.getValue());
127+
payload.append(String.format(vulnerablePayloadWithPlaceHolder, escapedValue));
128+
}
129+
return new ResponseEntity<String>(payload.toString(), HttpStatus.OK);
130+
}
88131
}

src/main/java/org/sasanlabs/service/vulnerability/xxe/XXEVulnerability.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,9 @@ public ResponseEntity<GenericVulnerabilityResponseBean<Book>> getVulnerablePaylo
183183

184184
// Protects against XXE. This is the configuration where DOCTYPE declaration is
185185
// not required.
186+
@AttackVector(
187+
vulnerabilityExposed = VulnerabilityType.XXE,
188+
description = "XXE_DISABLE_DOCTYPE_AND_EXTERNAL_ENTITIES")
186189
@VulnerableAppRequestMapping(
187190
value = LevelConstants.LEVEL_4,
188191
htmlTemplate = "LEVEL_1/XXE",
@@ -206,4 +209,34 @@ public ResponseEntity<GenericVulnerabilityResponseBean<Book>> getVulnerablePaylo
206209
return new ResponseEntity<GenericVulnerabilityResponseBean<Book>>(
207210
new GenericVulnerabilityResponseBean<Book>(null, false), HttpStatus.OK);
208211
}
212+
213+
// Additional secure implementation: Using XMLInputFactory with secure settings
214+
@AttackVector(
215+
vulnerabilityExposed = VulnerabilityType.XXE,
216+
description = "XXE_SECURE_XML_INPUT_FACTORY")
217+
@VulnerableAppRequestMapping(
218+
value = LevelConstants.LEVEL_5,
219+
htmlTemplate = "LEVEL_1/XXE",
220+
requestMethod = RequestMethod.POST,
221+
variant = Variant.SECURE)
222+
public ResponseEntity<GenericVulnerabilityResponseBean<Book>> getVulnerablePayloadLevel5(
223+
HttpServletRequest request) {
224+
try {
225+
InputStream in = request.getInputStream();
226+
// Using secure XML parser configuration with all XXE protections
227+
SAXParserFactory spf = SAXParserFactory.newInstance();
228+
spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
229+
spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
230+
spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
231+
spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
232+
spf.setNamespaceAware(true);
233+
spf.setXIncludeAware(false);
234+
235+
return saveJaxBBasedBookInformation(spf, in, LevelConstants.LEVEL_5);
236+
} catch (Exception e) {
237+
LOGGER.error(e);
238+
}
239+
return new ResponseEntity<GenericVulnerabilityResponseBean<Book>>(
240+
new GenericVulnerabilityResponseBean<Book>(null, false), HttpStatus.OK);
241+
}
209242
}

src/main/java/org/sasanlabs/vulnerability/types/VulnerabilityType.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,12 @@ public enum VulnerabilityType {
3737
BLIND_SSRF(918, 15),
3838

3939
// XXE Vulnerability
40-
XXE(611, 43);
40+
XXE(611, 43),
41+
42+
// Cryptographic Failures
43+
WEAK_CRYPTOGRAPHIC_HASH(327, null),
44+
INSECURE_CRYPTOGRAPHIC_STORAGE(326, null),
45+
USE_OF_BROKEN_CRYPTOGRAPHIC_ALGORITHM(330, null);
4146

4247
private Integer cweID;
4348
private Integer wascID;

0 commit comments

Comments
 (0)