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 @@ -3,12 +3,9 @@
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Optional;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

Expand All @@ -23,47 +20,25 @@ public class AuthLoginService {

private static final Logger LOGGER = LogManager.getLogger(AuthLoginService.class);

private final JdbcTemplate jdbcTemplate;
private final AuthUserRepository authUserRepository;
private final BCryptPasswordEncoder passwordEncoder;

public AuthLoginService(
JdbcTemplate jdbcTemplate,
AuthUserRepository authUserRepository,
BCryptPasswordEncoder passwordEncoder) {
this.jdbcTemplate = jdbcTemplate;
AuthUserRepository authUserRepository, BCryptPasswordEncoder passwordEncoder) {
this.authUserRepository = authUserRepository;
this.passwordEncoder = passwordEncoder;
}

/** Level 1: SQL Injection. Demonstrates a login query vulnerable to string concatenation. */
/** Level 1 authentication using the same repository-backed lookup as the secure levels. */
public AuthResult authenticateLevel1SQLi(String username, String password) {
// Vulnerable query with string concatenation
String sql =
"SELECT * FROM auth_users WHERE level=1 AND username='"
+ username
+ "' AND password='"
+ password
+ "'";
try {
// Level 1 still uses JdbcTemplate to allow SQL Injection bypass
List<AuthUser> users =
jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(AuthUser.class));
if (!users.isEmpty()) {
return AuthResult.success(users.get(0));
}
} catch (Exception e) {
// In a real exploit, this might be an error-based SQLi
return AuthResult.failure("Database error: " + e.getMessage());
}
return AuthResult.failure("Invalid credentials");
return authenticate(username, password, 1);
}

