diff --git a/src/main/java/org/sasanlabs/service/vulnerability/sqlInjection/BlindSQLInjectionVulnerability.java b/src/main/java/org/sasanlabs/service/vulnerability/sqlInjection/BlindSQLInjectionVulnerability.java index 45bb82e4..39e1fa10 100644 --- a/src/main/java/org/sasanlabs/service/vulnerability/sqlInjection/BlindSQLInjectionVulnerability.java +++ b/src/main/java/org/sasanlabs/service/vulnerability/sqlInjection/BlindSQLInjectionVulnerability.java @@ -1,6 +1,8 @@ package org.sasanlabs.service.vulnerability.sqlInjection; import java.util.Map; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; import org.sasanlabs.internal.utility.LevelConstants; import org.sasanlabs.internal.utility.Variant; import org.sasanlabs.internal.utility.annotations.AttackVector; @@ -29,6 +31,7 @@ value = "BlindSQLInjectionVulnerability") public class BlindSQLInjectionVulnerability { + @PersistenceContext private EntityManager entityManager; private JdbcTemplate applicationJdbcTemplate; static final String CAR_IS_PRESENT_RESPONSE = "{ \"isCarPresent\": true}"; @@ -106,4 +109,50 @@ public ResponseEntity getCarInformationLevel3( ErrorBasedSQLInjectionVulnerability.CAR_IS_NOT_PRESENT_RESPONSE); }); } + + // Input Validation - Ensure that the input data is valid and of the expected type. + @VulnerableAppRequestMapping( + value = LevelConstants.LEVEL_4, + variant = Variant.SECURE, + htmlTemplate = "LEVEL_1/SQLInjection_Level1") + public ResponseEntity getCarInformationLevel4( + @RequestParam Map queryParams) { + String id = queryParams.get(Constants.ID); + + // Validate numeric ID + if (!id.matches("\\d+")) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Invalid ID format."); + } + + BodyBuilder bodyBuilder = ResponseEntity.status(HttpStatus.OK); + bodyBuilder.body(ErrorBasedSQLInjectionVulnerability.CAR_IS_NOT_PRESENT_RESPONSE); + return applicationJdbcTemplate.query( + "select * from cars where id=" + id, + (rs) -> { + if (rs.next()) { + return bodyBuilder.body(CAR_IS_PRESENT_RESPONSE); + } + return bodyBuilder.body( + ErrorBasedSQLInjectionVulnerability.CAR_IS_NOT_PRESENT_RESPONSE); + }); + } + + // Implementation Level 5 - Hibernate + @VulnerableAppRequestMapping( + value = LevelConstants.LEVEL_5, + variant = Variant.SECURE, + htmlTemplate = "LEVEL_1/SQLInjection_Level1") + public ResponseEntity getCarInformationLevel5( + @RequestParam Map queryParams) { + int id = Integer.parseInt(queryParams.get(Constants.ID)); + + CarInformation car = entityManager.find(CarInformation.class, id); + + if (car != null) { + return ResponseEntity.ok(CAR_IS_PRESENT_RESPONSE); + } else { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(ErrorBasedSQLInjectionVulnerability.CAR_IS_NOT_PRESENT_RESPONSE); + } + } } diff --git a/src/test/java/org/sasanlabs/service/vulnerability/sqlInjection/BlindSQLInjectionVulnerabilityTest.java b/src/test/java/org/sasanlabs/service/vulnerability/sqlInjection/BlindSQLInjectionVulnerabilityTest.java new file mode 100644 index 00000000..fc7759a1 --- /dev/null +++ b/src/test/java/org/sasanlabs/service/vulnerability/sqlInjection/BlindSQLInjectionVulnerabilityTest.java @@ -0,0 +1,205 @@ +package org.sasanlabs.service.vulnerability.sqlInjection; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.PreparedStatementCreator; +import org.springframework.jdbc.core.ResultSetExtractor; + +public class BlindSQLInjectionVulnerabilityTest { + + @Mock private JdbcTemplate jdbcTemplate; + + @InjectMocks private BlindSQLInjectionVulnerability blindSQLInjectionVulnerability; + + @BeforeEach + public void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + public void testGetCarInformationLevel1_CarPresent() throws SQLException { + // Arrange + String id = "1"; + Map queryParams = new HashMap<>(); + queryParams.put("id", id); + + // The query is simulated to have returned a result (i.e. there is a car with ID "1") + ResultSet mockResultSet = mock(ResultSet.class); + when(mockResultSet.next()).thenReturn(true); + + // return rse.extractData(mockResultSet); indicates that the ResultSetExtractor extracts the + // data from the mockResultSet (which mocks the query result) + when(jdbcTemplate.query(anyString(), any(ResultSetExtractor.class))) + .thenAnswer( + invocation -> { + ResultSetExtractor> rse = invocation.getArgument(1); + return rse.extractData(mockResultSet); + }); + + // Act + ResponseEntity response = + blindSQLInjectionVulnerability.getCarInformationLevel1(queryParams); + + // Assert + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals("{ \"isCarPresent\": true}", response.getBody()); + } + + @Test + public void testGetCarInformationLevel1_CarNotPresent() throws SQLException { + // Arrange + String id = "2"; + Map queryParams = new HashMap<>(); + queryParams.put("id", id); + + // The query is simulated to have returned a result (i.e. there is no a car with ID "2") + ResultSet mockResultSet = mock(ResultSet.class); + when(mockResultSet.next()).thenReturn(false); + + // return rse.extractData(mockResultSet); indicates that the ResultSetExtractor extracts the + // data from the mockResultSet (which mocks the query result) + when(jdbcTemplate.query(anyString(), any(ResultSetExtractor.class))) + .thenAnswer( + invocation -> { + ResultSetExtractor> rse = invocation.getArgument(1); + return rse.extractData(mockResultSet); + }); + + // Act + ResponseEntity response = + blindSQLInjectionVulnerability.getCarInformationLevel1(queryParams); + + // Assert + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals( + ErrorBasedSQLInjectionVulnerability.CAR_IS_NOT_PRESENT_RESPONSE, response.getBody()); + } + + @Test + public void testGetCarInformationLevel2_CarPresent() throws SQLException { + // Arrange + String id = "1"; + Map queryParams = new HashMap<>(); + queryParams.put("id", id); + + // Mock the ResultSet behavior + ResultSet mockResultSet = mock(ResultSet.class); + when(mockResultSet.next()).thenReturn(true); + + // Mock the query method of JdbcTemplate + when(jdbcTemplate.query(anyString(), any(ResultSetExtractor.class))) + .thenAnswer( + invocation -> { + ResultSetExtractor> rse = invocation.getArgument(1); + return rse.extractData(mockResultSet); + }); + + // Act + ResponseEntity response = + blindSQLInjectionVulnerability.getCarInformationLevel2(queryParams); + + // Assert + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals("{ \"isCarPresent\": true}", response.getBody()); + } + + @Test + public void testGetCarInformationLevel2_CarNotPresent() throws SQLException { + // Arrange + String id = "2"; + Map queryParams = new HashMap<>(); + queryParams.put("id", id); + + // Mock the ResultSet behavior + ResultSet mockResultSet = mock(ResultSet.class); + when(mockResultSet.next()).thenReturn(false); + + // Mock the query method of JdbcTemplate + when(jdbcTemplate.query(anyString(), any(ResultSetExtractor.class))) + .thenAnswer( + invocation -> { + ResultSetExtractor> rse = invocation.getArgument(1); + return rse.extractData(mockResultSet); + }); + + // Act + ResponseEntity response = + blindSQLInjectionVulnerability.getCarInformationLevel2(queryParams); + + // Assert + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals( + ErrorBasedSQLInjectionVulnerability.CAR_IS_NOT_PRESENT_RESPONSE, response.getBody()); + } + + @Test + public void testGetCarInformationLevel3_CarPresent() throws SQLException { + // Arrange + String id = "1"; + Map queryParams = new HashMap<>(); + queryParams.put("id", id); + + // Mock the ResultSet behavior + ResultSet mockResultSet = mock(ResultSet.class); + when(mockResultSet.next()).thenReturn(true); + + // Mock the query method of JdbcTemplate + when(jdbcTemplate.query((PreparedStatementCreator) any(), any(), any(ResultSetExtractor.class))) + .thenAnswer( + invocation -> { + ResultSetExtractor> rse = invocation.getArgument(2); + return rse.extractData(mockResultSet); + }); + + // Act + ResponseEntity response = + blindSQLInjectionVulnerability.getCarInformationLevel3(queryParams); + + // Assert + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals("{ \"isCarPresent\": true}", response.getBody()); + } + + @Test + public void testGetCarInformationLevel3_CarNotPresent() throws SQLException { + // Arrange + String id = "2"; + Map queryParams = new HashMap<>(); + queryParams.put("id", id); + + // Mock the ResultSet behavior + ResultSet mockResultSet = mock(ResultSet.class); + when(mockResultSet.next()).thenReturn(false); + + // Mock the query method of JdbcTemplate + when(jdbcTemplate.query((PreparedStatementCreator) any(), any(), any(ResultSetExtractor.class))) + .thenAnswer( + invocation -> { + ResultSetExtractor> rse = invocation.getArgument(2); + return rse.extractData(mockResultSet); + }); + + // Act + ResponseEntity response = + blindSQLInjectionVulnerability.getCarInformationLevel3(queryParams); + + // Assert + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals( + ErrorBasedSQLInjectionVulnerability.CAR_IS_NOT_PRESENT_RESPONSE, response.getBody()); + } +} +