From 6104b9c2cc9620e974ee5fe11fe13d1492e47afe Mon Sep 17 00:00:00 2001 From: Omur Date: Sun, 16 Nov 2025 14:05:41 +0300 Subject: [PATCH 01/13] sqli starter application --- .../spring/spring-rest-openapi-v3/pom.xml | 10 +- .../v3/security/sqli/SQLiApplication.kt | 96 +++++++++++++++++++ .../openapi/v3/security/sqli/UserDto.kt | 12 +++ .../openapi/v3/security/sqli/UserEntity.kt | 19 ++++ .../v3/security/sqli/UserRepository.kt | 10 ++ .../main/resources/application-sqli-h2.yml | 15 +++ .../main/resources/application-sqli-mysql.yml | 11 +++ .../resources/application-sqli-postgres.yml | 11 +++ 8 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiApplication.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/UserDto.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/UserEntity.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/UserRepository.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/resources/application-sqli-h2.yml create mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/resources/application-sqli-mysql.yml create mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/resources/application-sqli-postgres.yml diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/pom.xml b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/pom.xml index 30f48eb10e..aa43017847 100644 --- a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/pom.xml +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/pom.xml @@ -150,6 +150,14 @@ itu + + org.postgresql + postgresql + + + mysql + mysql-connector-java + @@ -165,4 +173,4 @@ - \ No newline at end of file + diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiApplication.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiApplication.kt new file mode 100644 index 0000000000..b89b42fe0f --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiApplication.kt @@ -0,0 +1,96 @@ +package com.foo.rest.examples.spring.openapi.v3.security.sqli + +import io.swagger.v3.oas.annotations.Operation +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.SpringApplication +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import java.sql.Connection +import javax.annotation.PostConstruct +import javax.sql.DataSource + + +@SpringBootApplication(exclude = [SecurityAutoConfiguration::class]) +@RequestMapping(path = ["/api/sqli"]) +@RestController +open class SQLiApplication { + + @Autowired + private lateinit var dataSource: DataSource + + @Autowired + private lateinit var userRepository: UserRepository + + private var connection: Connection? = null + + companion object { + @JvmStatic + fun main(args: Array) { + SpringApplication.run(SQLiApplication::class.java, *args) + } + } + + @PostConstruct + fun init() { + connection = dataSource.connection + initializeTestData() + } + + private fun initializeTestData() { + if (userRepository.count() == 0L) { + userRepository.save(UserEntity(null,"admin", "admin123")) + userRepository.save(UserEntity(null,"user1","password1")) + } + } + + /** + * Attack: GET /api/sqli/query?username=admin' OR (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B, INFORMATION_SCHEMA.COLUMNS C)>0-- + */ + @GetMapping("/query") + @Operation(summary = "SQL Injection - Query Parameter") + fun timeBasedQuery(@RequestParam username: String): ResponseEntity { + return try { + val stmt = connection?.createStatement() + val rs = stmt?.executeQuery("SELECT COUNT(*) as cnt FROM users WHERE username = '$username'") + val count = if (rs?.next() == true) rs.getInt("cnt") else 0 + ResponseEntity.ok("COUNT: $count") + } catch (e: Exception) { + ResponseEntity.status(500).body("ERROR: ${e.message}") + } + } + + /** + * Attack: GET /api/sqli/path/admin' OR (SELECT SUM(a.ORDINAL_POSITION*b.ORDINAL_POSITION*c.ORDINAL_POSITION) FROM INFORMATION_SCHEMA.COLUMNS a, INFORMATION_SCHEMA.COLUMNS b, INFORMATION_SCHEMA.COLUMNS c)>1 -- + */ + @GetMapping("/path/{id}") + @Operation(summary = "SQL Injection - Path Parameter") + fun timeBasedPath(@PathVariable id: String): ResponseEntity { + return try { + val stmt = connection?.createStatement() + val rs = stmt?.executeQuery("SELECT username FROM users WHERE username = '$id'") + val username = if (rs?.next() == true) rs.getString("username") else "NOT_FOUND" + ResponseEntity.ok("USERNAME: $username") + } catch (e: Exception) { + ResponseEntity.status(500).body("ERROR: ${e.message}") + } + } + + /** + * Attack: POST {"username":"admin' AND (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B, INFORMATION_SCHEMA.COLUMNS C)>0--","password":"x"} + */ + @PostMapping("/body") + @Operation(summary = "SQL Injection - Body Parameter") + fun body(@RequestBody loginDto: LoginDto): ResponseEntity { + return try { + val stmt = connection?.createStatement() + val rs = stmt?.executeQuery("SELECT COUNT(*) as cnt FROM users WHERE username = '${loginDto.username}' AND password = '${loginDto.password}'") + val count = if (rs?.next() == true) rs.getInt("cnt") else 0 + ResponseEntity.ok("MATCHED: $count") + } catch (e: Exception) { + ResponseEntity.status(500).body("ERROR: ${e.message}") + } + } + +} diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/UserDto.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/UserDto.kt new file mode 100644 index 0000000000..98277c971c --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/UserDto.kt @@ -0,0 +1,12 @@ +package com.foo.rest.examples.spring.openapi.v3.security.sqli + + +data class UserDto( + var id: Long? = null, + var username: String? = null, +) + +data class LoginDto( + var username: String, + var password: String +) diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/UserEntity.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/UserEntity.kt new file mode 100644 index 0000000000..9b905bb273 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/UserEntity.kt @@ -0,0 +1,19 @@ +package com.foo.rest.examples.spring.openapi.v3.security.sqli + +import javax.persistence.* + + +@Entity +@Table(name = "users") +open class UserEntity( + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + open var id: Long? = null, + + @Column(name = "username", unique = true, nullable = false) + open var username: String? = null, + + @Column(name = "password", nullable = false) + open var password: String? = null, +) diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/UserRepository.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/UserRepository.kt new file mode 100644 index 0000000000..7bc7a816cf --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/UserRepository.kt @@ -0,0 +1,10 @@ +package com.foo.rest.examples.spring.openapi.v3.security.sqli + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + + +@Repository +interface UserRepository : JpaRepository { + +} diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/resources/application-sqli-h2.yml b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/resources/application-sqli-h2.yml new file mode 100644 index 0000000000..0ff4fc11ed --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/resources/application-sqli-h2.yml @@ -0,0 +1,15 @@ +spring: + datasource: + url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE + driver-class-name: org.h2.Driver + username: sa + password: + jpa: + database-platform: org.hibernate.dialect.H2Dialect + hibernate: + ddl-auto: create-drop + show-sql: true + h2: + console: + enabled: true + path: /h2-console diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/resources/application-sqli-mysql.yml b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/resources/application-sqli-mysql.yml new file mode 100644 index 0000000000..bab993d80f --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/resources/application-sqli-mysql.yml @@ -0,0 +1,11 @@ +spring: + datasource: + url: jdbc:mysql://localhost:3306/sqli_test?createDatabaseIfNotExist=true&useSSL=false&allowPublicKeyRetrieval=true + driver-class-name: com.mysql.cj.jdbc.Driver + username: root + password: root + jpa: + database-platform: org.hibernate.dialect.MySQL8Dialect + hibernate: + ddl-auto: create-drop + show-sql: true diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/resources/application-sqli-postgres.yml b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/resources/application-sqli-postgres.yml new file mode 100644 index 0000000000..6d40921cb5 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/resources/application-sqli-postgres.yml @@ -0,0 +1,11 @@ +spring: + datasource: + url: jdbc:postgresql://localhost:5432/sqli_test + driver-class-name: org.postgresql.Driver + username: postgres + password: postgres + jpa: + database-platform: org.hibernate.dialect.PostgreSQLDialect + hibernate: + ddl-auto: create-drop + show-sql: true From 3852b06e6366e56b417f3374b276415913d0949e Mon Sep 17 00:00:00 2001 From: Omur Sahin Date: Sun, 16 Nov 2025 21:31:46 +0300 Subject: [PATCH 02/13] sqli payload --- .../v3/security/sqli/SQLiApplication.kt | 96 ----------------- .../security/sqli/body/BodySQLiApplication.kt | 84 +++++++++++++++ .../v3/security/sqli/{ => common}/UserDto.kt | 2 +- .../security/sqli/{ => common}/UserEntity.kt | 2 +- .../sqli/{ => common}/UserRepository.kt | 2 +- .../security/sqli/path/PathSQLiApplication.kt | 83 ++++++++++++++ .../sqli/query/QuerySQLiApplication.kt | 83 ++++++++++++++ .../v3/security/sqli/SQLiH2BodyController.kt | 5 + .../v3/security/sqli/SQLiH2PathController.kt | 5 + .../v3/security/sqli/SQLiH2QueryController.kt | 5 + .../v3/security/sqli/SQLiQueryH2EMTest.kt | 61 +++++++++++ .../kotlin/org/evomaster/core/EMConfig.kt | 4 + .../problem/rest/oracle/RestSecurityOracle.kt | 10 ++ .../core/problem/rest/service/SecurityRest.kt | 101 ++++++++++++++++++ .../service/fitness/AbstractRestFitness.kt | 34 ++++++ docs/options.md | 1 + 16 files changed, 479 insertions(+), 99 deletions(-) delete mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiApplication.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/body/BodySQLiApplication.kt rename core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/{ => common}/UserDto.kt (69%) rename core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/{ => common}/UserEntity.kt (84%) rename core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/{ => common}/UserRepository.kt (73%) create mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/path/PathSQLiApplication.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/query/QuerySQLiApplication.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiH2BodyController.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiH2PathController.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiH2QueryController.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiQueryH2EMTest.kt diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiApplication.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiApplication.kt deleted file mode 100644 index b89b42fe0f..0000000000 --- a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiApplication.kt +++ /dev/null @@ -1,96 +0,0 @@ -package com.foo.rest.examples.spring.openapi.v3.security.sqli - -import io.swagger.v3.oas.annotations.Operation -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.SpringApplication -import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.* -import java.sql.Connection -import javax.annotation.PostConstruct -import javax.sql.DataSource - - -@SpringBootApplication(exclude = [SecurityAutoConfiguration::class]) -@RequestMapping(path = ["/api/sqli"]) -@RestController -open class SQLiApplication { - - @Autowired - private lateinit var dataSource: DataSource - - @Autowired - private lateinit var userRepository: UserRepository - - private var connection: Connection? = null - - companion object { - @JvmStatic - fun main(args: Array) { - SpringApplication.run(SQLiApplication::class.java, *args) - } - } - - @PostConstruct - fun init() { - connection = dataSource.connection - initializeTestData() - } - - private fun initializeTestData() { - if (userRepository.count() == 0L) { - userRepository.save(UserEntity(null,"admin", "admin123")) - userRepository.save(UserEntity(null,"user1","password1")) - } - } - - /** - * Attack: GET /api/sqli/query?username=admin' OR (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B, INFORMATION_SCHEMA.COLUMNS C)>0-- - */ - @GetMapping("/query") - @Operation(summary = "SQL Injection - Query Parameter") - fun timeBasedQuery(@RequestParam username: String): ResponseEntity { - return try { - val stmt = connection?.createStatement() - val rs = stmt?.executeQuery("SELECT COUNT(*) as cnt FROM users WHERE username = '$username'") - val count = if (rs?.next() == true) rs.getInt("cnt") else 0 - ResponseEntity.ok("COUNT: $count") - } catch (e: Exception) { - ResponseEntity.status(500).body("ERROR: ${e.message}") - } - } - - /** - * Attack: GET /api/sqli/path/admin' OR (SELECT SUM(a.ORDINAL_POSITION*b.ORDINAL_POSITION*c.ORDINAL_POSITION) FROM INFORMATION_SCHEMA.COLUMNS a, INFORMATION_SCHEMA.COLUMNS b, INFORMATION_SCHEMA.COLUMNS c)>1 -- - */ - @GetMapping("/path/{id}") - @Operation(summary = "SQL Injection - Path Parameter") - fun timeBasedPath(@PathVariable id: String): ResponseEntity { - return try { - val stmt = connection?.createStatement() - val rs = stmt?.executeQuery("SELECT username FROM users WHERE username = '$id'") - val username = if (rs?.next() == true) rs.getString("username") else "NOT_FOUND" - ResponseEntity.ok("USERNAME: $username") - } catch (e: Exception) { - ResponseEntity.status(500).body("ERROR: ${e.message}") - } - } - - /** - * Attack: POST {"username":"admin' AND (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B, INFORMATION_SCHEMA.COLUMNS C)>0--","password":"x"} - */ - @PostMapping("/body") - @Operation(summary = "SQL Injection - Body Parameter") - fun body(@RequestBody loginDto: LoginDto): ResponseEntity { - return try { - val stmt = connection?.createStatement() - val rs = stmt?.executeQuery("SELECT COUNT(*) as cnt FROM users WHERE username = '${loginDto.username}' AND password = '${loginDto.password}'") - val count = if (rs?.next() == true) rs.getInt("cnt") else 0 - ResponseEntity.ok("MATCHED: $count") - } catch (e: Exception) { - ResponseEntity.status(500).body("ERROR: ${e.message}") - } - } - -} diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/body/BodySQLiApplication.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/body/BodySQLiApplication.kt new file mode 100644 index 0000000000..a017eb4ffa --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/body/BodySQLiApplication.kt @@ -0,0 +1,84 @@ +package com.foo.rest.examples.spring.openapi.v3.security.sqli.body + +import com.foo.rest.examples.spring.openapi.v3.security.sqli.common.LoginDto +import com.foo.rest.examples.spring.openapi.v3.security.sqli.common.UserDto +import com.foo.rest.examples.spring.openapi.v3.security.sqli.common.UserEntity +import com.foo.rest.examples.spring.openapi.v3.security.sqli.common.UserRepository +import io.swagger.v3.oas.annotations.Operation +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.SpringApplication +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.autoconfigure.domain.EntityScan +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration +import org.springframework.context.annotation.ComponentScan +import org.springframework.data.jpa.repository.config.EnableJpaRepositories +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import java.sql.Connection +import javax.annotation.PostConstruct +import javax.sql.DataSource + + +@SpringBootApplication(exclude = [SecurityAutoConfiguration::class]) +@RequestMapping(path = ["/api/sqli/body"]) +@RestController +@ComponentScan(basePackages = ["com.foo.rest.examples.spring.openapi.v3.security.sqli.common", "com.foo.rest.examples.spring.openapi.v3.security.sqli.body"]) +@EnableJpaRepositories(basePackages = ["com.foo.rest.examples.spring.openapi.v3.security.sqli.common"]) +@EntityScan(basePackages = ["com.foo.rest.examples.spring.openapi.v3.security.sqli.common"]) +open class BodySQLiApplication { + + @Autowired + private lateinit var dataSource: DataSource + + @Autowired + private lateinit var userRepository: UserRepository + + private var connection: Connection? = null + + companion object { + @JvmStatic + fun main(args: Array) { + SpringApplication.run(BodySQLiApplication::class.java, *args) + } + } + + @PostConstruct + fun init() { + connection = dataSource.connection + initializeTestData() + } + + private fun initializeTestData() { + if (userRepository.count() == 0L) { + userRepository.save(UserEntity(null, "admin", "admin123")) + userRepository.save(UserEntity(null, "user1", "password1")) + } + } + + /** + * Safe endpoint - No SQL Injection vulnerability + */ + @GetMapping("/safe") + @Operation(summary = "Safe Query - No SQL Injection") + fun getSafeUsers(): ResponseEntity> { + val users = userRepository.findAll().map { UserDto(it.id, it.username) } + return ResponseEntity.ok(users) + } + + /** + * Attack: POST {"username":"admin' AND (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B, INFORMATION_SCHEMA.COLUMNS C)>0--","password":"x"} + */ + @PostMapping("/vulnerable") + @Operation(summary = "SQL Injection - Body Parameter") + fun body(@RequestBody loginDto: LoginDto): ResponseEntity { + return try { + val stmt = connection?.createStatement() + val rs = stmt?.executeQuery("SELECT COUNT(*) as cnt FROM users WHERE username = '${loginDto.username}' AND password = '${loginDto.password}'") + val count = if (rs?.next() == true) rs.getInt("cnt") else 0 + ResponseEntity.ok("MATCHED: $count") + } catch (e: Exception) { + ResponseEntity.status(500).body("ERROR: ${e.message}") + } + } + +} diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/UserDto.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/common/UserDto.kt similarity index 69% rename from core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/UserDto.kt rename to core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/common/UserDto.kt index 98277c971c..784796e301 100644 --- a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/UserDto.kt +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/common/UserDto.kt @@ -1,4 +1,4 @@ -package com.foo.rest.examples.spring.openapi.v3.security.sqli +package com.foo.rest.examples.spring.openapi.v3.security.sqli.common data class UserDto( diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/UserEntity.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/common/UserEntity.kt similarity index 84% rename from core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/UserEntity.kt rename to core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/common/UserEntity.kt index 9b905bb273..91c903db62 100644 --- a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/UserEntity.kt +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/common/UserEntity.kt @@ -1,4 +1,4 @@ -package com.foo.rest.examples.spring.openapi.v3.security.sqli +package com.foo.rest.examples.spring.openapi.v3.security.sqli.common import javax.persistence.* diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/UserRepository.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/common/UserRepository.kt similarity index 73% rename from core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/UserRepository.kt rename to core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/common/UserRepository.kt index 7bc7a816cf..d3da6d311e 100644 --- a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/UserRepository.kt +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/common/UserRepository.kt @@ -1,4 +1,4 @@ -package com.foo.rest.examples.spring.openapi.v3.security.sqli +package com.foo.rest.examples.spring.openapi.v3.security.sqli.common import org.springframework.data.jpa.repository.JpaRepository import org.springframework.stereotype.Repository diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/path/PathSQLiApplication.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/path/PathSQLiApplication.kt new file mode 100644 index 0000000000..79831d1d81 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/path/PathSQLiApplication.kt @@ -0,0 +1,83 @@ +package com.foo.rest.examples.spring.openapi.v3.security.sqli.path + +import com.foo.rest.examples.spring.openapi.v3.security.sqli.common.UserDto +import com.foo.rest.examples.spring.openapi.v3.security.sqli.common.UserEntity +import com.foo.rest.examples.spring.openapi.v3.security.sqli.common.UserRepository +import io.swagger.v3.oas.annotations.Operation +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.SpringApplication +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.autoconfigure.domain.EntityScan +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration +import org.springframework.context.annotation.ComponentScan +import org.springframework.data.jpa.repository.config.EnableJpaRepositories +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import java.sql.Connection +import javax.annotation.PostConstruct +import javax.sql.DataSource + + +@SpringBootApplication(exclude = [SecurityAutoConfiguration::class]) +@RequestMapping(path = ["/api/sqli/path"]) +@RestController +@ComponentScan(basePackages = ["com.foo.rest.examples.spring.openapi.v3.security.sqli.common", "com.foo.rest.examples.spring.openapi.v3.security.sqli.path"]) +@EnableJpaRepositories(basePackages = ["com.foo.rest.examples.spring.openapi.v3.security.sqli.common"]) +@EntityScan(basePackages = ["com.foo.rest.examples.spring.openapi.v3.security.sqli.common"]) +open class PathSQLiApplication { + + @Autowired + private lateinit var dataSource: DataSource + + @Autowired + private lateinit var userRepository: UserRepository + + private var connection: Connection? = null + + companion object { + @JvmStatic + fun main(args: Array) { + SpringApplication.run(PathSQLiApplication::class.java, *args) + } + } + + @PostConstruct + fun init() { + connection = dataSource.connection + initializeTestData() + } + + private fun initializeTestData() { + if (userRepository.count() == 0L) { + userRepository.save(UserEntity(null, "admin", "admin123")) + userRepository.save(UserEntity(null, "user1", "password1")) + } + } + + /** + * Safe endpoint - No SQL Injection vulnerability + */ + @GetMapping("/safe") + @Operation(summary = "Safe Query - No SQL Injection") + fun getSafeUsers(): ResponseEntity> { + val users = userRepository.findAll().map { UserDto(it.id, it.username) } + return ResponseEntity.ok(users) + } + + /** + * Attack: GET /api/sqli/path/vulnerable/admin' OR (SELECT SUM(a.ORDINAL_POSITION*b.ORDINAL_POSITION*c.ORDINAL_POSITION) FROM INFORMATION_SCHEMA.COLUMNS a, INFORMATION_SCHEMA.COLUMNS b, INFORMATION_SCHEMA.COLUMNS c)>1 -- + */ + @GetMapping("/vulnerable/{id}") + @Operation(summary = "SQL Injection - Path Parameter") + fun timeBasedPath(@PathVariable id: String): ResponseEntity { + return try { + val stmt = connection?.createStatement() + val rs = stmt?.executeQuery("SELECT username FROM users WHERE username = '$id'") + val username = if (rs?.next() == true) rs.getString("username") else "NOT_FOUND" + ResponseEntity.ok("USERNAME: $username") + } catch (e: Exception) { + ResponseEntity.status(500).body("ERROR: ${e.message}") + } + } + +} diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/query/QuerySQLiApplication.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/query/QuerySQLiApplication.kt new file mode 100644 index 0000000000..07d2b15696 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/query/QuerySQLiApplication.kt @@ -0,0 +1,83 @@ +package com.foo.rest.examples.spring.openapi.v3.security.sqli.query + +import com.foo.rest.examples.spring.openapi.v3.security.sqli.common.UserDto +import com.foo.rest.examples.spring.openapi.v3.security.sqli.common.UserEntity +import com.foo.rest.examples.spring.openapi.v3.security.sqli.common.UserRepository +import io.swagger.v3.oas.annotations.Operation +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.SpringApplication +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.autoconfigure.domain.EntityScan +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration +import org.springframework.context.annotation.ComponentScan +import org.springframework.data.jpa.repository.config.EnableJpaRepositories +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import java.sql.Connection +import javax.annotation.PostConstruct +import javax.sql.DataSource + + +@SpringBootApplication(exclude = [SecurityAutoConfiguration::class]) +@RequestMapping(path = ["/api/sqli/query"]) +@RestController +@ComponentScan(basePackages = ["com.foo.rest.examples.spring.openapi.v3.security.sqli.common", "com.foo.rest.examples.spring.openapi.v3.security.sqli.query"]) +@EnableJpaRepositories(basePackages = ["com.foo.rest.examples.spring.openapi.v3.security.sqli.common"]) +@EntityScan(basePackages = ["com.foo.rest.examples.spring.openapi.v3.security.sqli.common"]) +open class QuerySQLiApplication { + + @Autowired + private lateinit var dataSource: DataSource + + @Autowired + private lateinit var userRepository: UserRepository + + private var connection: Connection? = null + + companion object { + @JvmStatic + fun main(args: Array) { + SpringApplication.run(QuerySQLiApplication::class.java, *args) + } + } + + @PostConstruct + fun init() { + connection = dataSource.connection + initializeTestData() + } + + private fun initializeTestData() { + if (userRepository.count() == 0L) { + userRepository.save(UserEntity(null, "admin", "admin123")) + userRepository.save(UserEntity(null, "user1", "password1")) + } + } + + /** + * Safe endpoint - No SQL Injection vulnerability + */ + @GetMapping("/safe") + @Operation(summary = "Safe Query - No SQL Injection") + fun getSafeUsers(): ResponseEntity> { + val users = userRepository.findAll().map { UserDto(it.id, it.username) } + return ResponseEntity.ok(users) + } + + /** + * Attack: GET /api/sqli/query/vulnerable?username=admin' OR (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B, INFORMATION_SCHEMA.COLUMNS C)>0-- + */ + @GetMapping("/vulnerable") + @Operation(summary = "SQL Injection - Query Parameter") + fun timeBasedQuery(@RequestParam username: String): ResponseEntity { + return try { + val stmt = connection?.createStatement() + val rs = stmt?.executeQuery("SELECT COUNT(*) as cnt FROM users WHERE username = '$username'") + val count = if (rs?.next() == true) rs.getInt("cnt") else 0 + ResponseEntity.ok("COUNT: $count") + } catch (e: Exception) { + ResponseEntity.status(500).body("ERROR: ${e.message}") + } + } + +} diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiH2BodyController.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiH2BodyController.kt new file mode 100644 index 0000000000..c9254a4ff3 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiH2BodyController.kt @@ -0,0 +1,5 @@ +package com.foo.rest.examples.spring.openapi.v3.security.sqli +import com.foo.rest.examples.spring.openapi.v3.SpringController +import com.foo.rest.examples.spring.openapi.v3.security.sqli.body.BodySQLiApplication + +class SQLiH2BodyController : SpringController(BodySQLiApplication::class.java) \ No newline at end of file diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiH2PathController.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiH2PathController.kt new file mode 100644 index 0000000000..384d9e1f1a --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiH2PathController.kt @@ -0,0 +1,5 @@ +package com.foo.rest.examples.spring.openapi.v3.security.sqli +import com.foo.rest.examples.spring.openapi.v3.SpringController +import com.foo.rest.examples.spring.openapi.v3.security.sqli.path.PathSQLiApplication + +class SQLiH2PathController : SpringController(PathSQLiApplication::class.java) \ No newline at end of file diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiH2QueryController.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiH2QueryController.kt new file mode 100644 index 0000000000..0292ca4af9 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiH2QueryController.kt @@ -0,0 +1,5 @@ +package com.foo.rest.examples.spring.openapi.v3.security.sqli +import com.foo.rest.examples.spring.openapi.v3.SpringController +import com.foo.rest.examples.spring.openapi.v3.security.sqli.query.QuerySQLiApplication + +class SQLiH2QueryController : SpringController(QuerySQLiApplication::class.java) \ No newline at end of file diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiQueryH2EMTest.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiQueryH2EMTest.kt new file mode 100644 index 0000000000..ba2b7a6448 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiQueryH2EMTest.kt @@ -0,0 +1,61 @@ +package org.evomaster.e2etests.spring.openapi.v3.security.sqli + + +import com.foo.rest.examples.spring.openapi.v3.security.sqli.SQLiH2QueryController +import com.webfuzzing.commons.faults.DefinedFaultCategory +import org.evomaster.core.EMConfig +import org.evomaster.core.problem.enterprise.DetectedFaultUtils +import org.evomaster.e2etests.spring.openapi.v3.SpringTestBase +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test + +class SQLiQueryH2EMTest : SpringTestBase() { + + companion object { + @BeforeAll + @JvmStatic + fun init() { + val config = EMConfig() + config.instrumentMR_NET = false + config.instrumentMR_SQL = false + + initClass(SQLiH2QueryController(), config) + } + } + + @Test + fun testSQLiH2EM() { + runTestHandlingFlakyAndCompilation( + "SQLiH2EMTest", + 50, + ) { args: MutableList -> + + setOption(args, "security", "true") + + + val solution = initAndRun(args) + + Assertions.assertTrue(solution.individuals.isNotEmpty()) + + val faults = DetectedFaultUtils.getDetectedFaults(solution) + + Assertions.assertTrue(faults.size == 1) + + val faultCategories = DetectedFaultUtils.getDetectedFaultCategories(solution) + + Assertions.assertTrue({ DefinedFaultCategory.SQL_INJECTION in faultCategories }) + + Assertions.assertTrue(faults.any { + it.category == DefinedFaultCategory.SQL_INJECTION + && it.operationId == "GET:/api/sqli/query/vulnerable" + }) + + Assertions.assertFalse(faults.any { + it.category == DefinedFaultCategory.SQL_INJECTION + && it.operationId == "GET:/api/sqli/query/safe" + }) + + } + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/evomaster/core/EMConfig.kt b/core/src/main/kotlin/org/evomaster/core/EMConfig.kt index 1511cb2dce..82dd68af2b 100644 --- a/core/src/main/kotlin/org/evomaster/core/EMConfig.kt +++ b/core/src/main/kotlin/org/evomaster/core/EMConfig.kt @@ -2576,6 +2576,10 @@ class EMConfig { @Cfg("To apply SSRF detection as part of security testing.") var ssrf = false + @Experimental + @Cfg("Maximum response time (in milliseconds) to consider a potential SQL Injection vulnerability.") + var sqlInjectionMaxResponseTimeMs = 2000 + @Regex(faultCodeRegex) @Cfg("Disable oracles. Provide a comma-separated list of codes to disable. " + "By default, all oracles are enabled." diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt index 4987fd0055..2a847bfb81 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt @@ -222,4 +222,14 @@ object RestSecurityOracle { return true } + // Simple SQLi payloads + val SQLI_PAYLOADS = listOf( +// " '' OR (WITH RECURSIVE r(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM r WHERE i < 100000000) SELECT COUNT(*) FROM r)>0--", + // for h2 database +// "' OR (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B, INFORMATION_SCHEMA.COLUMNS C)>0--", + "' OR (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B)>0--", +// it takes extremely long. +// "' OR (SELECT SUM(a.ORDINAL_POSITION*b.ORDINAL_POSITION*c.ORDINAL_POSITION) FROM INFORMATION_SCHEMA.COLUMNS a, INFORMATION_SCHEMA.COLUMNS b, INFORMATION_SCHEMA.COLUMNS c)>1 --", + ) + } \ No newline at end of file diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/SecurityRest.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/SecurityRest.kt index 4473379de7..42c87a9cc1 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/SecurityRest.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/SecurityRest.kt @@ -7,6 +7,7 @@ import javax.annotation.PostConstruct import org.evomaster.core.logging.LoggingUtil import org.evomaster.core.problem.enterprise.DetectedFault +import org.evomaster.core.problem.enterprise.DetectedFaultUtils import org.evomaster.core.problem.enterprise.ExperimentalFaultCategory import org.evomaster.core.problem.enterprise.SampleType import org.evomaster.core.problem.enterprise.auth.AuthSettings @@ -18,12 +19,15 @@ import org.evomaster.core.problem.rest.* import org.evomaster.core.problem.rest.builder.CreateResourceUtils import org.evomaster.core.problem.rest.builder.RestIndividualSelectorUtils import org.evomaster.core.problem.rest.data.* +import org.evomaster.core.problem.rest.oracle.RestSecurityOracle.SQLI_PAYLOADS import org.evomaster.core.problem.rest.param.PathParam import org.evomaster.core.problem.rest.resource.RestResourceCalls import org.evomaster.core.problem.rest.service.sampler.AbstractRestSampler import org.evomaster.core.search.* import org.evomaster.core.search.action.ActionResult +import org.evomaster.core.search.gene.string.StringGene +import org.evomaster.core.search.gene.utils.GeneUtils import org.evomaster.core.search.service.Archive import org.evomaster.core.search.service.FitnessFunction import org.evomaster.core.search.service.IdMapper @@ -292,10 +296,107 @@ class SecurityRest { handleStackTraceCheck() } + if (!config.isEnabledFaultCategory(DefinedFaultCategory.SQL_INJECTION)) { + LoggingUtil.uniqueUserInfo("Skipping experimental security test for sql injection as disabled in configuration") + } else { + handleSqlICheck() + } + //TODO other rules. See FaultCategory //etc. } + + private fun handleSqlICheck(){ + + mainloop@ for(action in actionDefinitions){ + + + // Find individuals with 2xx response for this endpoint + val successfulIndividuals = RestIndividualSelectorUtils.findIndividuals( + individualsInSolution, + action.verb, + action.path, + statusGroup = StatusGroup.G_2xx + ) + + if(successfulIndividuals.isEmpty()){ + continue + } + + // Take the smallest successful individual + val target = successfulIndividuals.minBy { it.individual.size() } + + val actionIndex = RestIndividualSelectorUtils.findIndexOfAction( + target, + action.verb, + action.path, + statusGroup = StatusGroup.G_2xx + ) + + if(actionIndex < 0){ + continue + } + + // Slice to keep only up to the target action + val sliced = RestIndividualBuilder.sliceAllCallsInIndividualAfterAction( + target.individual, + actionIndex + ) + + // Try each sqli payload (but only add one test per endpoint) + for(payload in SQLI_PAYLOADS){ + + // Create a copy of the individual + var copy = sliced.copy() as RestIndividual + val actionCopy = copy.seeMainExecutableActions().last() as RestCallAction + + val genes = GeneUtils.getAllStringFields(actionCopy.parameters) + .filter { it.staticCheckIfImpactPhenotype() } + + if(genes.isEmpty()){ + continue + } + var anySuccess = false + + genes.forEach { + gene -> + if(gene !is StringGene) return@forEach + + // we need to do this way because we need to append our paylod + val hasInvalidChars = gene.invalidChars.any { payload.contains(it) } + if(!hasInvalidChars){ + // append the SQLi payload value + gene.value = gene.value + payload + anySuccess = true + } + } + + if(!anySuccess){ + continue + } + + copy.modifySampleType(SampleType.SECURITY) + copy.ensureFlattenedStructure() + + val evaluatedIndividual = fitness.computeWholeAchievedCoverageForPostProcessing(copy) + + if (evaluatedIndividual == null) { + log.warn("Failed to evaluate constructed individual in handleStackTraceCheck") + continue@mainloop + } + + val faultsCategories = DetectedFaultUtils.getDetectedFaultCategories(evaluatedIndividual) + + if(DefinedFaultCategory.SQL_INJECTION in faultsCategories){ + val added = archive.addIfNeeded(evaluatedIndividual) + assert(added) + continue@mainloop + } + + } + } + } /** * Checks whether any response body contains a stack trace, which would constitute a security issue. * Stack traces expose internal implementation details that can aid attackers in exploiting vulnerabilities. diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/fitness/AbstractRestFitness.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/fitness/AbstractRestFitness.kt index efde8c6d9a..be9648584f 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/fitness/AbstractRestFitness.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/fitness/AbstractRestFitness.kt @@ -31,6 +31,7 @@ import org.evomaster.core.problem.rest.link.RestLinkValueUpdater import org.evomaster.core.problem.rest.oracle.HttpSemanticsOracle import org.evomaster.core.problem.rest.oracle.RestSchemaOracle import org.evomaster.core.problem.rest.oracle.RestSecurityOracle +import org.evomaster.core.problem.rest.oracle.RestSecurityOracle.SQLI_PAYLOADS import org.evomaster.core.problem.rest.param.BodyParam import org.evomaster.core.problem.rest.param.HeaderParam import org.evomaster.core.problem.rest.param.QueryParam @@ -1214,6 +1215,7 @@ abstract class AbstractRestFitness : HttpWsFitness() { handleNotRecognizedAuthenticated(individual, actionResults, fv) handleForgottenAuthentication(individual, actionResults, fv) handleStackTraceCheck(individual, actionResults, fv) + handleSQLiCheck(individual, actionResults, fv) } private fun handleSsrfFaults( @@ -1314,6 +1316,38 @@ abstract class AbstractRestFitness : HttpWsFitness() { } } + private fun handleSQLiCheck( + individual: RestIndividual, + actionResults: List, + fv: FitnessValue + ) { + if (!config.isEnabledFaultCategory(DefinedFaultCategory.XSS)) { + return + } + + // Find the action(s) where XSS payload appears in the response + for(index in individual.seeMainExecutableActions().indices){ + val a = individual.seeMainExecutableActions()[index] + val r = actionResults.find { it.sourceLocalId == a.getLocalId() } as? RestCallResult + ?: continue + +// if(!r.getTimedout()){ +// continue +// } + + // in here executionTimeMs doesn't assign yet. + if(fv.executionTimeMs < config.sqlInjectionMaxResponseTimeMs){ + continue + } + + val scenarioId = idMapper.handleLocalTarget( + idMapper.getFaultDescriptiveId(DefinedFaultCategory.SQL_INJECTION, a.getName()) + ) + fv.updateTarget(scenarioId, 1.0, index) + r.addFault(DetectedFault(DefinedFaultCategory.SQL_INJECTION, a.getName(), null)) + break // Only add one fault per action + } + } private fun handleStackTraceCheck( individual: RestIndividual, diff --git a/docs/options.md b/docs/options.md index 4f724d0f8a..72cb4e1150 100644 --- a/docs/options.md +++ b/docs/options.md @@ -304,6 +304,7 @@ There are 3 types of options: |`seedTestCases`| __Boolean__. Whether to seed EvoMaster with some initial test cases. These test cases will be used and evolved throughout the search process. *Default value*: `false`.| |`seedTestCasesFormat`| __Enum__. Format of the test cases seeded to EvoMaster. *Valid values*: `POSTMAN`. *Default value*: `POSTMAN`.| |`seedTestCasesPath`| __String__. File path where the seeded test cases are located. *Default value*: `postman.postman_collection.json`.| +|`sqlInjectionMaxResponseTimeMs`| __Int__. Maximum response time (in milliseconds) to consider a potential SQL Injection vulnerability. *Default value*: `2000`.| |`ssrf`| __Boolean__. To apply SSRF detection as part of security testing. *Default value*: `false`.| |`structureMutationProFS`| __Double__. Specify a probability of applying structure mutator during the focused search. *Constraints*: `probability 0.0-1.0`. *Default value*: `0.0`.| |`structureMutationProbStrategy`| __Enum__. Specify a strategy to handle a probability of applying structure mutator during the focused search. *Valid values*: `SPECIFIED, SPECIFIED_FS, DPC_TO_SPECIFIED_BEFORE_FS, DPC_TO_SPECIFIED_AFTER_FS, ADAPTIVE_WITH_IMPACT`. *Default value*: `SPECIFIED`.| From 06e9d5b90711076aebca9f57f1b36cfab3df865d Mon Sep 17 00:00:00 2001 From: Omur Date: Mon, 17 Nov 2025 16:08:48 +0300 Subject: [PATCH 03/13] time record --- .../openapi/v3/security/sqli/SQLiQueryH2EMTest.kt | 6 +++--- .../core/problem/httpws/HttpWsCallResult.kt | 4 ++++ .../core/problem/rest/oracle/RestSecurityOracle.kt | 6 +++--- .../rest/service/fitness/AbstractRestFitness.kt | 12 +++++++++--- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiQueryH2EMTest.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiQueryH2EMTest.kt index ba2b7a6448..fb53cfd805 100644 --- a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiQueryH2EMTest.kt +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiQueryH2EMTest.kt @@ -18,7 +18,7 @@ class SQLiQueryH2EMTest : SpringTestBase() { fun init() { val config = EMConfig() config.instrumentMR_NET = false - config.instrumentMR_SQL = false +// config.instrumentMR_SQL = false initClass(SQLiH2QueryController(), config) } @@ -28,7 +28,7 @@ class SQLiQueryH2EMTest : SpringTestBase() { fun testSQLiH2EM() { runTestHandlingFlakyAndCompilation( "SQLiH2EMTest", - 50, + 20 ) { args: MutableList -> setOption(args, "security", "true") @@ -58,4 +58,4 @@ class SQLiQueryH2EMTest : SpringTestBase() { } } -} \ No newline at end of file +} diff --git a/core/src/main/kotlin/org/evomaster/core/problem/httpws/HttpWsCallResult.kt b/core/src/main/kotlin/org/evomaster/core/problem/httpws/HttpWsCallResult.kt index f2b0635b17..574c89d09f 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/httpws/HttpWsCallResult.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/httpws/HttpWsCallResult.kt @@ -25,6 +25,7 @@ abstract class HttpWsCallResult : EnterpriseActionResult { const val TCP_PROBLEM = "TCP_PROBLEM" const val APPLIED_LINK = "APPLIED_LINK" const val LOCATION = "LOCATION" + const val RESPONSE_TIME = "RESPONSE_TIME" const val VULNERABLE_SSRF = "VULNERABLE_SSRF" } @@ -126,4 +127,7 @@ abstract class HttpWsCallResult : EnterpriseActionResult { */ fun setVulnerableForSSRF(on: Boolean) = addResultValue(VULNERABLE_SSRF, on.toString()) fun getVulnerableForSSRF() : Boolean = getResultValue(VULNERABLE_SSRF)?.toBoolean() ?: false + + fun setResponseTime(responseTime: Long) = addResultValue(RESPONSE_TIME, responseTime.toString()) + fun getResponseTime(): Long = getResultValue(RESPONSE_TIME)?.toLong() ?: 0 } diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt index 2a847bfb81..3253074bf5 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt @@ -224,12 +224,12 @@ object RestSecurityOracle { // Simple SQLi payloads val SQLI_PAYLOADS = listOf( -// " '' OR (WITH RECURSIVE r(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM r WHERE i < 100000000) SELECT COUNT(*) FROM r)>0--", + "' OR (WITH RECURSIVE r(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM r WHERE i < 10000000) SELECT COUNT(*) FROM r)>0--", // for h2 database // "' OR (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B, INFORMATION_SCHEMA.COLUMNS C)>0--", - "' OR (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B)>0--", +// "' OR (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B)>0--", // it takes extremely long. // "' OR (SELECT SUM(a.ORDINAL_POSITION*b.ORDINAL_POSITION*c.ORDINAL_POSITION) FROM INFORMATION_SCHEMA.COLUMNS a, INFORMATION_SCHEMA.COLUMNS b, INFORMATION_SCHEMA.COLUMNS c)>1 --", ) -} \ No newline at end of file +} diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/fitness/AbstractRestFitness.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/fitness/AbstractRestFitness.kt index be9648584f..fb34dcec43 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/fitness/AbstractRestFitness.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/fitness/AbstractRestFitness.kt @@ -59,6 +59,7 @@ import org.evomaster.core.search.gene.wrapper.OptionalGene import org.evomaster.core.search.gene.string.StringGene import org.evomaster.core.search.gene.utils.GeneUtils import org.evomaster.core.search.service.DataPool +import org.evomaster.core.search.service.SearchTimeController import org.evomaster.core.taint.TaintAnalysis import org.evomaster.core.utils.StackTraceUtils import org.slf4j.Logger @@ -592,7 +593,13 @@ abstract class AbstractRestFitness : HttpWsFitness() { val appliedLink = handleLinks(a, all,actionResults) val response = try { - createInvocation(a, chainState, cookies, tokens).invoke() + SearchTimeController.measureTimeMillis( + { t, res -> + rcr.setResponseTime(t) + }, + {createInvocation(a, chainState, cookies, tokens).invoke()} + ) + } catch (e: ProcessingException) { log.debug("There has been an issue in the evaluation of a test: ${e.message}", e) @@ -1335,8 +1342,7 @@ abstract class AbstractRestFitness : HttpWsFitness() { // continue // } - // in here executionTimeMs doesn't assign yet. - if(fv.executionTimeMs < config.sqlInjectionMaxResponseTimeMs){ + if(r.getResponseTime() < config.sqlInjectionMaxResponseTimeMs && !r.getTimedout()){ continue } From 74617b3158f82552aaa997ca7da71ac132e1d2b9 Mon Sep 17 00:00:00 2001 From: Omur Date: Wed, 19 Nov 2025 16:48:33 +0300 Subject: [PATCH 04/13] sqli-mysql --- .../mysql/sqli/body/BodySQLiApplication.kt | 100 ++++++++++++++++++ .../spring/rest/mysql/sqli/body/UserDto.kt | 13 +++ .../spring/rest/mysql/sqli/body/UserEntity.kt | 19 ++++ .../rest/mysql/sqli/body/UserRepository.kt | 10 ++ .../mysql/sqli/path/PathSQLiApplication.kt | 80 ++++++++++++++ .../spring/rest/mysql/sqli/path/UserDto.kt | 12 +++ .../spring/rest/mysql/sqli/path/UserEntity.kt | 19 ++++ .../rest/mysql/sqli/path/UserRepository.kt | 10 ++ .../mysql/sqli/query/QuerySQLiApplication.kt | 76 +++++++++++++ .../spring/rest/mysql/sqli/query/UserDto.kt | 12 +++ .../rest/mysql/sqli/query/UserEntity.kt | 19 ++++ .../rest/mysql/sqli/query/UserRepository.kt | 10 ++ .../resources/schema/sqli/V1.0__createDB.sql | 12 +++ .../security/sqli/SQLiMySQLBodyController.kt | 12 +++ .../security/sqli/SQLiMySQLPathController.kt | 12 +++ .../security/sqli/SQLiMySQLQueryController.kt | 12 +++ .../rest/security/sqli/SQLiMySQLBodyEMTest.kt | 59 +++++++++++ .../rest/security/sqli/SQLiMySQLPathEMTest.kt | 58 ++++++++++ .../security/sqli/SQLiMySQLQueryEMTest.kt | 60 +++++++++++ .../main/resources/application-sqli-mysql.yml | 11 -- .../resources/application-sqli-postgres.yml | 11 -- .../problem/rest/oracle/RestSecurityOracle.kt | 4 + .../core/problem/rest/service/SecurityRest.kt | 7 +- 23 files changed, 613 insertions(+), 25 deletions(-) create mode 100644 core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/body/BodySQLiApplication.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/body/UserDto.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/body/UserEntity.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/body/UserRepository.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/path/PathSQLiApplication.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/path/UserDto.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/path/UserEntity.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/path/UserRepository.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/query/QuerySQLiApplication.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/query/UserDto.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/query/UserEntity.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/query/UserRepository.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-mysql/src/main/resources/schema/sqli/V1.0__createDB.sql create mode 100644 core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/security/sqli/SQLiMySQLBodyController.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/security/sqli/SQLiMySQLPathController.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/security/sqli/SQLiMySQLQueryController.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/security/sqli/SQLiMySQLBodyEMTest.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/security/sqli/SQLiMySQLPathEMTest.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/security/sqli/SQLiMySQLQueryEMTest.kt delete mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/resources/application-sqli-mysql.yml delete mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/resources/application-sqli-postgres.yml diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/body/BodySQLiApplication.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/body/BodySQLiApplication.kt new file mode 100644 index 0000000000..b805207e11 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/body/BodySQLiApplication.kt @@ -0,0 +1,100 @@ +package com.foo.spring.rest.mysql.sqli.body + +import com.foo.spring.rest.mysql.SwaggerConfiguration +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.media.ArraySchema +import io.swagger.v3.oas.annotations.media.Content +import io.swagger.v3.oas.annotations.media.Schema +import io.swagger.v3.oas.annotations.parameters.RequestBody +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.responses.ApiResponses +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.SpringApplication +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.autoconfigure.domain.EntityScan +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration +import org.springframework.context.annotation.ComponentScan +import org.springframework.data.jpa.repository.config.EnableJpaRepositories +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import springfox.documentation.swagger2.annotations.EnableSwagger2 +import java.sql.Connection +import javax.annotation.PostConstruct +import javax.sql.DataSource + + +@EnableSwagger2 +@SpringBootApplication(exclude = [SecurityAutoConfiguration::class]) +@RequestMapping(path = ["/api/sqli/body"]) +@RestController +open class BodySQLiApplication: SwaggerConfiguration() { + + @Autowired + private lateinit var dataSource: DataSource + + @Autowired + private lateinit var userRepository: UserRepository + + private var connection: Connection? = null + + companion object { + @JvmStatic + fun main(args: Array) { + SpringApplication.run(BodySQLiApplication::class.java, *args) + } + } + + @PostConstruct + fun init() { + connection = dataSource.connection + initializeTestData() + } + + private fun initializeTestData() { + if (userRepository.count() == 0L) { + userRepository.save(UserEntity(null, "admin", "admin123")) + userRepository.save(UserEntity(null, "user1", "password1")) + } + } + + /** + * Safe endpoint - No SQL Injection vulnerability + */ + @GetMapping("/safe") + @Operation(summary = "Safe Query - No SQL Injection") + @ApiResponses(value = [ + ApiResponse(responseCode = "200", description = "Successful operation", + content = [Content(mediaType = "application/json", + array = ArraySchema(schema = Schema(implementation = UserDto::class)))]) + ]) + fun getSafeUsers(): ResponseEntity> { + val users = userRepository.findAll().map { UserDto(it.id, it.username) } + return ResponseEntity.ok(users) + } + + /** + * Attack: POST {"username":"admin' AND (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B, INFORMATION_SCHEMA.COLUMNS C)>0--","password":"x"} + */ + @PostMapping("/vulnerable") + @Operation(summary = "SQL Injection - Body Parameter") + @ApiResponses(value = [ + ApiResponse(responseCode = "200", description = "Successful operation", + content = [Content(mediaType = "text/plain", schema = Schema(implementation = String::class))]), + ApiResponse(responseCode = "500", description = "Internal server error", + content = [Content(mediaType = "text/plain", schema = Schema(implementation = String::class))]) + ]) + @RequestBody(description = "Login credentials", required = true, + content = [Content(mediaType = "application/json", + schema = Schema(implementation = LoginDto::class))]) + fun body(@org.springframework.web.bind.annotation.RequestBody loginDto: LoginDto): ResponseEntity { + return try { + val stmt = connection?.createStatement() + val rs = stmt?.executeQuery("SELECT COUNT(*) as cnt FROM users WHERE username = '${loginDto.username}' AND password = '${loginDto.password}'") + val count = if (rs?.next() == true) rs.getInt("cnt") else 0 + ResponseEntity.ok("MATCHED: $count") + } catch (e: Exception) { + ResponseEntity.status(500).body("ERROR: ${e.message}") + } + } + +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/body/UserDto.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/body/UserDto.kt new file mode 100644 index 0000000000..c3ff2c756d --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/body/UserDto.kt @@ -0,0 +1,13 @@ +package com.foo.spring.rest.mysql.sqli.body + +import com.fasterxml.jackson.annotation.JsonProperty + +data class UserDto( + @JsonProperty("id") var id: Long? = null, + @JsonProperty("username") var username: String? = null, +) + +data class LoginDto( + @JsonProperty("username") var username: String = "", + @JsonProperty("password") var password: String = "" +) diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/body/UserEntity.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/body/UserEntity.kt new file mode 100644 index 0000000000..0814f815de --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/body/UserEntity.kt @@ -0,0 +1,19 @@ +package com.foo.spring.rest.mysql.sqli.body + +import javax.persistence.* + + +@Entity +@Table(name = "users") +open class UserEntity( + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + open var id: Long? = null, + + @Column(name = "username", unique = true, nullable = false) + open var username: String? = null, + + @Column(name = "password", nullable = false) + open var password: String? = null, +) diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/body/UserRepository.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/body/UserRepository.kt new file mode 100644 index 0000000000..4cf41e73f4 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/body/UserRepository.kt @@ -0,0 +1,10 @@ +package com.foo.spring.rest.mysql.sqli.body + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + + +@Repository +interface UserRepository : JpaRepository { + +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/path/PathSQLiApplication.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/path/PathSQLiApplication.kt new file mode 100644 index 0000000000..073dbd92ad --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/path/PathSQLiApplication.kt @@ -0,0 +1,80 @@ +package com.foo.spring.rest.mysql.sqli.path + + +import io.swagger.v3.oas.annotations.Operation +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.SpringApplication +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.autoconfigure.domain.EntityScan +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration +import org.springframework.context.annotation.ComponentScan +import org.springframework.data.jpa.repository.config.EnableJpaRepositories +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import springfox.documentation.swagger2.annotations.EnableSwagger2 +import java.sql.Connection +import javax.annotation.PostConstruct +import javax.sql.DataSource + + +@EnableSwagger2 +@SpringBootApplication(exclude = [SecurityAutoConfiguration::class]) +@RequestMapping(path = ["/api/sqli/path"]) +@RestController +open class PathSQLiApplication { + + @Autowired + private lateinit var dataSource: DataSource + + @Autowired + private lateinit var userRepository: UserRepository + + private var connection: Connection? = null + + companion object { + @JvmStatic + fun main(args: Array) { + SpringApplication.run(PathSQLiApplication::class.java, *args) + } + } + + @PostConstruct + fun init() { + connection = dataSource.connection + initializeTestData() + } + + private fun initializeTestData() { + if (userRepository.count() == 0L) { + userRepository.save(UserEntity(null, "admin", "admin123")) + userRepository.save(UserEntity(null, "user1", "password1")) + } + } + + /** + * Safe endpoint - No SQL Injection vulnerability + */ + @GetMapping("/safe") + @Operation(summary = "Safe Query - No SQL Injection") + fun getSafeUsers(): ResponseEntity> { + val users = userRepository.findAll().map { UserDto(it.id, it.username) } + return ResponseEntity.ok(users) + } + + /** + * Attack: GET /api/sqli/path/vulnerable/admin' OR (SELECT SUM(a.ORDINAL_POSITION*b.ORDINAL_POSITION*c.ORDINAL_POSITION) FROM INFORMATION_SCHEMA.COLUMNS a, INFORMATION_SCHEMA.COLUMNS b, INFORMATION_SCHEMA.COLUMNS c)>1 -- + */ + @GetMapping("/vulnerable/{id}") + @Operation(summary = "SQL Injection - Path Parameter") + fun timeBasedPath(@PathVariable id: String): ResponseEntity { + return try { + val stmt = connection?.createStatement() + val rs = stmt?.executeQuery("SELECT username FROM users WHERE username = '$id'") + val username = if (rs?.next() == true) rs.getString("username") else "NOT_FOUND" + ResponseEntity.ok("USERNAME: $username") + } catch (e: Exception) { + ResponseEntity.status(500).body("ERROR: ${e.message}") + } + } + +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/path/UserDto.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/path/UserDto.kt new file mode 100644 index 0000000000..fed8a7bd87 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/path/UserDto.kt @@ -0,0 +1,12 @@ +package com.foo.spring.rest.mysql.sqli.path + + +data class UserDto( + var id: Long? = null, + var username: String? = null, +) + +data class LoginDto( + var username: String = "", + var password: String = "" +) diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/path/UserEntity.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/path/UserEntity.kt new file mode 100644 index 0000000000..34fc831534 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/path/UserEntity.kt @@ -0,0 +1,19 @@ +package com.foo.spring.rest.mysql.sqli.path + +import javax.persistence.* + + +@Entity +@Table(name = "users") +open class UserEntity( + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + open var id: Long? = null, + + @Column(name = "username", unique = true, nullable = false) + open var username: String? = null, + + @Column(name = "password", nullable = false) + open var password: String? = null, +) diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/path/UserRepository.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/path/UserRepository.kt new file mode 100644 index 0000000000..fa4ff4ec2a --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/path/UserRepository.kt @@ -0,0 +1,10 @@ +package com.foo.spring.rest.mysql.sqli.path + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + + +@Repository +interface UserRepository : JpaRepository { + +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/query/QuerySQLiApplication.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/query/QuerySQLiApplication.kt new file mode 100644 index 0000000000..e42b4685c0 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/query/QuerySQLiApplication.kt @@ -0,0 +1,76 @@ +package com.foo.spring.rest.mysql.sqli.query + +import com.foo.spring.rest.mysql.SwaggerConfiguration +import io.swagger.v3.oas.annotations.Operation +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.SpringApplication +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import springfox.documentation.swagger2.annotations.EnableSwagger2 +import java.sql.Connection +import javax.annotation.PostConstruct +import javax.sql.DataSource + + +@EnableSwagger2 +@SpringBootApplication(exclude = [SecurityAutoConfiguration::class]) +@RequestMapping(path = ["/api/sqli/query"]) +open class QuerySQLiApplication: SwaggerConfiguration() { + + @Autowired + private lateinit var dataSource: DataSource + + @Autowired + private lateinit var userRepository: UserRepository + + private var connection: Connection? = null + + companion object { + @JvmStatic + fun main(args: Array) { + SpringApplication.run(QuerySQLiApplication::class.java, *args) + } + } + + @PostConstruct + fun init() { + connection = dataSource.connection + initializeTestData() + } + + private fun initializeTestData() { + if (userRepository.count() == 0L) { + userRepository.save(UserEntity(null, "admin", "admin123")) + userRepository.save(UserEntity(null, "user1", "password1")) + } + } + + /** + * Safe endpoint - No SQL Injection vulnerability + */ + @GetMapping("/safe") + @Operation(summary = "Safe Query - No SQL Injection") + fun getSafeUsers(): ResponseEntity> { + val users = userRepository.findAll().map { UserDto(it.id, it.username) } + return ResponseEntity.ok(users) + } + + /** + * Attack: GET /api/sqli/query/vulnerable?username=admin' OR (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B, INFORMATION_SCHEMA.COLUMNS C)>0-- + */ + @GetMapping("/vulnerable") + @Operation(summary = "SQL Injection - Query Parameter") + fun timeBasedQuery(@RequestParam username: String): ResponseEntity { + return try { + val stmt = connection?.createStatement() + val rs = stmt?.executeQuery("SELECT COUNT(*) as cnt FROM users WHERE username = '$username'") + val count = if (rs?.next() == true) rs.getInt("cnt") else 0 + ResponseEntity.ok("COUNT: $count") + } catch (e: Exception) { + ResponseEntity.status(500).body("ERROR: ${e.message}") + } + } + +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/query/UserDto.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/query/UserDto.kt new file mode 100644 index 0000000000..5cc7cad84f --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/query/UserDto.kt @@ -0,0 +1,12 @@ +package com.foo.spring.rest.mysql.sqli.query + + +data class UserDto( + var id: Long? = null, + var username: String? = null, +) + +data class LoginDto( + var username: String, + var password: String +) diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/query/UserEntity.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/query/UserEntity.kt new file mode 100644 index 0000000000..083c6738dc --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/query/UserEntity.kt @@ -0,0 +1,19 @@ +package com.foo.spring.rest.mysql.sqli.query + +import javax.persistence.* + + +@Entity +@Table(name = "users") +open class UserEntity( + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + open var id: Long? = null, + + @Column(name = "username", unique = true, nullable = false) + open var username: String? = null, + + @Column(name = "password", nullable = false) + open var password: String? = null, +) diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/query/UserRepository.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/query/UserRepository.kt new file mode 100644 index 0000000000..94a37dca46 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/kotlin/com/foo/spring/rest/mysql/sqli/query/UserRepository.kt @@ -0,0 +1,10 @@ +package com.foo.spring.rest.mysql.sqli.query + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + + +@Repository +interface UserRepository : JpaRepository { + +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/resources/schema/sqli/V1.0__createDB.sql b/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/resources/schema/sqli/V1.0__createDB.sql new file mode 100644 index 0000000000..ecaa651a7f --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/main/resources/schema/sqli/V1.0__createDB.sql @@ -0,0 +1,12 @@ +CREATE TABLE users ( + id BIGINT NOT NULL AUTO_INCREMENT, + username VARCHAR(255) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE hibernate_sequence ( + next_val BIGINT +); + +INSERT INTO hibernate_sequence VALUES (1); diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/security/sqli/SQLiMySQLBodyController.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/security/sqli/SQLiMySQLBodyController.kt new file mode 100644 index 0000000000..edc2fbd5b3 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/security/sqli/SQLiMySQLBodyController.kt @@ -0,0 +1,12 @@ +package com.foo.spring.rest.mysql.security.sqli + +import com.foo.spring.rest.mysql.SpringRestMySqlController +import com.foo.spring.rest.mysql.sqli.body.BodySQLiApplication +import org.evomaster.client.java.sql.DbSpecification + +class SQLiMySQLBodyController : SpringRestMySqlController(BodySQLiApplication::class.java){ + override fun pathToFlywayFiles() = "classpath:/schema/sqli" + override fun getDbSpecifications(): MutableList? { + return null + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/security/sqli/SQLiMySQLPathController.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/security/sqli/SQLiMySQLPathController.kt new file mode 100644 index 0000000000..b1db997ccc --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/security/sqli/SQLiMySQLPathController.kt @@ -0,0 +1,12 @@ +package com.foo.spring.rest.mysql.security.sqli + +import com.foo.spring.rest.mysql.SpringRestMySqlController +import com.foo.spring.rest.mysql.sqli.path.PathSQLiApplication +import org.evomaster.client.java.sql.DbSpecification + +class SQLiMySQLPathController : SpringRestMySqlController(PathSQLiApplication::class.java){ + override fun pathToFlywayFiles() = "classpath:/schema/sqli" + override fun getDbSpecifications(): MutableList? { + return null + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/security/sqli/SQLiMySQLQueryController.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/security/sqli/SQLiMySQLQueryController.kt new file mode 100644 index 0000000000..3f202ad986 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/security/sqli/SQLiMySQLQueryController.kt @@ -0,0 +1,12 @@ +package com.foo.spring.rest.mysql.security.sqli + +import com.foo.spring.rest.mysql.SpringRestMySqlController +import com.foo.spring.rest.mysql.sqli.query.QuerySQLiApplication +import org.evomaster.client.java.sql.DbSpecification + +class SQLiMySQLQueryController : SpringRestMySqlController(QuerySQLiApplication::class.java){ + override fun pathToFlywayFiles() = "classpath:/schema/sqli" + override fun getDbSpecifications(): MutableList? { + return null + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/security/sqli/SQLiMySQLBodyEMTest.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/security/sqli/SQLiMySQLBodyEMTest.kt new file mode 100644 index 0000000000..5c8aa76cc3 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/security/sqli/SQLiMySQLBodyEMTest.kt @@ -0,0 +1,59 @@ +package org.evomaster.e2etests.spring.rest.security.sqli + +import com.foo.spring.rest.mysql.security.sqli.SQLiMySQLBodyController +import com.webfuzzing.commons.faults.DefinedFaultCategory +import org.evomaster.core.EMConfig +import org.evomaster.core.problem.enterprise.DetectedFaultUtils +import org.evomaster.e2etests.spring.mysql.entity.SpringTestBase +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test + +class SQLiMySQLBodyEMTest : SpringTestBase() { + + companion object { + @BeforeAll + @JvmStatic + fun init() { + val config = EMConfig() + config.instrumentMR_NET = false + config.instrumentMR_SQL = false + + initClass(SQLiMySQLBodyController(), config) + } + } + + @Test + fun testRunEM() { + + runTestHandlingFlakyAndCompilation( + "SQLiMySQLBodyEM", + 100 + ) { args -> + setOption(args, "security", "true") + + val solution = initAndRun(args) + assertTrue(solution.individuals.isNotEmpty()) + + val faults = DetectedFaultUtils.getDetectedFaults(solution) + + assertTrue(faults.size == 1) + + val faultCategories = DetectedFaultUtils.getDetectedFaultCategories(solution) + + assertTrue({ DefinedFaultCategory.SQL_INJECTION in faultCategories }) + + assertTrue(faults.any { + it.category == DefinedFaultCategory.SQL_INJECTION + && it.operationId == "POST:/api/sqli/body/vulnerable" + }) + + assertFalse(faults.any { + it.category == DefinedFaultCategory.SQL_INJECTION + && it.operationId == "GET:/api/sqli/body/safe" + }) + + } + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/security/sqli/SQLiMySQLPathEMTest.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/security/sqli/SQLiMySQLPathEMTest.kt new file mode 100644 index 0000000000..aca4b4ad9f --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/security/sqli/SQLiMySQLPathEMTest.kt @@ -0,0 +1,58 @@ +package org.evomaster.e2etests.spring.rest.security.sqli + +import com.foo.spring.rest.mysql.security.sqli.SQLiMySQLBodyController +import com.foo.spring.rest.mysql.security.sqli.SQLiMySQLPathController +import com.webfuzzing.commons.faults.DefinedFaultCategory +import org.evomaster.core.EMConfig +import org.evomaster.core.problem.enterprise.DetectedFaultUtils +import org.evomaster.e2etests.spring.mysql.entity.SpringTestBase +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test + +class SQLiMySQLPathEMTest : SpringTestBase() { + + companion object { + @BeforeAll + @JvmStatic + fun init() { + val config = EMConfig() + config.instrumentMR_NET = false + config.instrumentMR_SQL = false + + initClass(SQLiMySQLPathController(), config) + } + } + + @Test + fun testRunEM() { + + runTestHandlingFlakyAndCompilation( + "SQLiMySQLPathEM", + 100 + ) { args -> + setOption(args, "security", "true") + + val solution = initAndRun(args) + assertTrue(solution.individuals.isNotEmpty()) + + val faults = DetectedFaultUtils.getDetectedFaults(solution) + + val faultCategories = DetectedFaultUtils.getDetectedFaultCategories(solution) + + assertTrue({ DefinedFaultCategory.SQL_INJECTION in faultCategories }) + + assertTrue(faults.any { + it.category == DefinedFaultCategory.SQL_INJECTION + && it.operationId == "GET:/api/sqli/path/vulnerable/{id}" + }) + + assertFalse(faults.any { + it.category == DefinedFaultCategory.SQL_INJECTION + && it.operationId == "GET:/api/sqli/path/safe" + }) + + } + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/security/sqli/SQLiMySQLQueryEMTest.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/security/sqli/SQLiMySQLQueryEMTest.kt new file mode 100644 index 0000000000..aa0ba73580 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/security/sqli/SQLiMySQLQueryEMTest.kt @@ -0,0 +1,60 @@ +package org.evomaster.e2etests.spring.rest.security.sqli + +import com.foo.spring.rest.mysql.security.sqli.SQLiMySQLQueryController +import com.webfuzzing.commons.faults.DefinedFaultCategory +import org.evomaster.core.EMConfig +import org.evomaster.core.problem.enterprise.DetectedFaultUtils +import org.evomaster.e2etests.spring.mysql.entity.SpringTestBase +import org.evomaster.e2etests.utils.RestTestBase +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test + +class SQLiMySQLQueryEMTest : SpringTestBase() { + + companion object { + @BeforeAll + @JvmStatic + fun init() { + val config = EMConfig() + config.instrumentMR_NET = false + config.instrumentMR_SQL = false + + initClass(SQLiMySQLQueryController(), config) + } + } + + @Test + fun testRunEM() { + + runTestHandlingFlakyAndCompilation( + "SQLiMySQLQueryEM", + 100 + ) { args -> + setOption(args, "security", "true") + + val solution = initAndRun(args) + assertTrue(solution.individuals.isNotEmpty()) + + val faults = DetectedFaultUtils.getDetectedFaults(solution) + + assertTrue(faults.size == 1) + + val faultCategories = DetectedFaultUtils.getDetectedFaultCategories(solution) + + assertTrue({ DefinedFaultCategory.SQL_INJECTION in faultCategories }) + + assertTrue(faults.any { + it.category == DefinedFaultCategory.SQL_INJECTION + && it.operationId == "GET:/api/sqli/query/vulnerable" + }) + + assertFalse(faults.any { + it.category == DefinedFaultCategory.SQL_INJECTION + && it.operationId == "GET:/api/sqli/query/safe" + }) + + } + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/resources/application-sqli-mysql.yml b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/resources/application-sqli-mysql.yml deleted file mode 100644 index bab993d80f..0000000000 --- a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/resources/application-sqli-mysql.yml +++ /dev/null @@ -1,11 +0,0 @@ -spring: - datasource: - url: jdbc:mysql://localhost:3306/sqli_test?createDatabaseIfNotExist=true&useSSL=false&allowPublicKeyRetrieval=true - driver-class-name: com.mysql.cj.jdbc.Driver - username: root - password: root - jpa: - database-platform: org.hibernate.dialect.MySQL8Dialect - hibernate: - ddl-auto: create-drop - show-sql: true diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/resources/application-sqli-postgres.yml b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/resources/application-sqli-postgres.yml deleted file mode 100644 index 6d40921cb5..0000000000 --- a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/resources/application-sqli-postgres.yml +++ /dev/null @@ -1,11 +0,0 @@ -spring: - datasource: - url: jdbc:postgresql://localhost:5432/sqli_test - driver-class-name: org.postgresql.Driver - username: postgres - password: postgres - jpa: - database-platform: org.hibernate.dialect.PostgreSQLDialect - hibernate: - ddl-auto: create-drop - show-sql: true diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt index 3253074bf5..c858031eaa 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt @@ -225,6 +225,10 @@ object RestSecurityOracle { // Simple SQLi payloads val SQLI_PAYLOADS = listOf( "' OR (WITH RECURSIVE r(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM r WHERE i < 10000000) SELECT COUNT(*) FROM r)>0--", + "' OR SLEEP(5)-- -", + "\" OR SLEEP(5)-- -", + "' union select sleep(5)-- -", + "\" union select sleep(5)-- -", // for h2 database // "' OR (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B, INFORMATION_SCHEMA.COLUMNS C)>0--", // "' OR (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B)>0--", diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/SecurityRest.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/SecurityRest.kt index 42c87a9cc1..45f4193fc8 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/SecurityRest.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/SecurityRest.kt @@ -361,13 +361,14 @@ class SecurityRest { genes.forEach { gene -> - if(gene !is StringGene) return@forEach + val leafGene = gene.getLeafGene() + if(leafGene !is StringGene) return@forEach // we need to do this way because we need to append our paylod - val hasInvalidChars = gene.invalidChars.any { payload.contains(it) } + val hasInvalidChars = leafGene.invalidChars.any { payload.contains(it) } if(!hasInvalidChars){ // append the SQLi payload value - gene.value = gene.value + payload + leafGene.value = leafGene.value + payload anySuccess = true } } From 7729b7ca6703c8f8be57d812228d764263efca69 Mon Sep 17 00:00:00 2001 From: Omur Date: Sun, 23 Nov 2025 22:39:46 +0300 Subject: [PATCH 05/13] postgres --- .../sqli/SQLiMySQLBodyController.kt | 2 +- .../sqli/SQLiMySQLPathController.kt | 2 +- .../sqli/SQLiMySQLQueryController.kt | 2 +- .../sqli/SQLiMySQLBodyEMTest.kt | 11 +- .../sqli/SQLiMySQLPathEMTest.kt | 12 +-- .../sqli/SQLiMySQLQueryEMTest.kt | 12 +-- .../v3/security/sqli/SQLiBodyH2EMTest.kt | 56 ++++++++++ .../v3/security/sqli/SQLiPathH2EMTest.kt | 58 ++++++++++ .../v3/security/sqli/SQLiQueryH2EMTest.kt | 7 +- .../postgres/sqli/body/BodySQLiApplication.kt | 100 ++++++++++++++++++ .../spring/rest/postgres/sqli/body/UserDto.kt | 13 +++ .../rest/postgres/sqli/body/UserEntity.kt | 19 ++++ .../rest/postgres/sqli/body/UserRepository.kt | 10 ++ .../postgres/sqli/path/PathSQLiApplication.kt | 78 ++++++++++++++ .../spring/rest/postgres/sqli/path/UserDto.kt | 12 +++ .../rest/postgres/sqli/path/UserEntity.kt | 19 ++++ .../rest/postgres/sqli/path/UserRepository.kt | 10 ++ .../sqli/query/QuerySQLiApplication.kt | 76 +++++++++++++ .../rest/postgres/sqli/query/UserDto.kt | 12 +++ .../rest/postgres/sqli/query/UserEntity.kt | 19 ++++ .../postgres/sqli/query/UserRepository.kt | 10 ++ .../resources/schema/sqli/V1.0__createDB.sql | 7 ++ .../sqli/SQLiPostgresBodyController.kt | 12 +++ .../sqli/SQLiPostgresPathController.kt | 12 +++ .../sqli/SQLiPostgresQueryController.kt | 12 +++ .../postgres/SQLiPostgresBodyEMTest.kt | 55 ++++++++++ .../postgres/SQLiPostgresPathEMTest.kt | 53 ++++++++++ .../postgres/SQLiPostgresQueryEMTest.kt | 55 ++++++++++ .../problem/rest/oracle/RestSecurityOracle.kt | 4 + 29 files changed, 715 insertions(+), 35 deletions(-) rename core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/{security => }/sqli/SQLiMySQLBodyController.kt (90%) rename core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/{security => }/sqli/SQLiMySQLPathController.kt (90%) rename core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/{security => }/sqli/SQLiMySQLQueryController.kt (90%) rename core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/{security => }/sqli/SQLiMySQLBodyEMTest.kt (81%) rename core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/{security => }/sqli/SQLiMySQLPathEMTest.kt (77%) rename core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/{security => }/sqli/SQLiMySQLQueryEMTest.kt (79%) create mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiBodyH2EMTest.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiPathH2EMTest.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/body/BodySQLiApplication.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/body/UserDto.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/body/UserEntity.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/body/UserRepository.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/path/PathSQLiApplication.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/path/UserDto.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/path/UserEntity.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/path/UserRepository.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/query/QuerySQLiApplication.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/query/UserDto.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/query/UserEntity.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/query/UserRepository.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-postgres/src/main/resources/schema/sqli/V1.0__createDB.sql create mode 100644 core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/com/foo/spring/rest/postgres/sqli/SQLiPostgresBodyController.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/com/foo/spring/rest/postgres/sqli/SQLiPostgresPathController.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/com/foo/spring/rest/postgres/sqli/SQLiPostgresQueryController.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/postgres/SQLiPostgresBodyEMTest.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/postgres/SQLiPostgresPathEMTest.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/postgres/SQLiPostgresQueryEMTest.kt diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/security/sqli/SQLiMySQLBodyController.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/sqli/SQLiMySQLBodyController.kt similarity index 90% rename from core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/security/sqli/SQLiMySQLBodyController.kt rename to core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/sqli/SQLiMySQLBodyController.kt index edc2fbd5b3..0d828c5bb0 100644 --- a/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/security/sqli/SQLiMySQLBodyController.kt +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/sqli/SQLiMySQLBodyController.kt @@ -1,4 +1,4 @@ -package com.foo.spring.rest.mysql.security.sqli +package com.foo.spring.rest.mysql.sqli import com.foo.spring.rest.mysql.SpringRestMySqlController import com.foo.spring.rest.mysql.sqli.body.BodySQLiApplication diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/security/sqli/SQLiMySQLPathController.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/sqli/SQLiMySQLPathController.kt similarity index 90% rename from core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/security/sqli/SQLiMySQLPathController.kt rename to core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/sqli/SQLiMySQLPathController.kt index b1db997ccc..8cfa41290a 100644 --- a/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/security/sqli/SQLiMySQLPathController.kt +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/sqli/SQLiMySQLPathController.kt @@ -1,4 +1,4 @@ -package com.foo.spring.rest.mysql.security.sqli +package com.foo.spring.rest.mysql.sqli import com.foo.spring.rest.mysql.SpringRestMySqlController import com.foo.spring.rest.mysql.sqli.path.PathSQLiApplication diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/security/sqli/SQLiMySQLQueryController.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/sqli/SQLiMySQLQueryController.kt similarity index 90% rename from core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/security/sqli/SQLiMySQLQueryController.kt rename to core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/sqli/SQLiMySQLQueryController.kt index 3f202ad986..b3b30290bb 100644 --- a/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/security/sqli/SQLiMySQLQueryController.kt +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/com/foo/spring/rest/mysql/sqli/SQLiMySQLQueryController.kt @@ -1,4 +1,4 @@ -package com.foo.spring.rest.mysql.security.sqli +package com.foo.spring.rest.mysql.sqli import com.foo.spring.rest.mysql.SpringRestMySqlController import com.foo.spring.rest.mysql.sqli.query.QuerySQLiApplication diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/security/sqli/SQLiMySQLBodyEMTest.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/sqli/SQLiMySQLBodyEMTest.kt similarity index 81% rename from core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/security/sqli/SQLiMySQLBodyEMTest.kt rename to core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/sqli/SQLiMySQLBodyEMTest.kt index 5c8aa76cc3..2cad7ecf60 100644 --- a/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/security/sqli/SQLiMySQLBodyEMTest.kt +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/sqli/SQLiMySQLBodyEMTest.kt @@ -1,8 +1,7 @@ -package org.evomaster.e2etests.spring.rest.security.sqli +package org.evomaster.e2etests.spring.rest.sqli -import com.foo.spring.rest.mysql.security.sqli.SQLiMySQLBodyController +import com.foo.spring.rest.mysql.sqli.SQLiMySQLBodyController import com.webfuzzing.commons.faults.DefinedFaultCategory -import org.evomaster.core.EMConfig import org.evomaster.core.problem.enterprise.DetectedFaultUtils import org.evomaster.e2etests.spring.mysql.entity.SpringTestBase import org.junit.jupiter.api.Assertions.assertFalse @@ -16,11 +15,7 @@ class SQLiMySQLBodyEMTest : SpringTestBase() { @BeforeAll @JvmStatic fun init() { - val config = EMConfig() - config.instrumentMR_NET = false - config.instrumentMR_SQL = false - - initClass(SQLiMySQLBodyController(), config) + initClass(SQLiMySQLBodyController()) } } diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/security/sqli/SQLiMySQLPathEMTest.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/sqli/SQLiMySQLPathEMTest.kt similarity index 77% rename from core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/security/sqli/SQLiMySQLPathEMTest.kt rename to core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/sqli/SQLiMySQLPathEMTest.kt index aca4b4ad9f..a8afa170e0 100644 --- a/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/security/sqli/SQLiMySQLPathEMTest.kt +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/sqli/SQLiMySQLPathEMTest.kt @@ -1,9 +1,7 @@ -package org.evomaster.e2etests.spring.rest.security.sqli +package org.evomaster.e2etests.spring.rest.sqli -import com.foo.spring.rest.mysql.security.sqli.SQLiMySQLBodyController -import com.foo.spring.rest.mysql.security.sqli.SQLiMySQLPathController +import com.foo.spring.rest.mysql.sqli.SQLiMySQLPathController import com.webfuzzing.commons.faults.DefinedFaultCategory -import org.evomaster.core.EMConfig import org.evomaster.core.problem.enterprise.DetectedFaultUtils import org.evomaster.e2etests.spring.mysql.entity.SpringTestBase import org.junit.jupiter.api.Assertions.assertFalse @@ -17,11 +15,7 @@ class SQLiMySQLPathEMTest : SpringTestBase() { @BeforeAll @JvmStatic fun init() { - val config = EMConfig() - config.instrumentMR_NET = false - config.instrumentMR_SQL = false - - initClass(SQLiMySQLPathController(), config) + initClass(SQLiMySQLPathController()) } } diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/security/sqli/SQLiMySQLQueryEMTest.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/sqli/SQLiMySQLQueryEMTest.kt similarity index 79% rename from core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/security/sqli/SQLiMySQLQueryEMTest.kt rename to core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/sqli/SQLiMySQLQueryEMTest.kt index aa0ba73580..2226a7f373 100644 --- a/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/security/sqli/SQLiMySQLQueryEMTest.kt +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/sqli/SQLiMySQLQueryEMTest.kt @@ -1,11 +1,9 @@ -package org.evomaster.e2etests.spring.rest.security.sqli +package org.evomaster.e2etests.spring.rest.sqli -import com.foo.spring.rest.mysql.security.sqli.SQLiMySQLQueryController +import com.foo.spring.rest.mysql.sqli.SQLiMySQLQueryController import com.webfuzzing.commons.faults.DefinedFaultCategory -import org.evomaster.core.EMConfig import org.evomaster.core.problem.enterprise.DetectedFaultUtils import org.evomaster.e2etests.spring.mysql.entity.SpringTestBase -import org.evomaster.e2etests.utils.RestTestBase import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeAll @@ -17,11 +15,7 @@ class SQLiMySQLQueryEMTest : SpringTestBase() { @BeforeAll @JvmStatic fun init() { - val config = EMConfig() - config.instrumentMR_NET = false - config.instrumentMR_SQL = false - - initClass(SQLiMySQLQueryController(), config) + initClass(SQLiMySQLQueryController()) } } diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiBodyH2EMTest.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiBodyH2EMTest.kt new file mode 100644 index 0000000000..bf51ac0ec7 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiBodyH2EMTest.kt @@ -0,0 +1,56 @@ +package org.evomaster.e2etests.spring.openapi.v3.security.sqli + + +import com.foo.rest.examples.spring.openapi.v3.security.sqli.SQLiH2BodyController +import com.webfuzzing.commons.faults.DefinedFaultCategory +import org.evomaster.core.problem.enterprise.DetectedFaultUtils +import org.evomaster.e2etests.spring.openapi.v3.SpringTestBase +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test + +class SQLiBodyH2EMTest : SpringTestBase() { + + companion object { + @BeforeAll + @JvmStatic + fun init() { + initClass(SQLiH2BodyController()) + } + } + + @Test + fun testSQLiH2EM() { + runTestHandlingFlakyAndCompilation( + "SQLiBodyH2EMTest", + 20 + ) { args: MutableList -> + + setOption(args, "security", "true") + + + val solution = initAndRun(args) + + Assertions.assertTrue(solution.individuals.isNotEmpty()) + + val faults = DetectedFaultUtils.getDetectedFaults(solution) + + Assertions.assertTrue(faults.size == 1) + + val faultCategories = DetectedFaultUtils.getDetectedFaultCategories(solution) + + Assertions.assertTrue({ DefinedFaultCategory.SQL_INJECTION in faultCategories }) + + Assertions.assertTrue(faults.any { + it.category == DefinedFaultCategory.SQL_INJECTION + && it.operationId == "POST:/api/sqli/body/vulnerable" + }) + + Assertions.assertFalse(faults.any { + it.category == DefinedFaultCategory.SQL_INJECTION + && it.operationId == "GET:/api/sqli/body/safe" + }) + + } + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiPathH2EMTest.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiPathH2EMTest.kt new file mode 100644 index 0000000000..b39f17d32c --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiPathH2EMTest.kt @@ -0,0 +1,58 @@ +package org.evomaster.e2etests.spring.openapi.v3.security.sqli + + +import com.foo.rest.examples.spring.openapi.v3.security.sqli.SQLiH2PathController +import com.webfuzzing.commons.faults.DefinedFaultCategory +import org.evomaster.core.problem.enterprise.DetectedFaultUtils +import org.evomaster.e2etests.spring.openapi.v3.SpringTestBase +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test + +class SQLiPathH2EMTest : SpringTestBase() { + + companion object { + @BeforeAll + @JvmStatic + fun init() { + initClass(SQLiH2PathController()) + } + } + + @Test + fun testSQLiH2EM() { + runTestHandlingFlakyAndCompilation( + "SQLiPathH2EMTest", + 20 + ) { args: MutableList -> + + setOption(args, "security", "true") + + + val solution = initAndRun(args) + + Assertions.assertTrue(solution.individuals.isNotEmpty()) + + val faults = DetectedFaultUtils.getDetectedFaults(solution) + + Assertions.assertTrue(faults.size == 1) + + val faultCategories = DetectedFaultUtils.getDetectedFaultCategories(solution) + + assertTrue({ DefinedFaultCategory.SQL_INJECTION in faultCategories }) + + assertTrue(faults.any { + it.category == DefinedFaultCategory.SQL_INJECTION + && it.operationId == "GET:/api/sqli/path/vulnerable/{id}" + }) + + assertFalse(faults.any { + it.category == DefinedFaultCategory.SQL_INJECTION + && it.operationId == "GET:/api/sqli/path/safe" + }) + + } + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiQueryH2EMTest.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiQueryH2EMTest.kt index fb53cfd805..2297cb5a98 100644 --- a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiQueryH2EMTest.kt +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiQueryH2EMTest.kt @@ -3,7 +3,6 @@ package org.evomaster.e2etests.spring.openapi.v3.security.sqli import com.foo.rest.examples.spring.openapi.v3.security.sqli.SQLiH2QueryController import com.webfuzzing.commons.faults.DefinedFaultCategory -import org.evomaster.core.EMConfig import org.evomaster.core.problem.enterprise.DetectedFaultUtils import org.evomaster.e2etests.spring.openapi.v3.SpringTestBase import org.junit.jupiter.api.Assertions @@ -16,11 +15,7 @@ class SQLiQueryH2EMTest : SpringTestBase() { @BeforeAll @JvmStatic fun init() { - val config = EMConfig() - config.instrumentMR_NET = false -// config.instrumentMR_SQL = false - - initClass(SQLiH2QueryController(), config) + initClass(SQLiH2QueryController()) } } diff --git a/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/body/BodySQLiApplication.kt b/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/body/BodySQLiApplication.kt new file mode 100644 index 0000000000..7848c2a7ce --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/body/BodySQLiApplication.kt @@ -0,0 +1,100 @@ +package com.foo.spring.rest.postgres.sqli.body + +import com.foo.spring.rest.postgres.SwaggerConfiguration +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.media.ArraySchema +import io.swagger.v3.oas.annotations.media.Content +import io.swagger.v3.oas.annotations.media.Schema +import io.swagger.v3.oas.annotations.parameters.RequestBody +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.responses.ApiResponses +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.SpringApplication +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.autoconfigure.domain.EntityScan +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration +import org.springframework.context.annotation.ComponentScan +import org.springframework.data.jpa.repository.config.EnableJpaRepositories +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import springfox.documentation.swagger2.annotations.EnableSwagger2 +import java.sql.Connection +import javax.annotation.PostConstruct +import javax.sql.DataSource + + +@EnableSwagger2 +@SpringBootApplication(exclude = [SecurityAutoConfiguration::class]) +@RequestMapping(path = ["/api/sqli/body"]) +@RestController +open class BodySQLiApplication: SwaggerConfiguration() { + + @Autowired + private lateinit var dataSource: DataSource + + @Autowired + private lateinit var userRepository: UserRepository + + private var connection: Connection? = null + + companion object { + @JvmStatic + fun main(args: Array) { + SpringApplication.run(BodySQLiApplication::class.java, *args) + } + } + + @PostConstruct + fun init() { + connection = dataSource.connection + initializeTestData() + } + + private fun initializeTestData() { + if (userRepository.count() == 0L) { + userRepository.save(UserEntity(null, "admin", "admin123")) + userRepository.save(UserEntity(null, "user1", "password1")) + } + } + + /** + * Safe endpoint - No SQL Injection vulnerability + */ + @GetMapping("/safe") + @Operation(summary = "Safe Query - No SQL Injection") + @ApiResponses(value = [ + ApiResponse(responseCode = "200", description = "Successful operation", + content = [Content(mediaType = "application/json", + array = ArraySchema(schema = Schema(implementation = UserDto::class)))]) + ]) + fun getSafeUsers(): ResponseEntity> { + val users = userRepository.findAll().map { UserDto(it.id, it.username) } + return ResponseEntity.ok(users) + } + + /** + * Attack: POST {"username":"admin' AND (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B, INFORMATION_SCHEMA.COLUMNS C)>0--","password":"x"} + */ + @PostMapping("/vulnerable") + @Operation(summary = "SQL Injection - Body Parameter") + @ApiResponses(value = [ + ApiResponse(responseCode = "200", description = "Successful operation", + content = [Content(mediaType = "text/plain", schema = Schema(implementation = String::class))]), + ApiResponse(responseCode = "500", description = "Internal server error", + content = [Content(mediaType = "text/plain", schema = Schema(implementation = String::class))]) + ]) + @RequestBody(description = "Login credentials", required = true, + content = [Content(mediaType = "application/json", + schema = Schema(implementation = LoginDto::class))]) + fun body(@org.springframework.web.bind.annotation.RequestBody loginDto: LoginDto): ResponseEntity { + return try { + val stmt = connection?.createStatement() + val rs = stmt?.executeQuery("SELECT COUNT(*) as cnt FROM users WHERE username = '${loginDto.username}' AND password = '${loginDto.password}'") + val count = if (rs?.next() == true) rs.getInt("cnt") else 0 + ResponseEntity.ok("MATCHED: $count") + } catch (e: Exception) { + ResponseEntity.status(500).body("ERROR: ${e.message}") + } + } + +} diff --git a/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/body/UserDto.kt b/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/body/UserDto.kt new file mode 100644 index 0000000000..cd673e477f --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/body/UserDto.kt @@ -0,0 +1,13 @@ +package com.foo.spring.rest.postgres.sqli.body + +import com.fasterxml.jackson.annotation.JsonProperty + +data class UserDto( + @JsonProperty("id") var id: Long? = null, + @JsonProperty("username") var username: String? = null, +) + +data class LoginDto( + @JsonProperty("username") var username: String = "", + @JsonProperty("password") var password: String = "" +) diff --git a/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/body/UserEntity.kt b/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/body/UserEntity.kt new file mode 100644 index 0000000000..57ab29162d --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/body/UserEntity.kt @@ -0,0 +1,19 @@ +package com.foo.spring.rest.postgres.sqli.body + +import javax.persistence.* + + +@Entity +@Table(name = "users") +open class UserEntity( + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + open var id: Long? = null, + + @Column(name = "username", unique = true, nullable = false) + open var username: String? = null, + + @Column(name = "password", nullable = false) + open var password: String? = null, +) diff --git a/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/body/UserRepository.kt b/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/body/UserRepository.kt new file mode 100644 index 0000000000..597efa2ff0 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/body/UserRepository.kt @@ -0,0 +1,10 @@ +package com.foo.spring.rest.postgres.sqli.body + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + + +@Repository +interface UserRepository : JpaRepository { + +} diff --git a/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/path/PathSQLiApplication.kt b/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/path/PathSQLiApplication.kt new file mode 100644 index 0000000000..b82f5ac4ba --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/path/PathSQLiApplication.kt @@ -0,0 +1,78 @@ +package com.foo.spring.rest.postgres.sqli.path + + +import com.foo.spring.rest.postgres.SwaggerConfiguration +import io.swagger.v3.oas.annotations.Operation +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.SpringApplication +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import springfox.documentation.swagger2.annotations.EnableSwagger2 +import java.sql.Connection +import javax.annotation.PostConstruct +import javax.sql.DataSource + + +@EnableSwagger2 +@SpringBootApplication(exclude = [SecurityAutoConfiguration::class]) +@RequestMapping(path = ["/api/sqli/path"]) +@RestController +open class PathSQLiApplication: SwaggerConfiguration() { + + @Autowired + private lateinit var dataSource: DataSource + + @Autowired + private lateinit var userRepository: UserRepository + + private var connection: Connection? = null + + companion object { + @JvmStatic + fun main(args: Array) { + SpringApplication.run(PathSQLiApplication::class.java, *args) + } + } + + @PostConstruct + fun init() { + connection = dataSource.connection + initializeTestData() + } + + private fun initializeTestData() { + if (userRepository.count() == 0L) { + userRepository.save(UserEntity(null, "admin", "admin123")) + userRepository.save(UserEntity(null, "user1", "password1")) + } + } + + /** + * Safe endpoint - No SQL Injection vulnerability + */ + @GetMapping("/safe") + @Operation(summary = "Safe Query - No SQL Injection") + fun getSafeUsers(): ResponseEntity> { + val users = userRepository.findAll().map { UserDto(it.id, it.username) } + return ResponseEntity.ok(users) + } + + /** + * Attack: GET /api/sqli/path/vulnerable/admin' OR (SELECT SUM(a.ORDINAL_POSITION*b.ORDINAL_POSITION*c.ORDINAL_POSITION) FROM INFORMATION_SCHEMA.COLUMNS a, INFORMATION_SCHEMA.COLUMNS b, INFORMATION_SCHEMA.COLUMNS c)>1 -- + */ + @GetMapping("/vulnerable/{id}") + @Operation(summary = "SQL Injection - Path Parameter") + fun timeBasedPath(@PathVariable id: String): ResponseEntity { + return try { + val stmt = connection?.createStatement() + val rs = stmt?.executeQuery("SELECT username FROM users WHERE username = '$id'") + val username = if (rs?.next() == true) rs.getString("username") else "NOT_FOUND" + ResponseEntity.ok("USERNAME: $username") + } catch (e: Exception) { + ResponseEntity.status(500).body("ERROR: ${e.message}") + } + } + +} diff --git a/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/path/UserDto.kt b/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/path/UserDto.kt new file mode 100644 index 0000000000..b08e138208 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/path/UserDto.kt @@ -0,0 +1,12 @@ +package com.foo.spring.rest.postgres.sqli.path + + +data class UserDto( + var id: Long? = null, + var username: String? = null, +) + +data class LoginDto( + var username: String = "", + var password: String = "" +) diff --git a/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/path/UserEntity.kt b/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/path/UserEntity.kt new file mode 100644 index 0000000000..b975cf0901 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/path/UserEntity.kt @@ -0,0 +1,19 @@ +package com.foo.spring.rest.postgres.sqli.path + +import javax.persistence.* + + +@Entity +@Table(name = "users") +open class UserEntity( + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + open var id: Long? = null, + + @Column(name = "username", unique = true, nullable = false) + open var username: String? = null, + + @Column(name = "password", nullable = false) + open var password: String? = null, +) diff --git a/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/path/UserRepository.kt b/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/path/UserRepository.kt new file mode 100644 index 0000000000..af6641136e --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/path/UserRepository.kt @@ -0,0 +1,10 @@ +package com.foo.spring.rest.postgres.sqli.path + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + + +@Repository +interface UserRepository : JpaRepository { + +} diff --git a/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/query/QuerySQLiApplication.kt b/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/query/QuerySQLiApplication.kt new file mode 100644 index 0000000000..75a110df1d --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/query/QuerySQLiApplication.kt @@ -0,0 +1,76 @@ +package com.foo.spring.rest.postgres.sqli.query + +import com.foo.spring.rest.postgres.SwaggerConfiguration +import io.swagger.v3.oas.annotations.Operation +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.SpringApplication +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* +import springfox.documentation.swagger2.annotations.EnableSwagger2 +import java.sql.Connection +import javax.annotation.PostConstruct +import javax.sql.DataSource + + +@EnableSwagger2 +@SpringBootApplication(exclude = [SecurityAutoConfiguration::class]) +@RequestMapping(path = ["/api/sqli/query"]) +open class QuerySQLiApplication: SwaggerConfiguration() { + + @Autowired + private lateinit var dataSource: DataSource + + @Autowired + private lateinit var userRepository: UserRepository + + private var connection: Connection? = null + + companion object { + @JvmStatic + fun main(args: Array) { + SpringApplication.run(QuerySQLiApplication::class.java, *args) + } + } + + @PostConstruct + fun init() { + connection = dataSource.connection + initializeTestData() + } + + private fun initializeTestData() { + if (userRepository.count() == 0L) { + userRepository.save(UserEntity(null, "admin", "admin123")) + userRepository.save(UserEntity(null, "user1", "password1")) + } + } + + /** + * Safe endpoint - No SQL Injection vulnerability + */ + @GetMapping("/safe") + @Operation(summary = "Safe Query - No SQL Injection") + fun getSafeUsers(): ResponseEntity> { + val users = userRepository.findAll().map { UserDto(it.id, it.username) } + return ResponseEntity.ok(users) + } + + /** + * Attack: GET /api/sqli/query/vulnerable?username=admin' OR (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B, INFORMATION_SCHEMA.COLUMNS C)>0-- + */ + @GetMapping("/vulnerable") + @Operation(summary = "SQL Injection - Query Parameter") + fun timeBasedQuery(@RequestParam username: String): ResponseEntity { + return try { + val stmt = connection?.createStatement() + val rs = stmt?.executeQuery("SELECT COUNT(*) as cnt FROM users WHERE username = '$username'") + val count = if (rs?.next() == true) rs.getInt("cnt") else 0 + ResponseEntity.ok("COUNT: $count") + } catch (e: Exception) { + ResponseEntity.status(500).body("ERROR: ${e.message}") + } + } + +} diff --git a/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/query/UserDto.kt b/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/query/UserDto.kt new file mode 100644 index 0000000000..dd353a82bf --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/query/UserDto.kt @@ -0,0 +1,12 @@ +package com.foo.spring.rest.postgres.sqli.query + + +data class UserDto( + var id: Long? = null, + var username: String? = null, +) + +data class LoginDto( + var username: String, + var password: String +) diff --git a/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/query/UserEntity.kt b/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/query/UserEntity.kt new file mode 100644 index 0000000000..2cca768353 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/query/UserEntity.kt @@ -0,0 +1,19 @@ +package com.foo.spring.rest.postgres.sqli.query + +import javax.persistence.* + + +@Entity +@Table(name = "users") +open class UserEntity( + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + open var id: Long? = null, + + @Column(name = "username", unique = true, nullable = false) + open var username: String? = null, + + @Column(name = "password", nullable = false) + open var password: String? = null, +) diff --git a/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/query/UserRepository.kt b/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/query/UserRepository.kt new file mode 100644 index 0000000000..c9f3afd349 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/kotlin/com/foo/spring/rest/postgres/sqli/query/UserRepository.kt @@ -0,0 +1,10 @@ +package com.foo.spring.rest.postgres.sqli.query + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + + +@Repository +interface UserRepository : JpaRepository { + +} diff --git a/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/resources/schema/sqli/V1.0__createDB.sql b/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/resources/schema/sqli/V1.0__createDB.sql new file mode 100644 index 0000000000..87abc561e2 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-postgres/src/main/resources/schema/sqli/V1.0__createDB.sql @@ -0,0 +1,7 @@ +CREATE TABLE users ( + id BIGSERIAL PRIMARY KEY, + username VARCHAR(255) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL +); + +CREATE SEQUENCE hibernate_sequence START WITH 1 INCREMENT BY 1; diff --git a/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/com/foo/spring/rest/postgres/sqli/SQLiPostgresBodyController.kt b/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/com/foo/spring/rest/postgres/sqli/SQLiPostgresBodyController.kt new file mode 100644 index 0000000000..54ebc9f929 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/com/foo/spring/rest/postgres/sqli/SQLiPostgresBodyController.kt @@ -0,0 +1,12 @@ +package com.foo.spring.rest.postgres.sqli + +import com.foo.spring.rest.postgres.SpringRestPostgresController +import com.foo.spring.rest.postgres.sqli.body.BodySQLiApplication +import org.evomaster.client.java.sql.DbSpecification + +class SQLiPostgresBodyController : SpringRestPostgresController(BodySQLiApplication::class.java){ + override fun pathToFlywayFiles() = "classpath:/schema/sqli" + override fun getDbSpecifications(): MutableList? { + return null + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/com/foo/spring/rest/postgres/sqli/SQLiPostgresPathController.kt b/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/com/foo/spring/rest/postgres/sqli/SQLiPostgresPathController.kt new file mode 100644 index 0000000000..05b6f1c98b --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/com/foo/spring/rest/postgres/sqli/SQLiPostgresPathController.kt @@ -0,0 +1,12 @@ +package com.foo.spring.rest.postgres.sqli + +import com.foo.spring.rest.postgres.SpringRestPostgresController +import com.foo.spring.rest.postgres.sqli.path.PathSQLiApplication +import org.evomaster.client.java.sql.DbSpecification + +class SQLiPostgresPathController : SpringRestPostgresController(PathSQLiApplication::class.java){ + override fun pathToFlywayFiles() = "classpath:/schema/sqli" + override fun getDbSpecifications(): MutableList? { + return null + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/com/foo/spring/rest/postgres/sqli/SQLiPostgresQueryController.kt b/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/com/foo/spring/rest/postgres/sqli/SQLiPostgresQueryController.kt new file mode 100644 index 0000000000..5ff8128cc4 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/com/foo/spring/rest/postgres/sqli/SQLiPostgresQueryController.kt @@ -0,0 +1,12 @@ +package com.foo.spring.rest.postgres.sqli + +import com.foo.spring.rest.postgres.SpringRestPostgresController +import com.foo.spring.rest.postgres.sqli.query.QuerySQLiApplication +import org.evomaster.client.java.sql.DbSpecification + +class SQLiPostgresQueryController : SpringRestPostgresController(QuerySQLiApplication::class.java){ + override fun pathToFlywayFiles() = "classpath:/schema/sqli" + override fun getDbSpecifications(): MutableList? { + return null + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/postgres/SQLiPostgresBodyEMTest.kt b/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/postgres/SQLiPostgresBodyEMTest.kt new file mode 100644 index 0000000000..69f9e5a1b0 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/postgres/SQLiPostgresBodyEMTest.kt @@ -0,0 +1,55 @@ +package org.evomaster.e2etests.spring.rest.postgres.postgres + +import com.foo.spring.rest.postgres.sqli.SQLiPostgresBodyController +import com.webfuzzing.commons.faults.DefinedFaultCategory +import org.evomaster.core.EMConfig +import org.evomaster.core.problem.enterprise.DetectedFaultUtils +import org.evomaster.e2etests.spring.rest.postgres.SpringRestPostgresTestBase +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test + +class SQLiPostgresBodyEMTest : SpringRestPostgresTestBase() { + + companion object { + @BeforeAll + @JvmStatic + fun init() { + initClass(SQLiPostgresBodyController()) + } + } + + @Test + fun testRunEM() { + + runTestHandlingFlakyAndCompilation( + "SQLiPostgresBodyEM", + 100 + ) { args -> + setOption(args, "security", "true") + + val solution = initAndRun(args) + assertTrue(solution.individuals.isNotEmpty()) + + val faults = DetectedFaultUtils.getDetectedFaults(solution) + + assertTrue(faults.size == 1) + + val faultCategories = DetectedFaultUtils.getDetectedFaultCategories(solution) + + assertTrue({ DefinedFaultCategory.SQL_INJECTION in faultCategories }) + + assertTrue(faults.any { + it.category == DefinedFaultCategory.SQL_INJECTION + && it.operationId == "POST:/api/sqli/body/vulnerable" + }) + + assertFalse(faults.any { + it.category == DefinedFaultCategory.SQL_INJECTION + && it.operationId == "GET:/api/sqli/body/safe" + }) + + } + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/postgres/SQLiPostgresPathEMTest.kt b/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/postgres/SQLiPostgresPathEMTest.kt new file mode 100644 index 0000000000..7ec8a77d4f --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/postgres/SQLiPostgresPathEMTest.kt @@ -0,0 +1,53 @@ +package org.evomaster.e2etests.spring.rest.postgres.postgres + +import com.foo.spring.rest.postgres.sqli.SQLiPostgresPathController +import com.webfuzzing.commons.faults.DefinedFaultCategory +import org.evomaster.core.EMConfig +import org.evomaster.core.problem.enterprise.DetectedFaultUtils +import org.evomaster.e2etests.spring.rest.postgres.SpringRestPostgresTestBase +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test + +class SQLiPostgresPathEMTest : SpringRestPostgresTestBase() { + + companion object { + @BeforeAll + @JvmStatic + fun init() { + initClass(SQLiPostgresPathController()) + } + } + + @Test + fun testRunEM() { + + runTestHandlingFlakyAndCompilation( + "SQLiMySQLPathEM", + 100 + ) { args -> + setOption(args, "security", "true") + + val solution = initAndRun(args) + assertTrue(solution.individuals.isNotEmpty()) + + val faults = DetectedFaultUtils.getDetectedFaults(solution) + + val faultCategories = DetectedFaultUtils.getDetectedFaultCategories(solution) + + assertTrue({ DefinedFaultCategory.SQL_INJECTION in faultCategories }) + + assertTrue(faults.any { + it.category == DefinedFaultCategory.SQL_INJECTION + && it.operationId == "GET:/api/sqli/path/vulnerable/{id}" + }) + + assertFalse(faults.any { + it.category == DefinedFaultCategory.SQL_INJECTION + && it.operationId == "GET:/api/sqli/path/safe" + }) + + } + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/postgres/SQLiPostgresQueryEMTest.kt b/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/postgres/SQLiPostgresQueryEMTest.kt new file mode 100644 index 0000000000..89344d1dd8 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/postgres/SQLiPostgresQueryEMTest.kt @@ -0,0 +1,55 @@ +package org.evomaster.e2etests.spring.rest.postgres.postgres + +import com.foo.spring.rest.postgres.sqli.SQLiPostgresQueryController +import com.webfuzzing.commons.faults.DefinedFaultCategory +import org.evomaster.core.EMConfig +import org.evomaster.core.problem.enterprise.DetectedFaultUtils +import org.evomaster.e2etests.spring.rest.postgres.SpringRestPostgresTestBase +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test + +class SQLiPostgresQueryEMTest : SpringRestPostgresTestBase() { + + companion object { + @BeforeAll + @JvmStatic + fun init() { + initClass(SQLiPostgresQueryController()) + } + } + + @Test + fun testRunEM() { + + runTestHandlingFlakyAndCompilation( + "SQLiMySQLQueryEM", + 100 + ) { args -> + setOption(args, "security", "true") + + val solution = initAndRun(args) + assertTrue(solution.individuals.isNotEmpty()) + + val faults = DetectedFaultUtils.getDetectedFaults(solution) + + assertTrue(faults.size == 1) + + val faultCategories = DetectedFaultUtils.getDetectedFaultCategories(solution) + + assertTrue({ DefinedFaultCategory.SQL_INJECTION in faultCategories }) + + assertTrue(faults.any { + it.category == DefinedFaultCategory.SQL_INJECTION + && it.operationId == "GET:/api/sqli/query/vulnerable" + }) + + assertFalse(faults.any { + it.category == DefinedFaultCategory.SQL_INJECTION + && it.operationId == "GET:/api/sqli/query/safe" + }) + + } + } +} diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt index c858031eaa..73075655be 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt @@ -229,6 +229,10 @@ object RestSecurityOracle { "\" OR SLEEP(5)-- -", "' union select sleep(5)-- -", "\" union select sleep(5)-- -", + "' OR pg_sleep(5)-- -", + "\" OR pg_sleep(5)-- -", + "' union select pg_sleep(5)-- -", + "\" union select pg_sleep(5)-- -", // for h2 database // "' OR (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B, INFORMATION_SCHEMA.COLUMNS C)>0--", // "' OR (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B)>0--", From 09142fc9401140c91ac6bd47c297a352dcbb52b1 Mon Sep 17 00:00:00 2001 From: Omur Sahin Date: Wed, 26 Nov 2025 19:05:43 +0300 Subject: [PATCH 06/13] import fix --- .../org/evomaster/core/problem/rest/service/SecurityRest.kt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/SecurityRest.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/SecurityRest.kt index 4368ec3d02..5db71ac50c 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/SecurityRest.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/SecurityRest.kt @@ -6,8 +6,6 @@ import org.evomaster.core.EMConfig import javax.annotation.PostConstruct import org.evomaster.core.logging.LoggingUtil -import org.evomaster.core.problem.enterprise.DetectedFault -import org.evomaster.core.problem.api.param.Param import org.evomaster.core.problem.enterprise.DetectedFaultUtils import org.evomaster.core.problem.enterprise.ExperimentalFaultCategory import org.evomaster.core.problem.enterprise.SampleType @@ -22,16 +20,12 @@ import org.evomaster.core.problem.rest.builder.RestIndividualSelectorUtils import org.evomaster.core.problem.rest.data.* import org.evomaster.core.problem.rest.oracle.RestSecurityOracle.SQLI_PAYLOADS import org.evomaster.core.problem.rest.oracle.RestSecurityOracle.XSS_PAYLOADS -import org.evomaster.core.problem.rest.param.BodyParam import org.evomaster.core.problem.rest.param.PathParam -import org.evomaster.core.problem.rest.param.QueryParam import org.evomaster.core.problem.rest.resource.RestResourceCalls import org.evomaster.core.problem.rest.service.sampler.AbstractRestSampler import org.evomaster.core.search.gene.string.StringGene import org.evomaster.core.search.* -import org.evomaster.core.search.action.ActionResult -import org.evomaster.core.search.gene.string.StringGene import org.evomaster.core.search.gene.utils.GeneUtils import org.evomaster.core.search.service.Archive import org.evomaster.core.search.service.FitnessFunction From b0b178a3d89278a5d7e9912cba99c580a5cda75e Mon Sep 17 00:00:00 2001 From: Omur Sahin Date: Tue, 2 Dec 2025 21:31:21 +0300 Subject: [PATCH 07/13] sqli updates --- .../spring/rest/sqli/SQLiMySQLBodyEMTest.kt | 1 + .../spring/rest/sqli/SQLiMySQLPathEMTest.kt | 1 + .../spring/rest/sqli/SQLiMySQLQueryEMTest.kt | 1 + .../spring/spring-rest-openapi-v3/pom.xml | 9 --------- .../openapi/v3/security/sqli/SQLiBodyH2EMTest.kt | 1 + .../openapi/v3/security/sqli/SQLiPathH2EMTest.kt | 1 + .../v3/security/sqli/SQLiQueryH2EMTest.kt | 1 + .../{postgres => sqli}/SQLiPostgresBodyEMTest.kt | 4 ++-- .../{postgres => sqli}/SQLiPostgresPathEMTest.kt | 6 +++--- .../SQLiPostgresQueryEMTest.kt | 6 +++--- .../main/kotlin/org/evomaster/core/EMConfig.kt | 14 ++++++++++++++ .../core/problem/httpws/HttpWsCallResult.kt | 2 +- .../problem/rest/oracle/RestSecurityOracle.kt | 6 +++++- .../core/problem/rest/service/SecurityRest.kt | 14 ++++++++++---- .../rest/service/fitness/AbstractRestFitness.kt | 16 ++++++++-------- 15 files changed, 52 insertions(+), 31 deletions(-) rename core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/{postgres => sqli}/SQLiPostgresBodyEMTest.kt (94%) rename core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/{postgres => sqli}/SQLiPostgresPathEMTest.kt (92%) rename core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/{postgres => sqli}/SQLiPostgresQueryEMTest.kt (92%) diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/sqli/SQLiMySQLBodyEMTest.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/sqli/SQLiMySQLBodyEMTest.kt index 2cad7ecf60..602543528c 100644 --- a/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/sqli/SQLiMySQLBodyEMTest.kt +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/sqli/SQLiMySQLBodyEMTest.kt @@ -27,6 +27,7 @@ class SQLiMySQLBodyEMTest : SpringTestBase() { 100 ) { args -> setOption(args, "security", "true") + setOption(args, "sqli", "true") val solution = initAndRun(args) assertTrue(solution.individuals.isNotEmpty()) diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/sqli/SQLiMySQLPathEMTest.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/sqli/SQLiMySQLPathEMTest.kt index a8afa170e0..806f631807 100644 --- a/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/sqli/SQLiMySQLPathEMTest.kt +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/sqli/SQLiMySQLPathEMTest.kt @@ -27,6 +27,7 @@ class SQLiMySQLPathEMTest : SpringTestBase() { 100 ) { args -> setOption(args, "security", "true") + setOption(args, "sqli", "true") val solution = initAndRun(args) assertTrue(solution.individuals.isNotEmpty()) diff --git a/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/sqli/SQLiMySQLQueryEMTest.kt b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/sqli/SQLiMySQLQueryEMTest.kt index 2226a7f373..be34064f50 100644 --- a/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/sqli/SQLiMySQLQueryEMTest.kt +++ b/core-tests/e2e-tests/spring/spring-rest-mysql/src/test/kotlin/org/evomaster/e2etests/spring/rest/sqli/SQLiMySQLQueryEMTest.kt @@ -27,6 +27,7 @@ class SQLiMySQLQueryEMTest : SpringTestBase() { 100 ) { args -> setOption(args, "security", "true") + setOption(args, "sqli", "true") val solution = initAndRun(args) assertTrue(solution.individuals.isNotEmpty()) diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/pom.xml b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/pom.xml index aa43017847..a8c998a09c 100644 --- a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/pom.xml +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/pom.xml @@ -149,15 +149,6 @@ com.ethlo.time itu - - - org.postgresql - postgresql - - - mysql - mysql-connector-java - diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiBodyH2EMTest.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiBodyH2EMTest.kt index bf51ac0ec7..ac1df936d5 100644 --- a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiBodyH2EMTest.kt +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiBodyH2EMTest.kt @@ -27,6 +27,7 @@ class SQLiBodyH2EMTest : SpringTestBase() { ) { args: MutableList -> setOption(args, "security", "true") + setOption(args, "sqli", "true") val solution = initAndRun(args) diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiPathH2EMTest.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiPathH2EMTest.kt index b39f17d32c..4da48c21be 100644 --- a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiPathH2EMTest.kt +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiPathH2EMTest.kt @@ -29,6 +29,7 @@ class SQLiPathH2EMTest : SpringTestBase() { ) { args: MutableList -> setOption(args, "security", "true") + setOption(args, "sqli", "true") val solution = initAndRun(args) diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiQueryH2EMTest.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiQueryH2EMTest.kt index 2297cb5a98..501ba40f67 100644 --- a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiQueryH2EMTest.kt +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiQueryH2EMTest.kt @@ -27,6 +27,7 @@ class SQLiQueryH2EMTest : SpringTestBase() { ) { args: MutableList -> setOption(args, "security", "true") + setOption(args, "sqli", "true") val solution = initAndRun(args) diff --git a/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/postgres/SQLiPostgresBodyEMTest.kt b/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/sqli/SQLiPostgresBodyEMTest.kt similarity index 94% rename from core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/postgres/SQLiPostgresBodyEMTest.kt rename to core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/sqli/SQLiPostgresBodyEMTest.kt index 69f9e5a1b0..470bf6a18c 100644 --- a/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/postgres/SQLiPostgresBodyEMTest.kt +++ b/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/sqli/SQLiPostgresBodyEMTest.kt @@ -1,8 +1,7 @@ -package org.evomaster.e2etests.spring.rest.postgres.postgres +package org.evomaster.e2etests.spring.rest.postgres.sqli import com.foo.spring.rest.postgres.sqli.SQLiPostgresBodyController import com.webfuzzing.commons.faults.DefinedFaultCategory -import org.evomaster.core.EMConfig import org.evomaster.core.problem.enterprise.DetectedFaultUtils import org.evomaster.e2etests.spring.rest.postgres.SpringRestPostgresTestBase import org.junit.jupiter.api.Assertions.assertFalse @@ -28,6 +27,7 @@ class SQLiPostgresBodyEMTest : SpringRestPostgresTestBase() { 100 ) { args -> setOption(args, "security", "true") + setOption(args, "sqli", "true") val solution = initAndRun(args) assertTrue(solution.individuals.isNotEmpty()) diff --git a/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/postgres/SQLiPostgresPathEMTest.kt b/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/sqli/SQLiPostgresPathEMTest.kt similarity index 92% rename from core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/postgres/SQLiPostgresPathEMTest.kt rename to core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/sqli/SQLiPostgresPathEMTest.kt index 7ec8a77d4f..12b8bde0fc 100644 --- a/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/postgres/SQLiPostgresPathEMTest.kt +++ b/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/sqli/SQLiPostgresPathEMTest.kt @@ -1,8 +1,7 @@ -package org.evomaster.e2etests.spring.rest.postgres.postgres +package org.evomaster.e2etests.spring.rest.postgres.sqli import com.foo.spring.rest.postgres.sqli.SQLiPostgresPathController import com.webfuzzing.commons.faults.DefinedFaultCategory -import org.evomaster.core.EMConfig import org.evomaster.core.problem.enterprise.DetectedFaultUtils import org.evomaster.e2etests.spring.rest.postgres.SpringRestPostgresTestBase import org.junit.jupiter.api.Assertions.assertFalse @@ -24,10 +23,11 @@ class SQLiPostgresPathEMTest : SpringRestPostgresTestBase() { fun testRunEM() { runTestHandlingFlakyAndCompilation( - "SQLiMySQLPathEM", + "SQLiPostgresPathEM", 100 ) { args -> setOption(args, "security", "true") + setOption(args, "sqli", "true") val solution = initAndRun(args) assertTrue(solution.individuals.isNotEmpty()) diff --git a/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/postgres/SQLiPostgresQueryEMTest.kt b/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/sqli/SQLiPostgresQueryEMTest.kt similarity index 92% rename from core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/postgres/SQLiPostgresQueryEMTest.kt rename to core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/sqli/SQLiPostgresQueryEMTest.kt index 89344d1dd8..1a14f0ac0d 100644 --- a/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/postgres/SQLiPostgresQueryEMTest.kt +++ b/core-tests/e2e-tests/spring/spring-rest-postgres/src/test/kotlin/org/evomaster/e2etests/spring/rest/postgres/sqli/SQLiPostgresQueryEMTest.kt @@ -1,8 +1,7 @@ -package org.evomaster.e2etests.spring.rest.postgres.postgres +package org.evomaster.e2etests.spring.rest.postgres.sqli import com.foo.spring.rest.postgres.sqli.SQLiPostgresQueryController import com.webfuzzing.commons.faults.DefinedFaultCategory -import org.evomaster.core.EMConfig import org.evomaster.core.problem.enterprise.DetectedFaultUtils import org.evomaster.e2etests.spring.rest.postgres.SpringRestPostgresTestBase import org.junit.jupiter.api.Assertions.assertFalse @@ -24,10 +23,11 @@ class SQLiPostgresQueryEMTest : SpringRestPostgresTestBase() { fun testRunEM() { runTestHandlingFlakyAndCompilation( - "SQLiMySQLQueryEM", + "SQLiPostgresQueryEM", 100 ) { args -> setOption(args, "security", "true") + setOption(args, "sqli", "true") val solution = initAndRun(args) assertTrue(solution.individuals.isNotEmpty()) diff --git a/core/src/main/kotlin/org/evomaster/core/EMConfig.kt b/core/src/main/kotlin/org/evomaster/core/EMConfig.kt index 9e9815d621..ce59554d3d 100644 --- a/core/src/main/kotlin/org/evomaster/core/EMConfig.kt +++ b/core/src/main/kotlin/org/evomaster/core/EMConfig.kt @@ -2604,9 +2604,15 @@ class EMConfig { @Experimental @Cfg("Maximum response time (in milliseconds) to consider a potential SQL Injection vulnerability.") var sqlInjectionMaxResponseTimeMs = 2000 + + @Experimental @Cfg("To apply XSS detection as part of security testing.") var xss = false + @Experimental + @Cfg("To apply SQLi detection as part of security testing.") + var sqli = false + @Regex(faultCodeRegex) @Cfg("Disable oracles. Provide a comma-separated list of codes to disable. " + "By default, all oracles are enabled." @@ -2949,6 +2955,14 @@ class EMConfig { * Some might be experimental, while others might be explicitly excluded by the user */ fun isEnabledFaultCategory(category: FaultCategory) : Boolean{ + if(category == DefinedFaultCategory.XSS && !xss){ + return false; + } + + if(category == DefinedFaultCategory.SQL_INJECTION && !sqli){ + return false; + } + return category !in getDisabledOracleCodesList() } diff --git a/core/src/main/kotlin/org/evomaster/core/problem/httpws/HttpWsCallResult.kt b/core/src/main/kotlin/org/evomaster/core/problem/httpws/HttpWsCallResult.kt index 574c89d09f..95cb3ed28d 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/httpws/HttpWsCallResult.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/httpws/HttpWsCallResult.kt @@ -128,6 +128,6 @@ abstract class HttpWsCallResult : EnterpriseActionResult { fun setVulnerableForSSRF(on: Boolean) = addResultValue(VULNERABLE_SSRF, on.toString()) fun getVulnerableForSSRF() : Boolean = getResultValue(VULNERABLE_SSRF)?.toBoolean() ?: false - fun setResponseTime(responseTime: Long) = addResultValue(RESPONSE_TIME, responseTime.toString()) + fun setResponseTime(responseTime: Long?) = addResultValue(RESPONSE_TIME, responseTime.toString()) fun getResponseTime(): Long = getResultValue(RESPONSE_TIME)?.toLong() ?: 0 } diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt index aa2c4b8a94..3c2da55c49 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt @@ -222,7 +222,11 @@ object RestSecurityOracle { return true } - // Simple SQLi payloads + /** + * Simple SQLi payloads. Used to check for SQL Injection vulnerability. + * The payloads are designed to introduce delays in the database response, + * which can be detected by measuring the response time of the application. + */ val SQLI_PAYLOADS = listOf( "' OR (WITH RECURSIVE r(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM r WHERE i < 10000000) SELECT COUNT(*) FROM r)>0--", "' OR SLEEP(5)-- -", diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/SecurityRest.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/SecurityRest.kt index e2b469e766..939eaf83d3 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/SecurityRest.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/SecurityRest.kt @@ -283,7 +283,7 @@ class SecurityRest { handleNotRecognizedAuthenticated() } - if (!config.xss || !config.isEnabledFaultCategory(DefinedFaultCategory.XSS)) { + if (!config.isEnabledFaultCategory(DefinedFaultCategory.XSS)) { LoggingUtil.uniqueUserInfo("Skipping security test for XSS as disabled in configuration") } else { handleXSSCheck() @@ -369,11 +369,17 @@ class SecurityRest { val leafGene = gene.getLeafGene() if(leafGene !is StringGene) return@forEach - // we need to do this way because we need to append our paylod + // we need to do this way because we need to append our payload + + //check invalid chars val hasInvalidChars = leafGene.invalidChars.any { payload.contains(it) } - if(!hasInvalidChars){ + + //check max length + val hasMaxLength = (leafGene.getPhenotype().getValueAsRawString().length + payload.length) > leafGene.maxLength + + if(!hasInvalidChars && !hasMaxLength){ // append the SQLi payload value - leafGene.value = leafGene.value + payload + leafGene.getPhenotype().setFromStringValue(leafGene.getPhenotype().getValueAsRawString() + payload) anySuccess = true } } diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/fitness/AbstractRestFitness.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/fitness/AbstractRestFitness.kt index ff502ad395..7344b7071c 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/fitness/AbstractRestFitness.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/fitness/AbstractRestFitness.kt @@ -597,11 +597,15 @@ abstract class AbstractRestFitness : HttpWsFitness() { val appliedLink = handleLinks(a, all,actionResults) val response = try { + val call = createInvocation(a, chainState, cookies, tokens) + SearchTimeController.measureTimeMillis( { t, res -> rcr.setResponseTime(t) }, - {createInvocation(a, chainState, cookies, tokens).invoke()} + { + call.invoke() + } ) } catch (e: ProcessingException) { @@ -1360,7 +1364,7 @@ abstract class AbstractRestFitness : HttpWsFitness() { actionResults: List, fv: FitnessValue ) { - if (!config.isEnabledFaultCategory(DefinedFaultCategory.XSS)) { + if (!config.isEnabledFaultCategory(DefinedFaultCategory.SQL_INJECTION)) { return } @@ -1370,11 +1374,7 @@ abstract class AbstractRestFitness : HttpWsFitness() { val r = actionResults.find { it.sourceLocalId == a.getLocalId() } as? RestCallResult ?: continue -// if(!r.getTimedout()){ -// continue -// } - - if(r.getResponseTime() < config.sqlInjectionMaxResponseTimeMs && !r.getTimedout()){ + if(r.getResponseTime() < config.sqlInjectionMaxResponseTimeMs && !r.getTimedout()) { continue } @@ -1415,7 +1415,7 @@ abstract class AbstractRestFitness : HttpWsFitness() { actionResults: List, fv: FitnessValue ) { - if(!config.xss || !config.isEnabledFaultCategory(DefinedFaultCategory.XSS)){ + if(!config.isEnabledFaultCategory(DefinedFaultCategory.XSS)){ return } From 712c89c0512ebd27e82bdfb7406e1960b97c218a Mon Sep 17 00:00:00 2001 From: Omur Sahin Date: Tue, 2 Dec 2025 23:19:46 +0300 Subject: [PATCH 08/13] doc --- docs/options.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/options.md b/docs/options.md index 831cd48928..f2a3e760d0 100644 --- a/docs/options.md +++ b/docs/options.md @@ -310,6 +310,7 @@ There are 3 types of options: |`seedTestCasesFormat`| __Enum__. Format of the test cases seeded to EvoMaster. *Valid values*: `POSTMAN`. *Default value*: `POSTMAN`.| |`seedTestCasesPath`| __String__. File path where the seeded test cases are located. *Default value*: `postman.postman_collection.json`.| |`sqlInjectionMaxResponseTimeMs`| __Int__. Maximum response time (in milliseconds) to consider a potential SQL Injection vulnerability. *Default value*: `2000`.| +|`sqli`| __Boolean__. To apply SQLi detection as part of security testing. *Default value*: `false`.| |`ssrf`| __Boolean__. To apply SSRF detection as part of security testing. *Default value*: `false`.| |`structureMutationProFS`| __Double__. Specify a probability of applying structure mutator during the focused search. *Constraints*: `probability 0.0-1.0`. *Default value*: `0.0`.| |`structureMutationProbStrategy`| __Enum__. Specify a strategy to handle a probability of applying structure mutator during the focused search. *Valid values*: `SPECIFIED, SPECIFIED_FS, DPC_TO_SPECIFIED_BEFORE_FS, DPC_TO_SPECIFIED_AFTER_FS, ADAPTIVE_WITH_IMPACT`. *Default value*: `SPECIFIED`.| From f0a82ac46b683bf853ce493eaa6ad5ea4d256b5b Mon Sep 17 00:00:00 2001 From: Omur Date: Wed, 3 Dec 2025 16:44:08 +0300 Subject: [PATCH 09/13] new config parameters for sqli --- .../main/kotlin/org/evomaster/core/EMConfig.kt | 17 +++++++++++++---- .../problem/rest/oracle/RestSecurityOracle.kt | 18 +++++++++--------- .../core/problem/rest/service/SecurityRest.kt | 2 +- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/core/src/main/kotlin/org/evomaster/core/EMConfig.kt b/core/src/main/kotlin/org/evomaster/core/EMConfig.kt index ce59554d3d..0f2053e6e0 100644 --- a/core/src/main/kotlin/org/evomaster/core/EMConfig.kt +++ b/core/src/main/kotlin/org/evomaster/core/EMConfig.kt @@ -2601,10 +2601,6 @@ class EMConfig { @Cfg("To apply SSRF detection as part of security testing.") var ssrf = false - @Experimental - @Cfg("Maximum response time (in milliseconds) to consider a potential SQL Injection vulnerability.") - var sqlInjectionMaxResponseTimeMs = 2000 - @Experimental @Cfg("To apply XSS detection as part of security testing.") var xss = false @@ -2613,6 +2609,19 @@ class EMConfig { @Cfg("To apply SQLi detection as part of security testing.") var sqli = false + @Experimental + @Cfg("Maximum response time (in milliseconds) to consider a potential SQL Injection vulnerability.") + var sqlInjectionMaxResponseTimeMs = 2000 + + @Experimental + @Cfg("Injected sleep duration (in seconds) used inside the malicious payload to detect time-based vulnerabilities.") + var sqliInjectedSleepDuration = 5 + + @Experimental + @Cfg("Maximum allowed baseline response time (in milliseconds) before the malicious payload is applied.") + var sqliBaselineMaxResponseTimeMs = 2000 + + @Regex(faultCodeRegex) @Cfg("Disable oracles. Provide a comma-separated list of codes to disable. " + "By default, all oracles are enabled." diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt index 3c2da55c49..fe983e3ff2 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt @@ -229,14 +229,14 @@ object RestSecurityOracle { */ val SQLI_PAYLOADS = listOf( "' OR (WITH RECURSIVE r(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM r WHERE i < 10000000) SELECT COUNT(*) FROM r)>0--", - "' OR SLEEP(5)-- -", - "\" OR SLEEP(5)-- -", - "' union select sleep(5)-- -", - "\" union select sleep(5)-- -", - "' OR pg_sleep(5)-- -", - "\" OR pg_sleep(5)-- -", - "' union select pg_sleep(5)-- -", - "\" union select pg_sleep(5)-- -", + "' OR SLEEP(%d)-- -", + "\" OR SLEEP(%d)-- -", + "' union select sleep(%d)-- -", + "\" union select sleep(%d)-- -", + "' OR pg_sleep(%d)-- -", + "\" OR pg_sleep(%d)-- -", + "' union select pg_sleep(%d)-- -", + "\" union select pg_sleep(%d)-- -" // for h2 database // "' OR (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B, INFORMATION_SCHEMA.COLUMNS C)>0--", // "' OR (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B)>0--", @@ -244,7 +244,7 @@ object RestSecurityOracle { // "' OR (SELECT SUM(a.ORDINAL_POSITION*b.ORDINAL_POSITION*c.ORDINAL_POSITION) FROM INFORMATION_SCHEMA.COLUMNS a, INFORMATION_SCHEMA.COLUMNS b, INFORMATION_SCHEMA.COLUMNS c)>1 --", ) - + // Simple XSS payloads inspired by big-list-of-naughty-strings // https://github.com/minimaxir/big-list-of-naughty-strings/blob/master/blns.txt val XSS_PAYLOADS = listOf( diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/SecurityRest.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/SecurityRest.kt index 939eaf83d3..e3789ed6ce 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/SecurityRest.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/SecurityRest.kt @@ -379,7 +379,7 @@ class SecurityRest { if(!hasInvalidChars && !hasMaxLength){ // append the SQLi payload value - leafGene.getPhenotype().setFromStringValue(leafGene.getPhenotype().getValueAsRawString() + payload) + leafGene.getPhenotype().setFromStringValue(leafGene.getPhenotype().getValueAsRawString() + String.format(payload, config.sqliInjectedSleepDuration)) anySuccess = true } } From a60108ecb3fd088bae829c3c7d044c50687f271a Mon Sep 17 00:00:00 2001 From: Omur Date: Fri, 5 Dec 2025 22:23:58 +0300 Subject: [PATCH 10/13] n-k update --- .../security/sqli/body/BodySQLiApplication.kt | 84 ------------------- .../v3/security/sqli/common/UserDto.kt | 12 --- .../v3/security/sqli/common/UserEntity.kt | 19 ----- .../v3/security/sqli/common/UserRepository.kt | 10 --- .../security/sqli/path/PathSQLiApplication.kt | 83 ------------------ .../sqli/query/QuerySQLiApplication.kt | 83 ------------------ .../v3/security/sqli/SQLiH2BodyController.kt | 5 -- .../v3/security/sqli/SQLiH2PathController.kt | 5 -- .../v3/security/sqli/SQLiH2QueryController.kt | 5 -- .../v3/security/sqli/SQLiBodyH2EMTest.kt | 57 ------------- .../v3/security/sqli/SQLiPathH2EMTest.kt | 59 ------------- .../v3/security/sqli/SQLiQueryH2EMTest.kt | 57 ------------- .../core/problem/rest/data/RestCallAction.kt | 3 +- .../core/problem/rest/service/SecurityRest.kt | 3 +- .../service/fitness/AbstractRestFitness.kt | 20 ++++- 15 files changed, 22 insertions(+), 483 deletions(-) delete mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/body/BodySQLiApplication.kt delete mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/common/UserDto.kt delete mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/common/UserEntity.kt delete mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/common/UserRepository.kt delete mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/path/PathSQLiApplication.kt delete mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/query/QuerySQLiApplication.kt delete mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiH2BodyController.kt delete mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiH2PathController.kt delete mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiH2QueryController.kt delete mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiBodyH2EMTest.kt delete mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiPathH2EMTest.kt delete mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiQueryH2EMTest.kt diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/body/BodySQLiApplication.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/body/BodySQLiApplication.kt deleted file mode 100644 index a017eb4ffa..0000000000 --- a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/body/BodySQLiApplication.kt +++ /dev/null @@ -1,84 +0,0 @@ -package com.foo.rest.examples.spring.openapi.v3.security.sqli.body - -import com.foo.rest.examples.spring.openapi.v3.security.sqli.common.LoginDto -import com.foo.rest.examples.spring.openapi.v3.security.sqli.common.UserDto -import com.foo.rest.examples.spring.openapi.v3.security.sqli.common.UserEntity -import com.foo.rest.examples.spring.openapi.v3.security.sqli.common.UserRepository -import io.swagger.v3.oas.annotations.Operation -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.SpringApplication -import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.boot.autoconfigure.domain.EntityScan -import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration -import org.springframework.context.annotation.ComponentScan -import org.springframework.data.jpa.repository.config.EnableJpaRepositories -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.* -import java.sql.Connection -import javax.annotation.PostConstruct -import javax.sql.DataSource - - -@SpringBootApplication(exclude = [SecurityAutoConfiguration::class]) -@RequestMapping(path = ["/api/sqli/body"]) -@RestController -@ComponentScan(basePackages = ["com.foo.rest.examples.spring.openapi.v3.security.sqli.common", "com.foo.rest.examples.spring.openapi.v3.security.sqli.body"]) -@EnableJpaRepositories(basePackages = ["com.foo.rest.examples.spring.openapi.v3.security.sqli.common"]) -@EntityScan(basePackages = ["com.foo.rest.examples.spring.openapi.v3.security.sqli.common"]) -open class BodySQLiApplication { - - @Autowired - private lateinit var dataSource: DataSource - - @Autowired - private lateinit var userRepository: UserRepository - - private var connection: Connection? = null - - companion object { - @JvmStatic - fun main(args: Array) { - SpringApplication.run(BodySQLiApplication::class.java, *args) - } - } - - @PostConstruct - fun init() { - connection = dataSource.connection - initializeTestData() - } - - private fun initializeTestData() { - if (userRepository.count() == 0L) { - userRepository.save(UserEntity(null, "admin", "admin123")) - userRepository.save(UserEntity(null, "user1", "password1")) - } - } - - /** - * Safe endpoint - No SQL Injection vulnerability - */ - @GetMapping("/safe") - @Operation(summary = "Safe Query - No SQL Injection") - fun getSafeUsers(): ResponseEntity> { - val users = userRepository.findAll().map { UserDto(it.id, it.username) } - return ResponseEntity.ok(users) - } - - /** - * Attack: POST {"username":"admin' AND (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B, INFORMATION_SCHEMA.COLUMNS C)>0--","password":"x"} - */ - @PostMapping("/vulnerable") - @Operation(summary = "SQL Injection - Body Parameter") - fun body(@RequestBody loginDto: LoginDto): ResponseEntity { - return try { - val stmt = connection?.createStatement() - val rs = stmt?.executeQuery("SELECT COUNT(*) as cnt FROM users WHERE username = '${loginDto.username}' AND password = '${loginDto.password}'") - val count = if (rs?.next() == true) rs.getInt("cnt") else 0 - ResponseEntity.ok("MATCHED: $count") - } catch (e: Exception) { - ResponseEntity.status(500).body("ERROR: ${e.message}") - } - } - -} diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/common/UserDto.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/common/UserDto.kt deleted file mode 100644 index 784796e301..0000000000 --- a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/common/UserDto.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.foo.rest.examples.spring.openapi.v3.security.sqli.common - - -data class UserDto( - var id: Long? = null, - var username: String? = null, -) - -data class LoginDto( - var username: String, - var password: String -) diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/common/UserEntity.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/common/UserEntity.kt deleted file mode 100644 index 91c903db62..0000000000 --- a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/common/UserEntity.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.foo.rest.examples.spring.openapi.v3.security.sqli.common - -import javax.persistence.* - - -@Entity -@Table(name = "users") -open class UserEntity( - - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - open var id: Long? = null, - - @Column(name = "username", unique = true, nullable = false) - open var username: String? = null, - - @Column(name = "password", nullable = false) - open var password: String? = null, -) diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/common/UserRepository.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/common/UserRepository.kt deleted file mode 100644 index d3da6d311e..0000000000 --- a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/common/UserRepository.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.foo.rest.examples.spring.openapi.v3.security.sqli.common - -import org.springframework.data.jpa.repository.JpaRepository -import org.springframework.stereotype.Repository - - -@Repository -interface UserRepository : JpaRepository { - -} diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/path/PathSQLiApplication.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/path/PathSQLiApplication.kt deleted file mode 100644 index 79831d1d81..0000000000 --- a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/path/PathSQLiApplication.kt +++ /dev/null @@ -1,83 +0,0 @@ -package com.foo.rest.examples.spring.openapi.v3.security.sqli.path - -import com.foo.rest.examples.spring.openapi.v3.security.sqli.common.UserDto -import com.foo.rest.examples.spring.openapi.v3.security.sqli.common.UserEntity -import com.foo.rest.examples.spring.openapi.v3.security.sqli.common.UserRepository -import io.swagger.v3.oas.annotations.Operation -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.SpringApplication -import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.boot.autoconfigure.domain.EntityScan -import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration -import org.springframework.context.annotation.ComponentScan -import org.springframework.data.jpa.repository.config.EnableJpaRepositories -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.* -import java.sql.Connection -import javax.annotation.PostConstruct -import javax.sql.DataSource - - -@SpringBootApplication(exclude = [SecurityAutoConfiguration::class]) -@RequestMapping(path = ["/api/sqli/path"]) -@RestController -@ComponentScan(basePackages = ["com.foo.rest.examples.spring.openapi.v3.security.sqli.common", "com.foo.rest.examples.spring.openapi.v3.security.sqli.path"]) -@EnableJpaRepositories(basePackages = ["com.foo.rest.examples.spring.openapi.v3.security.sqli.common"]) -@EntityScan(basePackages = ["com.foo.rest.examples.spring.openapi.v3.security.sqli.common"]) -open class PathSQLiApplication { - - @Autowired - private lateinit var dataSource: DataSource - - @Autowired - private lateinit var userRepository: UserRepository - - private var connection: Connection? = null - - companion object { - @JvmStatic - fun main(args: Array) { - SpringApplication.run(PathSQLiApplication::class.java, *args) - } - } - - @PostConstruct - fun init() { - connection = dataSource.connection - initializeTestData() - } - - private fun initializeTestData() { - if (userRepository.count() == 0L) { - userRepository.save(UserEntity(null, "admin", "admin123")) - userRepository.save(UserEntity(null, "user1", "password1")) - } - } - - /** - * Safe endpoint - No SQL Injection vulnerability - */ - @GetMapping("/safe") - @Operation(summary = "Safe Query - No SQL Injection") - fun getSafeUsers(): ResponseEntity> { - val users = userRepository.findAll().map { UserDto(it.id, it.username) } - return ResponseEntity.ok(users) - } - - /** - * Attack: GET /api/sqli/path/vulnerable/admin' OR (SELECT SUM(a.ORDINAL_POSITION*b.ORDINAL_POSITION*c.ORDINAL_POSITION) FROM INFORMATION_SCHEMA.COLUMNS a, INFORMATION_SCHEMA.COLUMNS b, INFORMATION_SCHEMA.COLUMNS c)>1 -- - */ - @GetMapping("/vulnerable/{id}") - @Operation(summary = "SQL Injection - Path Parameter") - fun timeBasedPath(@PathVariable id: String): ResponseEntity { - return try { - val stmt = connection?.createStatement() - val rs = stmt?.executeQuery("SELECT username FROM users WHERE username = '$id'") - val username = if (rs?.next() == true) rs.getString("username") else "NOT_FOUND" - ResponseEntity.ok("USERNAME: $username") - } catch (e: Exception) { - ResponseEntity.status(500).body("ERROR: ${e.message}") - } - } - -} diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/query/QuerySQLiApplication.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/query/QuerySQLiApplication.kt deleted file mode 100644 index 07d2b15696..0000000000 --- a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/query/QuerySQLiApplication.kt +++ /dev/null @@ -1,83 +0,0 @@ -package com.foo.rest.examples.spring.openapi.v3.security.sqli.query - -import com.foo.rest.examples.spring.openapi.v3.security.sqli.common.UserDto -import com.foo.rest.examples.spring.openapi.v3.security.sqli.common.UserEntity -import com.foo.rest.examples.spring.openapi.v3.security.sqli.common.UserRepository -import io.swagger.v3.oas.annotations.Operation -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.SpringApplication -import org.springframework.boot.autoconfigure.SpringBootApplication -import org.springframework.boot.autoconfigure.domain.EntityScan -import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration -import org.springframework.context.annotation.ComponentScan -import org.springframework.data.jpa.repository.config.EnableJpaRepositories -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.* -import java.sql.Connection -import javax.annotation.PostConstruct -import javax.sql.DataSource - - -@SpringBootApplication(exclude = [SecurityAutoConfiguration::class]) -@RequestMapping(path = ["/api/sqli/query"]) -@RestController -@ComponentScan(basePackages = ["com.foo.rest.examples.spring.openapi.v3.security.sqli.common", "com.foo.rest.examples.spring.openapi.v3.security.sqli.query"]) -@EnableJpaRepositories(basePackages = ["com.foo.rest.examples.spring.openapi.v3.security.sqli.common"]) -@EntityScan(basePackages = ["com.foo.rest.examples.spring.openapi.v3.security.sqli.common"]) -open class QuerySQLiApplication { - - @Autowired - private lateinit var dataSource: DataSource - - @Autowired - private lateinit var userRepository: UserRepository - - private var connection: Connection? = null - - companion object { - @JvmStatic - fun main(args: Array) { - SpringApplication.run(QuerySQLiApplication::class.java, *args) - } - } - - @PostConstruct - fun init() { - connection = dataSource.connection - initializeTestData() - } - - private fun initializeTestData() { - if (userRepository.count() == 0L) { - userRepository.save(UserEntity(null, "admin", "admin123")) - userRepository.save(UserEntity(null, "user1", "password1")) - } - } - - /** - * Safe endpoint - No SQL Injection vulnerability - */ - @GetMapping("/safe") - @Operation(summary = "Safe Query - No SQL Injection") - fun getSafeUsers(): ResponseEntity> { - val users = userRepository.findAll().map { UserDto(it.id, it.username) } - return ResponseEntity.ok(users) - } - - /** - * Attack: GET /api/sqli/query/vulnerable?username=admin' OR (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B, INFORMATION_SCHEMA.COLUMNS C)>0-- - */ - @GetMapping("/vulnerable") - @Operation(summary = "SQL Injection - Query Parameter") - fun timeBasedQuery(@RequestParam username: String): ResponseEntity { - return try { - val stmt = connection?.createStatement() - val rs = stmt?.executeQuery("SELECT COUNT(*) as cnt FROM users WHERE username = '$username'") - val count = if (rs?.next() == true) rs.getInt("cnt") else 0 - ResponseEntity.ok("COUNT: $count") - } catch (e: Exception) { - ResponseEntity.status(500).body("ERROR: ${e.message}") - } - } - -} diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiH2BodyController.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiH2BodyController.kt deleted file mode 100644 index c9254a4ff3..0000000000 --- a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiH2BodyController.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.foo.rest.examples.spring.openapi.v3.security.sqli -import com.foo.rest.examples.spring.openapi.v3.SpringController -import com.foo.rest.examples.spring.openapi.v3.security.sqli.body.BodySQLiApplication - -class SQLiH2BodyController : SpringController(BodySQLiApplication::class.java) \ No newline at end of file diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiH2PathController.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiH2PathController.kt deleted file mode 100644 index 384d9e1f1a..0000000000 --- a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiH2PathController.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.foo.rest.examples.spring.openapi.v3.security.sqli -import com.foo.rest.examples.spring.openapi.v3.SpringController -import com.foo.rest.examples.spring.openapi.v3.security.sqli.path.PathSQLiApplication - -class SQLiH2PathController : SpringController(PathSQLiApplication::class.java) \ No newline at end of file diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiH2QueryController.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiH2QueryController.kt deleted file mode 100644 index 0292ca4af9..0000000000 --- a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/sqli/SQLiH2QueryController.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.foo.rest.examples.spring.openapi.v3.security.sqli -import com.foo.rest.examples.spring.openapi.v3.SpringController -import com.foo.rest.examples.spring.openapi.v3.security.sqli.query.QuerySQLiApplication - -class SQLiH2QueryController : SpringController(QuerySQLiApplication::class.java) \ No newline at end of file diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiBodyH2EMTest.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiBodyH2EMTest.kt deleted file mode 100644 index ac1df936d5..0000000000 --- a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiBodyH2EMTest.kt +++ /dev/null @@ -1,57 +0,0 @@ -package org.evomaster.e2etests.spring.openapi.v3.security.sqli - - -import com.foo.rest.examples.spring.openapi.v3.security.sqli.SQLiH2BodyController -import com.webfuzzing.commons.faults.DefinedFaultCategory -import org.evomaster.core.problem.enterprise.DetectedFaultUtils -import org.evomaster.e2etests.spring.openapi.v3.SpringTestBase -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.BeforeAll -import org.junit.jupiter.api.Test - -class SQLiBodyH2EMTest : SpringTestBase() { - - companion object { - @BeforeAll - @JvmStatic - fun init() { - initClass(SQLiH2BodyController()) - } - } - - @Test - fun testSQLiH2EM() { - runTestHandlingFlakyAndCompilation( - "SQLiBodyH2EMTest", - 20 - ) { args: MutableList -> - - setOption(args, "security", "true") - setOption(args, "sqli", "true") - - - val solution = initAndRun(args) - - Assertions.assertTrue(solution.individuals.isNotEmpty()) - - val faults = DetectedFaultUtils.getDetectedFaults(solution) - - Assertions.assertTrue(faults.size == 1) - - val faultCategories = DetectedFaultUtils.getDetectedFaultCategories(solution) - - Assertions.assertTrue({ DefinedFaultCategory.SQL_INJECTION in faultCategories }) - - Assertions.assertTrue(faults.any { - it.category == DefinedFaultCategory.SQL_INJECTION - && it.operationId == "POST:/api/sqli/body/vulnerable" - }) - - Assertions.assertFalse(faults.any { - it.category == DefinedFaultCategory.SQL_INJECTION - && it.operationId == "GET:/api/sqli/body/safe" - }) - - } - } -} diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiPathH2EMTest.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiPathH2EMTest.kt deleted file mode 100644 index 4da48c21be..0000000000 --- a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiPathH2EMTest.kt +++ /dev/null @@ -1,59 +0,0 @@ -package org.evomaster.e2etests.spring.openapi.v3.security.sqli - - -import com.foo.rest.examples.spring.openapi.v3.security.sqli.SQLiH2PathController -import com.webfuzzing.commons.faults.DefinedFaultCategory -import org.evomaster.core.problem.enterprise.DetectedFaultUtils -import org.evomaster.e2etests.spring.openapi.v3.SpringTestBase -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Assertions.assertFalse -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.BeforeAll -import org.junit.jupiter.api.Test - -class SQLiPathH2EMTest : SpringTestBase() { - - companion object { - @BeforeAll - @JvmStatic - fun init() { - initClass(SQLiH2PathController()) - } - } - - @Test - fun testSQLiH2EM() { - runTestHandlingFlakyAndCompilation( - "SQLiPathH2EMTest", - 20 - ) { args: MutableList -> - - setOption(args, "security", "true") - setOption(args, "sqli", "true") - - - val solution = initAndRun(args) - - Assertions.assertTrue(solution.individuals.isNotEmpty()) - - val faults = DetectedFaultUtils.getDetectedFaults(solution) - - Assertions.assertTrue(faults.size == 1) - - val faultCategories = DetectedFaultUtils.getDetectedFaultCategories(solution) - - assertTrue({ DefinedFaultCategory.SQL_INJECTION in faultCategories }) - - assertTrue(faults.any { - it.category == DefinedFaultCategory.SQL_INJECTION - && it.operationId == "GET:/api/sqli/path/vulnerable/{id}" - }) - - assertFalse(faults.any { - it.category == DefinedFaultCategory.SQL_INJECTION - && it.operationId == "GET:/api/sqli/path/safe" - }) - - } - } -} diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiQueryH2EMTest.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiQueryH2EMTest.kt deleted file mode 100644 index 501ba40f67..0000000000 --- a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/sqli/SQLiQueryH2EMTest.kt +++ /dev/null @@ -1,57 +0,0 @@ -package org.evomaster.e2etests.spring.openapi.v3.security.sqli - - -import com.foo.rest.examples.spring.openapi.v3.security.sqli.SQLiH2QueryController -import com.webfuzzing.commons.faults.DefinedFaultCategory -import org.evomaster.core.problem.enterprise.DetectedFaultUtils -import org.evomaster.e2etests.spring.openapi.v3.SpringTestBase -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.BeforeAll -import org.junit.jupiter.api.Test - -class SQLiQueryH2EMTest : SpringTestBase() { - - companion object { - @BeforeAll - @JvmStatic - fun init() { - initClass(SQLiH2QueryController()) - } - } - - @Test - fun testSQLiH2EM() { - runTestHandlingFlakyAndCompilation( - "SQLiH2EMTest", - 20 - ) { args: MutableList -> - - setOption(args, "security", "true") - setOption(args, "sqli", "true") - - - val solution = initAndRun(args) - - Assertions.assertTrue(solution.individuals.isNotEmpty()) - - val faults = DetectedFaultUtils.getDetectedFaults(solution) - - Assertions.assertTrue(faults.size == 1) - - val faultCategories = DetectedFaultUtils.getDetectedFaultCategories(solution) - - Assertions.assertTrue({ DefinedFaultCategory.SQL_INJECTION in faultCategories }) - - Assertions.assertTrue(faults.any { - it.category == DefinedFaultCategory.SQL_INJECTION - && it.operationId == "GET:/api/sqli/query/vulnerable" - }) - - Assertions.assertFalse(faults.any { - it.category == DefinedFaultCategory.SQL_INJECTION - && it.operationId == "GET:/api/sqli/query/safe" - }) - - } - } -} diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/data/RestCallAction.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/data/RestCallAction.kt index a01730572d..291dd34680 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/data/RestCallAction.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/data/RestCallAction.kt @@ -434,4 +434,5 @@ class RestCallAction( this.weakReference = wr return copy } -} \ No newline at end of file + var baseResponseTime: Long? = 0 +} diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/SecurityRest.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/SecurityRest.kt index e3789ed6ce..f6a5dd6537 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/SecurityRest.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/SecurityRest.kt @@ -348,7 +348,7 @@ class SecurityRest { target.individual, actionIndex ) - + val responseTime = target.evaluatedMainActions()[actionIndex].result.getResultValue("RESPONSE_TIME")?.toLong() // Try each sqli payload (but only add one test per endpoint) for(payload in SQLI_PAYLOADS){ @@ -387,6 +387,7 @@ class SecurityRest { if(!anySuccess){ continue } + actionCopy.baseResponseTime = responseTime copy.modifySampleType(SampleType.SECURITY) copy.ensureFlattenedStructure() diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/fitness/AbstractRestFitness.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/fitness/AbstractRestFitness.kt index 7344b7071c..77d150d02e 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/service/fitness/AbstractRestFitness.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/service/fitness/AbstractRestFitness.kt @@ -1368,13 +1368,29 @@ abstract class AbstractRestFitness : HttpWsFitness() { return } - // Find the action(s) where XSS payload appears in the response for(index in individual.seeMainExecutableActions().indices){ val a = individual.seeMainExecutableActions()[index] val r = actionResults.find { it.sourceLocalId == a.getLocalId() } as? RestCallResult ?: continue - if(r.getResponseTime() < config.sqlInjectionMaxResponseTimeMs && !r.getTimedout()) { + val baseline = a.baseResponseTime + val afterPayload = r.getResponseTime() + val K = config.sqliBaselineMaxResponseTimeMs // K: maximum allowed baseline response time + val N = config.sqliInjectedSleepDuration * 1000 // N: expected delay introduced by the injected sleep payload + + // Baseline must be fast enough (baseline < K) + val baselineIsFast = baseline!! < K + + // Response after injection must be slow enough (response > N) + val responseIsSlowEnough = afterPayload > N + + // Timeout is also considered a potential vulnerability indicator + val isTimeout = r.getTimedout() + + // If baseline is fast AND the response after payload is slow enough (or timed out), + // then we consider this a potential time-based SQL injection vulnerability. + // Otherwise, skip this result. + if (!(baselineIsFast && (responseIsSlowEnough || isTimeout))) { continue } From e0536c3e4a3081e8eaa3101e151ea1fcf3c57439 Mon Sep 17 00:00:00 2001 From: Omur Sahin Date: Fri, 5 Dec 2025 23:55:14 +0300 Subject: [PATCH 11/13] doc --- core/src/main/kotlin/org/evomaster/core/EMConfig.kt | 4 ---- docs/options.md | 2 ++ 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/core/src/main/kotlin/org/evomaster/core/EMConfig.kt b/core/src/main/kotlin/org/evomaster/core/EMConfig.kt index 0f2053e6e0..3d701c3aa4 100644 --- a/core/src/main/kotlin/org/evomaster/core/EMConfig.kt +++ b/core/src/main/kotlin/org/evomaster/core/EMConfig.kt @@ -2609,10 +2609,6 @@ class EMConfig { @Cfg("To apply SQLi detection as part of security testing.") var sqli = false - @Experimental - @Cfg("Maximum response time (in milliseconds) to consider a potential SQL Injection vulnerability.") - var sqlInjectionMaxResponseTimeMs = 2000 - @Experimental @Cfg("Injected sleep duration (in seconds) used inside the malicious payload to detect time-based vulnerabilities.") var sqliInjectedSleepDuration = 5 diff --git a/docs/options.md b/docs/options.md index f2a3e760d0..40e9880997 100644 --- a/docs/options.md +++ b/docs/options.md @@ -311,6 +311,8 @@ There are 3 types of options: |`seedTestCasesPath`| __String__. File path where the seeded test cases are located. *Default value*: `postman.postman_collection.json`.| |`sqlInjectionMaxResponseTimeMs`| __Int__. Maximum response time (in milliseconds) to consider a potential SQL Injection vulnerability. *Default value*: `2000`.| |`sqli`| __Boolean__. To apply SQLi detection as part of security testing. *Default value*: `false`.| +|`sqliBaselineMaxResponseTimeMs`| __Int__. Maximum allowed baseline response time (in milliseconds) before the malicious payload is applied. *Default value*: `2000`.| +|`sqliInjectedSleepDuration`| __Int__. Injected sleep duration (in seconds) used inside the malicious payload to detect time-based vulnerabilities. *Default value*: `5`.| |`ssrf`| __Boolean__. To apply SSRF detection as part of security testing. *Default value*: `false`.| |`structureMutationProFS`| __Double__. Specify a probability of applying structure mutator during the focused search. *Constraints*: `probability 0.0-1.0`. *Default value*: `0.0`.| |`structureMutationProbStrategy`| __Enum__. Specify a strategy to handle a probability of applying structure mutator during the focused search. *Valid values*: `SPECIFIED, SPECIFIED_FS, DPC_TO_SPECIFIED_BEFORE_FS, DPC_TO_SPECIFIED_AFTER_FS, ADAPTIVE_WITH_IMPACT`. *Default value*: `SPECIFIED`.| From b8db90b838b9c116b4b8eaaf9b89f508e4f75c57 Mon Sep 17 00:00:00 2001 From: Omur Date: Sat, 6 Dec 2025 09:57:30 +0300 Subject: [PATCH 12/13] doc fix --- docs/options.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/options.md b/docs/options.md index 40e9880997..31f4ba98f7 100644 --- a/docs/options.md +++ b/docs/options.md @@ -309,7 +309,6 @@ There are 3 types of options: |`seedTestCases`| __Boolean__. Whether to seed EvoMaster with some initial test cases. These test cases will be used and evolved throughout the search process. *Default value*: `false`.| |`seedTestCasesFormat`| __Enum__. Format of the test cases seeded to EvoMaster. *Valid values*: `POSTMAN`. *Default value*: `POSTMAN`.| |`seedTestCasesPath`| __String__. File path where the seeded test cases are located. *Default value*: `postman.postman_collection.json`.| -|`sqlInjectionMaxResponseTimeMs`| __Int__. Maximum response time (in milliseconds) to consider a potential SQL Injection vulnerability. *Default value*: `2000`.| |`sqli`| __Boolean__. To apply SQLi detection as part of security testing. *Default value*: `false`.| |`sqliBaselineMaxResponseTimeMs`| __Int__. Maximum allowed baseline response time (in milliseconds) before the malicious payload is applied. *Default value*: `2000`.| |`sqliInjectedSleepDuration`| __Int__. Injected sleep duration (in seconds) used inside the malicious payload to detect time-based vulnerabilities. *Default value*: `5`.| From a7237cdbd1572ff5a397eaacc70992d27f6ade1a Mon Sep 17 00:00:00 2001 From: Omur Sahin Date: Sun, 7 Dec 2025 15:58:57 +0300 Subject: [PATCH 13/13] remove h2 payloads --- .../core/problem/rest/oracle/RestSecurityOracle.kt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt b/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt index fe983e3ff2..cf44da7466 100644 --- a/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt +++ b/core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/RestSecurityOracle.kt @@ -228,7 +228,6 @@ object RestSecurityOracle { * which can be detected by measuring the response time of the application. */ val SQLI_PAYLOADS = listOf( - "' OR (WITH RECURSIVE r(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM r WHERE i < 10000000) SELECT COUNT(*) FROM r)>0--", "' OR SLEEP(%d)-- -", "\" OR SLEEP(%d)-- -", "' union select sleep(%d)-- -", @@ -237,11 +236,6 @@ object RestSecurityOracle { "\" OR pg_sleep(%d)-- -", "' union select pg_sleep(%d)-- -", "\" union select pg_sleep(%d)-- -" - // for h2 database -// "' OR (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B, INFORMATION_SCHEMA.COLUMNS C)>0--", -// "' OR (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS A, INFORMATION_SCHEMA.COLUMNS B)>0--", -// it takes extremely long. -// "' OR (SELECT SUM(a.ORDINAL_POSITION*b.ORDINAL_POSITION*c.ORDINAL_POSITION) FROM INFORMATION_SCHEMA.COLUMNS a, INFORMATION_SCHEMA.COLUMNS b, INFORMATION_SCHEMA.COLUMNS c)>1 --", )