From b35538a92d640c7572b2135cc4208a19e6343ffc Mon Sep 17 00:00:00 2001 From: seran <7030273+seran@users.noreply.github.com> Date: Wed, 26 Nov 2025 15:21:19 +0100 Subject: [PATCH 1/5] setFromStringValue for UriGene --- .../kotlin/org/evomaster/core/search/gene/uri/UriGene.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/src/main/kotlin/org/evomaster/core/search/gene/uri/UriGene.kt b/core/src/main/kotlin/org/evomaster/core/search/gene/uri/UriGene.kt index 3aa487a74b..f24dbfd223 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/gene/uri/UriGene.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/gene/uri/UriGene.kt @@ -87,6 +87,10 @@ class UriGene(name: String, return gene.unsafeCopyValueFrom(other) } + override fun unsafeSetFromStringValue(value: String): Boolean { + return gene.unsafeSetFromStringValue(value) + } + override fun customShouldApplyShallowMutation( randomness: Randomness, @@ -97,4 +101,4 @@ class UriGene(name: String, return false } -} \ No newline at end of file +} From 2dd034a4a9b2b832c8892e9eb366044fdab78eb4 Mon Sep 17 00:00:00 2001 From: seran <7030273+seran@users.noreply.github.com> Date: Tue, 9 Dec 2025 10:12:48 +0100 Subject: [PATCH 2/5] minor change --- .../main/kotlin/org/evomaster/core/search/gene/uri/UriGene.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/main/kotlin/org/evomaster/core/search/gene/uri/UriGene.kt b/core/src/main/kotlin/org/evomaster/core/search/gene/uri/UriGene.kt index f24dbfd223..b26fe19fb0 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/gene/uri/UriGene.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/gene/uri/UriGene.kt @@ -91,7 +91,6 @@ class UriGene(name: String, return gene.unsafeSetFromStringValue(value) } - override fun customShouldApplyShallowMutation( randomness: Randomness, selectionStrategy: SubsetGeneMutationSelectionStrategy, From c8d21448b1ad6f49790f27b1c837a69d0eff1539 Mon Sep 17 00:00:00 2001 From: seran <7030273+seran@users.noreply.github.com> Date: Tue, 9 Dec 2025 19:42:29 +0100 Subject: [PATCH 3/5] e2e for URI --- .../security/ssrf/uri/SSRFUriApplication.kt | 64 +++++++++++++++++++ .../v3/security/ssrf/uri/SSRFUriController.kt | 5 ++ .../v3/security/ssrf/uri/SSRFUriEMTest.kt | 46 +++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/ssrf/uri/SSRFUriApplication.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/ssrf/uri/SSRFUriController.kt create mode 100644 core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/ssrf/uri/SSRFUriEMTest.kt diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/ssrf/uri/SSRFUriApplication.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/ssrf/uri/SSRFUriApplication.kt new file mode 100644 index 0000000000..d59d077e2c --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/security/ssrf/uri/SSRFUriApplication.kt @@ -0,0 +1,64 @@ +package com.foo.rest.examples.spring.openapi.v3.security.ssrf.uri + +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.responses.ApiResponses +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.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController +import java.net.HttpURLConnection +import java.net.URI + +@SpringBootApplication(exclude = [SecurityAutoConfiguration::class]) +@RequestMapping(path = ["/api"]) +@RestController +open class SSRFUriApplication { + + companion object { + @JvmStatic + fun main(args: Array) { + SpringApplication.run(SSRFUriApplication::class.java, *args) + } + } + + @Operation( + summary = "GET endpoint to fetch data from remote source", + description = "Can be used to fetch data from remote source." + ) + @ApiResponses( + value = [ + ApiResponse(responseCode = "200", description = "Successful response"), + ApiResponse(responseCode = "204", description = "No data to fetch"), + ApiResponse(responseCode = "400", description = "Invalid request"), + ApiResponse(responseCode = "500", description = "Invalid server error") + ] + ) + @GetMapping(path = ["/uri"]) + open fun uriTest(@RequestParam dataSource: String): ResponseEntity { + if (dataSource != null) { + return try { + val uri = URI(dataSource) + if (uri.scheme == "http") { + val connection = uri.toURL().openConnection() as HttpURLConnection + connection.requestMethod = "GET" + connection.connectTimeout = 1000 + + if (connection.responseCode == 200) { + return ResponseEntity.status(200).body("OK") + } + ResponseEntity.status(204).body("Unable to fetch.") + } + ResponseEntity.status(204).body("Unable to fetch.") + } catch (e: Exception) { + ResponseEntity.status(204).body("Unable to fetch.") + } + } + + return ResponseEntity.badRequest().body("Invalid request") + } +} diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/ssrf/uri/SSRFUriController.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/ssrf/uri/SSRFUriController.kt new file mode 100644 index 0000000000..313f2ff114 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/com/foo/rest/examples/spring/openapi/v3/security/ssrf/uri/SSRFUriController.kt @@ -0,0 +1,5 @@ +package com.foo.rest.examples.spring.openapi.v3.security.ssrf.uri + +import com.foo.rest.examples.spring.openapi.v3.SpringController + +class SSRFUriController: SpringController(SSRFUriApplication::class.java) diff --git a/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/ssrf/uri/SSRFUriEMTest.kt b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/ssrf/uri/SSRFUriEMTest.kt new file mode 100644 index 0000000000..7b6e428988 --- /dev/null +++ b/core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/security/ssrf/uri/SSRFUriEMTest.kt @@ -0,0 +1,46 @@ +package org.evomaster.e2etests.spring.openapi.v3.security.ssrf.uri + +import com.foo.rest.examples.spring.openapi.v3.security.ssrf.uri.SSRFUriController +import org.evomaster.core.EMConfig +import org.evomaster.core.problem.rest.data.HttpVerb +import org.evomaster.e2etests.spring.openapi.v3.SpringTestBase +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test + +class SSRFUriEMTest: SpringTestBase() { + + companion object { + @BeforeAll + @JvmStatic + fun init() { + val config = EMConfig() + config.instrumentMR_NET = false + initClass(SSRFUriController(), config) + } + } + + @Test + fun testSSRFUri() { + runTestHandlingFlakyAndCompilation( + "SSRFUriGeneratedTest", + 30, + ) { args: MutableList -> + + // If mocking enabled, it'll spin new services each time when there is a valid URL. + setOption(args, "externalServiceIPSelectionStrategy", "NONE") + + setOption(args, "security", "true") + setOption(args, "ssrf", "true") + setOption(args, "vulnerableInputClassificationStrategy", "MANUAL") + setOption(args, "schemaOracles", "false") + + val solution = initAndRun(args) + + assertTrue(solution.individuals.isNotEmpty()) + assertTrue(solution.hasSsrfFaults()) + + assertHasAtLeastOne(solution, HttpVerb.GET, 200, "/api/uri", "OK") + } + } +} From 170c9ba64308836ce8f41e38bf7ab3496dcf1bfc Mon Sep 17 00:00:00 2001 From: seran <7030273+seran@users.noreply.github.com> Date: Fri, 16 Jan 2026 20:28:12 +0100 Subject: [PATCH 4/5] working on --- .../core/search/gene/uri/UriDataGene.kt | 27 ++++++++++++++++++- .../core/search/gene/uri/UriGeneTest.kt | 18 +++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 core/src/test/kotlin/org/evomaster/core/search/gene/uri/UriGeneTest.kt diff --git a/core/src/main/kotlin/org/evomaster/core/search/gene/uri/UriDataGene.kt b/core/src/main/kotlin/org/evomaster/core/search/gene/uri/UriDataGene.kt index 79a56d0392..2498d4956b 100644 --- a/core/src/main/kotlin/org/evomaster/core/search/gene/uri/UriDataGene.kt +++ b/core/src/main/kotlin/org/evomaster/core/search/gene/uri/UriDataGene.kt @@ -90,4 +90,29 @@ class UriDataGene( return false } -} \ No newline at end of file + @Deprecated("Do not call directly outside this package. Call setFromStringValue") + override fun unsafeSetFromStringValue(value: String): Boolean { + return try { + val uri = URI(value) + + if (uri.scheme == "data") { + val uriParts = uri.schemeSpecificPart + val parts = uriParts.split(",", limit = 2) + val metadata = parts[0].split(";") + val t = metadata[0] + val b64 = metadata[2].equals("base64", ignoreCase = true) + val d = parts[1] + + type.unsafeSetFromStringValue(t) + base64.unsafeSetFromStringValue(b64.toString()) + data.unsafeSetFromStringValue(d) + true + } else { + false + } + } catch (e: Exception) { + false + } + } + +} diff --git a/core/src/test/kotlin/org/evomaster/core/search/gene/uri/UriGeneTest.kt b/core/src/test/kotlin/org/evomaster/core/search/gene/uri/UriGeneTest.kt new file mode 100644 index 0000000000..d00907470f --- /dev/null +++ b/core/src/test/kotlin/org/evomaster/core/search/gene/uri/UriGeneTest.kt @@ -0,0 +1,18 @@ +package org.evomaster.core.search.gene.uri + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +class UriGeneTest { + + @Test + fun testSetValueBasedOn() { + val gene = UriGene("data:content/type;base64,") + +// assertEquals("http://0.0.0.0:0/", gene.getValueAsRawString()) + assertTrue(gene.unsafeSetFromStringValue("data:text/plain;charset=UTF-8;base64,R0lGODdh")) + + assertEquals("127.0.0.1", gene.getValueAsRawString()) + } +} From 8dfcdd55b511a7f85e984040cfa6df4a8b3abc4d Mon Sep 17 00:00:00 2001 From: seran <7030273+seran@users.noreply.github.com> Date: Fri, 16 Jan 2026 20:30:20 +0100 Subject: [PATCH 5/5] test disabled --- .../org/evomaster/core/search/gene/uri/UriGeneTest.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/core/src/test/kotlin/org/evomaster/core/search/gene/uri/UriGeneTest.kt b/core/src/test/kotlin/org/evomaster/core/search/gene/uri/UriGeneTest.kt index d00907470f..23649fabcb 100644 --- a/core/src/test/kotlin/org/evomaster/core/search/gene/uri/UriGeneTest.kt +++ b/core/src/test/kotlin/org/evomaster/core/search/gene/uri/UriGeneTest.kt @@ -1,18 +1,16 @@ package org.evomaster.core.search.gene.uri -import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test class UriGeneTest { + @Disabled("Work in progress") @Test - fun testSetValueBasedOn() { + fun testSetValueBasedOnForUriDataGene() { val gene = UriGene("data:content/type;base64,") -// assertEquals("http://0.0.0.0:0/", gene.getValueAsRawString()) assertTrue(gene.unsafeSetFromStringValue("data:text/plain;charset=UTF-8;base64,R0lGODdh")) - - assertEquals("127.0.0.1", gene.getValueAsRawString()) } }