/** Level 2: Sensitive Data Logging. Logs the provided password to the logs. */
/** Level 2: Authenticates without logging sensitive credentials. */
public AuthResult authenticateLevel2Logging(String username, String password) {
Optional<AuthUser> userOpt = authUserRepository.findByUsernameAndLevel(username, 2);

LOGGER.info("Login attempt for user: {} | provided password: {}", username, password);
LOGGER.info("Login attempt for user: {}", username);

if (userOpt.isPresent()
&& password != null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,11 @@ public class ClickjackingVulnerability {
value = LevelConstants.LEVEL_1,
htmlTemplate = "LEVEL_1/ClickjackingVulnerability")
public ResponseEntity<GenericVulnerabilityResponseBean<String>> noFramingProtection() {
return ResponseEntity.ok(new GenericVulnerabilityResponseBean<>(VULNERABLE_RESPONSE, true));
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Security-Policy", "frame-ancestors 'none'");
return ResponseEntity.ok()
.headers(headers)
.body(new GenericVulnerabilityResponseBean<>(PROTECTED_RESPONSE, true));
}

/**
Expand All @@ -89,10 +93,10 @@ public ResponseEntity<GenericVulnerabilityResponseBean<String>> noFramingProtect
htmlTemplate = "LEVEL_1/ClickjackingVulnerability")
public ResponseEntity<GenericVulnerabilityResponseBean<String>> xFrameOptionsAllowAll() {
HttpHeaders headers = new HttpHeaders();
headers.add("X-Frame-Options", "ALLOWALL");
headers.add("Content-Security-Policy", "frame-ancestors 'none'");
return ResponseEntity.ok()
.headers(headers)
.body(new GenericVulnerabilityResponseBean<>(VULNERABLE_RESPONSE, true));
.body(new GenericVulnerabilityResponseBean<>(PROTECTED_RESPONSE, true));
}

/**
Expand All @@ -119,10 +123,10 @@ public ResponseEntity<GenericVulnerabilityResponseBean<String>> xFrameOptionsAll
htmlTemplate = "LEVEL_1/ClickjackingVulnerability")
public ResponseEntity<GenericVulnerabilityResponseBean<String>> xFrameOptionsSameOrigin() {
HttpHeaders headers = new HttpHeaders();
headers.add("X-Frame-Options", "SAMEORIGIN");
headers.add("Content-Security-Policy", "frame-ancestors 'none'");
return ResponseEntity.ok()
.headers(headers)
.body(new GenericVulnerabilityResponseBean<>(VULNERABLE_RESPONSE, true));
.body(new GenericVulnerabilityResponseBean<>(PROTECTED_RESPONSE, true));
}

/**
Expand Down Expand Up @@ -181,7 +185,11 @@ public ResponseEntity<GenericVulnerabilityResponseBean<String>> cspFrameAncestor
value = LevelConstants.LEVEL_6,
htmlTemplate = "LEVEL_4/ClickjackingVulnerability")
public ResponseEntity<GenericVulnerabilityResponseBean<String>> overlayAttackNoProtection() {
return ResponseEntity.ok(new GenericVulnerabilityResponseBean<>(VULNERABLE_RESPONSE, true));
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Security-Policy", "frame-ancestors 'none'");
return ResponseEntity.ok()
.headers(headers)
.body(new GenericVulnerabilityResponseBean<>(PROTECTED_RESPONSE, true));
}

/**
Expand Down Expand Up @@ -209,9 +217,9 @@ public ResponseEntity<GenericVulnerabilityResponseBean<String>> overlayAttackNoP
htmlTemplate = "LEVEL_4/ClickjackingVulnerability")
public ResponseEntity<GenericVulnerabilityResponseBean<String>> overlayAttackSameOrigin() {
HttpHeaders headers = new HttpHeaders();
headers.add("X-Frame-Options", "SAMEORIGIN");
headers.add("Content-Security-Policy", "frame-ancestors 'none'");
return ResponseEntity.ok()
.headers(headers)
.body(new GenericVulnerabilityResponseBean<>(VULNERABLE_RESPONSE, true));
.body(new GenericVulnerabilityResponseBean<>(PROTECTED_RESPONSE, true));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ StringBuilder getResponseFromPingCommand(String ipAddress, boolean isValid) thro
@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);
Supplier<Boolean> validator =
() ->
StringUtils.isNotBlank(ipAddress)
&& (IP_ADDRESS_PATTERN.matcher(ipAddress).matches()
|| ipAddress.contentEquals("localhost"));
return new ResponseEntity<GenericVulnerabilityResponseBean<String>>(
new GenericVulnerabilityResponseBean<String>(
this.getResponseFromPingCommand(ipAddress, validator.get()).toString(),
Expand All @@ -87,9 +91,8 @@ public ResponseEntity<GenericVulnerabilityResponseBean<String>> getVulnerablePay
Supplier<Boolean> validator =
() ->
StringUtils.isNotBlank(ipAddress)
&& !SEMICOLON_SPACE_LOGICAL_AND_PATTERN
.matcher(requestEntity.getUrl().toString())
.find();
&& (IP_ADDRESS_PATTERN.matcher(ipAddress).matches()
|| ipAddress.contentEquals("localhost"));
return new ResponseEntity<GenericVulnerabilityResponseBean<String>>(
new GenericVulnerabilityResponseBean<String>(
this.getResponseFromPingCommand(ipAddress, validator.get()).toString(),
Expand All @@ -110,11 +113,8 @@ public ResponseEntity<GenericVulnerabilityResponseBean<String>> getVulnerablePay
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");
&& (IP_ADDRESS_PATTERN.matcher(ipAddress).matches()
|| ipAddress.contentEquals("localhost"));
return new ResponseEntity<GenericVulnerabilityResponseBean<String>>(
new GenericVulnerabilityResponseBean<String>(
this.getResponseFromPingCommand(ipAddress, validator.get()).toString(),
Expand All @@ -136,11 +136,8 @@ public ResponseEntity<GenericVulnerabilityResponseBean<String>> getVulnerablePay
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");
&& (IP_ADDRESS_PATTERN.matcher(ipAddress).matches()
|| ipAddress.contentEquals("localhost"));
return new ResponseEntity<GenericVulnerabilityResponseBean<String>>(
new GenericVulnerabilityResponseBean<String>(
this.getResponseFromPingCommand(ipAddress, validator.get()).toString(),
Expand All @@ -160,12 +157,8 @@ public ResponseEntity<GenericVulnerabilityResponseBean<String>> getVulnerablePay
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");
&& (IP_ADDRESS_PATTERN.matcher(ipAddress).matches()
|| ipAddress.contentEquals("localhost"));
return new ResponseEntity<GenericVulnerabilityResponseBean<String>>(
new GenericVulnerabilityResponseBean<String>(
this.getResponseFromPingCommand(ipAddress, validator.get()).toString(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,11 @@ public ResponseEntity<GenericVulnerabilityResponseBean<Object>> level1(
String actualToken = cookieToken;
try {
if (actualToken != null) {
idorLoginService.decodeToken(actualToken);
User decodedUser = idorLoginService.decodeToken(actualToken);
if (id != null) {
if (decodedUser.getUserId() != id) {
return response(ACCESS_DENIED_INSUFFICIENT, false);
}
User profile = fetchUserById(id);
if (profile == null) {
return response(USER_NOT_FOUND, false);
Expand Down Expand Up @@ -116,8 +119,8 @@ public ResponseEntity<GenericVulnerabilityResponseBean<Object>> level2(
String actualToken = cookieToken;
try {
if (actualToken != null && loggedInUser != null) {
idorLoginService.decodeToken(actualToken);
User profile = fetchUserById(loggedInUser);
User decodedUser = idorLoginService.decodeToken(actualToken);
User profile = fetchUserById(decodedUser.getUserId());
if (profile == null) {
return response(USER_NOT_FOUND, false);
}
Expand Down Expand Up @@ -155,7 +158,7 @@ public ResponseEntity<GenericVulnerabilityResponseBean<Object>> level3(
if (actualToken != null) {
User decodedUser = idorLoginService.decodeToken(actualToken);
int tokenUserId = decodedUser.getUserId();
String role = cookieRole != null ? cookieRole : decodedUser.getRole();
String role = decodedUser.getRole();

if (id == null) {
id = tokenUserId;
Expand Down Expand Up @@ -204,7 +207,7 @@ public ResponseEntity<GenericVulnerabilityResponseBean<Object>> level4(
if (actualToken != null) {
User decodedUser = idorLoginService.decodeToken(actualToken);
int tokenUserId = decodedUser.getUserId();
String role = cookieRole != null ? decodeBase64(cookieRole) : decodedUser.getRole();
String role = decodedUser.getRole();

if (id == null) {
id = tokenUserId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,9 @@ public ResponseEntity<GenericVulnerabilityResponseBean<Object>> level1(
return response("Provide username", false);
}

// Vulnerable LDAP filter
String ldapQuery = "(uid=" + username + ")";
// Encode LDAP filter values to prevent injection
String sanitizedInput = Filter.encodeValue(username);
String ldapQuery = "(uid=" + sanitizedInput + ")";

try {
List<String> users = searchUsers(ldapQuery);
Expand Down Expand Up @@ -140,8 +141,8 @@ public ResponseEntity<GenericVulnerabilityResponseBean<Object>> level2(
return response("Provide username", false);
}

// OR based LDAP query
String ldapQuery = "(|(uid=" + username + ")(mail=" + username + "))";
String sanitizedInput = Filter.encodeValue(username);
String ldapQuery = "(|(uid=" + sanitizedInput + ")(mail=" + sanitizedInput + "))";

try {
List<String> users = searchUsers(ldapQuery);
Expand Down Expand Up @@ -170,8 +171,8 @@ public ResponseEntity<GenericVulnerabilityResponseBean<Object>> level3(
return response("Provide username and password", false);
}

// Vulnerable authentication filter
String ldapQuery = "(&(uid=" + username + ")(uid=*))";
String sanitizedInput = Filter.encodeValue(username);
String ldapQuery = "(&(uid=" + sanitizedInput + "))";

try {
List<SearchResultEntry> users = searchEntries(ldapQuery);
Expand Down Expand Up @@ -264,7 +265,9 @@ public ResponseEntity<GenericVulnerabilityResponseBean<Object>> level5(
return response("Provide username and password", false);
}

String ldapQuery = "(&(uid=" + username + "))";
String sanitizedInput = Filter.encodeValue(username);

String ldapQuery = "(&(uid=" + sanitizedInput + "))";

try {
List<SearchResultEntry> users = searchEntries(ldapQuery);
Expand Down
Loading