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
@@ -1,6 +1,5 @@
package org.sasanlabs.service.vulnerability.idor;

import java.util.Base64;
import java.util.List;
import org.sasanlabs.internal.utility.LevelConstants;
import org.sasanlabs.internal.utility.Variant;
Expand Down Expand Up @@ -204,12 +203,16 @@ 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();

if (id == null) {
id = tokenUserId;
}

String role = fetchRoleById(tokenUserId);
if (role == null) {
return response(INVALID_USER, false);
}

if (ROLE_ADMIN.equalsIgnoreCase(role) || tokenUserId == id) {
User profile = fetchUserById(id);
if (profile == null) {
Expand Down Expand Up @@ -293,6 +296,13 @@ private User fetchUserById(int id) {
return users.get(0);
}

private String fetchRoleById(int id) {
List<String> roles =
jdbcTemplate.query(
SQL_ROLE_BY_ID, new Object[] {id}, (rs, rowNum) -> rs.getString("role"));
return roles.isEmpty() ? null : roles.get(0);
}

private List<User> fetchAllUsers() {
return jdbcTemplate.query(
SQL_ALL_PROFILES,
Expand All @@ -304,14 +314,6 @@ private List<User> fetchAllUsers() {
rs.getString("role")));
}

private String decodeBase64(String encodedId) {
try {
return new String(Base64.getUrlDecoder().decode(encodedId));
} catch (IllegalArgumentException e) {
return null;
}
}

private ResponseEntity<GenericVulnerabilityResponseBean<Object>> response(
Object content, boolean isValid) {
return new ResponseEntity<>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ void level3_ShouldAllowRoleEscalationWhenRoleCookieIsAdmin() {
}

@Test
void level4_ShouldAllowOpaqueIdAccess() {
void level4_ShouldRejectRoleEscalationThroughEncodedCookie() {
String encodedRole =
java.util.Base64.getUrlEncoder()
.withoutPadding()
Expand All @@ -112,19 +112,18 @@ void level4_ShouldAllowOpaqueIdAccess() {
User decoded = new User();
decoded.setUserId(1);
decoded.setRole("USER");
User bob = new User(2, "Bob", 60000, "USER");

when(idorLoginService.decodeToken(escalatedToken)).thenReturn(decoded);
when(jdbcTemplate.query(
anyString(),
eq(SQL_ROLE_BY_ID),
any(Object[].class),
any(org.springframework.jdbc.core.RowMapper.class)))
.thenReturn(Arrays.asList(bob));
.thenReturn(Arrays.asList("USER"));

ResponseEntity<GenericVulnerabilityResponseBean<Object>> response =
idor.level4(escalatedToken, encodedRole, 2);

assertTrue(response.getBody().getIsValid());
assertFalse(response.getBody().getIsValid());
assertEquals("Access Denied - Insufficient privileges", response.getBody().getContent());
}

@Test
Expand Down Expand Up @@ -173,31 +172,80 @@ void level3_ShouldRejectInvalidToken() {
}

@Test
void level4_ShouldAllowAccessToAnyOpaqueIdRegardlessOfRole() {
void level4_ShouldAllowDatabaseConfirmedAdminAccessToAnyId() {
String userToken =
java.util.Base64.getEncoder()
.encodeToString("{\"userId\":1,\"role\":\"USER\"}".getBytes());
String encodedRole =
java.util.Base64.getUrlEncoder()
.withoutPadding()
.encodeToString("ADMIN".getBytes());

User decoded = new User();
decoded.setUserId(1);
decoded.setRole("USER");
User bob = new User(2, "Bob", 60000, "USER");

when(idorLoginService.decodeToken(userToken)).thenReturn(decoded);
when(jdbcTemplate.query(
anyString(),
eq(SQL_ROLE_BY_ID),
any(Object[].class),
any(org.springframework.jdbc.core.RowMapper.class)))
.thenReturn(Arrays.asList("ADMIN"));
when(jdbcTemplate.query(
eq(SQL_PROFILE_BY_ID),
any(Object[].class),
any(org.springframework.jdbc.core.RowMapper.class)))
.thenReturn(Arrays.asList(bob));

ResponseEntity<GenericVulnerabilityResponseBean<Object>> response =
idor.level4(userToken, encodedRole, 2);
idor.level4(userToken, null, 2);

assertTrue(response.getBody().getIsValid());
assertEquals("ADMIN", ((User) response.getBody().getContent()).getRole());
}

@Test
void level4_ShouldAllowUserToAccessOwnProfile() {
String userToken = "valid-user-token";
User decoded = new User();
decoded.setUserId(1);
decoded.setRole("ADMIN");
User profile = new User(1, "Alice", 50000, "USER");

when(idorLoginService.decodeToken(userToken)).thenReturn(decoded);
when(jdbcTemplate.query(
eq(SQL_ROLE_BY_ID),
any(Object[].class),
any(org.springframework.jdbc.core.RowMapper.class)))
.thenReturn(Arrays.asList("USER"));
when(jdbcTemplate.query(
eq(SQL_PROFILE_BY_ID),
any(Object[].class),
any(org.springframework.jdbc.core.RowMapper.class)))
.thenReturn(Arrays.asList(profile));

ResponseEntity<GenericVulnerabilityResponseBean<Object>> response =
idor.level4(userToken, null, 1);

assertTrue(response.getBody().getIsValid());
assertEquals("USER", ((User) response.getBody().getContent()).getRole());
}

@Test
void level4_ShouldRejectTokenIdentityMissingFromDatabase() {
String userToken = "deleted-user-token";
User decoded = new User();
decoded.setUserId(99);
decoded.setRole("ADMIN");

when(idorLoginService.decodeToken(userToken)).thenReturn(decoded);
when(jdbcTemplate.query(
eq(SQL_ROLE_BY_ID),
any(Object[].class),
any(org.springframework.jdbc.core.RowMapper.class)))
.thenReturn(Arrays.asList());

ResponseEntity<GenericVulnerabilityResponseBean<Object>> response =
idor.level4(userToken, null, 1);

assertFalse(response.getBody().getIsValid());
assertEquals("Invalid user", response.getBody().getContent());
}

@Test
Expand Down
Loading