Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
1968ce2
starter of the forgotten auth
omursahin May 12, 2025
5b9c157
first try of the forgotten auth
omursahin May 12, 2025
1decb60
adding comments
omursahin May 13, 2025
fba81d0
create check
omursahin May 14, 2025
152c6e1
algorithm update
omursahin May 19, 2025
1164f53
updates
omursahin May 22, 2025
0aaee21
fixed confusing handling of auth filter
arcuri82 May 22, 2025
0a17deb
auth fix
omursahin May 22, 2025
4f5cef7
nullable bool
omursahin May 22, 2025
439e877
updating for security
omursahin May 23, 2025
edd9505
test update
omursahin May 23, 2025
709b8d2
global validity constraints problem
omursahin May 23, 2025
59e5977
Merge branch 'master' into forgotten-auth-oracle
omursahin May 23, 2025
3affa04
checking read operation
omursahin May 24, 2025
e90427f
Another try of the construction individual
omursahin May 25, 2025
1e4e735
403-200 individual
omursahin May 26, 2025
2000f1c
comments
omursahin May 26, 2025
428b102
test update
omursahin May 26, 2025
5b3e060
irrelevant test
omursahin May 26, 2025
f04392c
Child removing exception in mocking services
omursahin May 27, 2025
4ec25d2
conflict try
Sep 8, 2025
71e0942
Merge branch 'master' into forgotten-auth-oracle
omursahin Sep 8, 2025
6a9d0d3
experimental category fix
omursahin Sep 10, 2025
976b7fa
Merge branch 'master' into forgotten-auth-oracle
omursahin Sep 13, 2025
12e12da
revert unnecessary change
omursahin Sep 16, 2025
6ec4696
try to fix merging same action
omursahin Sep 16, 2025
b1ec868
test update
Sep 16, 2025
258e3e4
minor fixes
omursahin Sep 17, 2025
f96718c
slice individual
omursahin Sep 17, 2025
ee2b381
fixed merge when dealing with hostname
arcuri82 Sep 17, 2025
c85d48d
Merge branch 'forgotten-auth-oracle' of https://github.com/WebFuzzing…
arcuri82 Sep 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package bar.examples.it.spring.forgottenauthentication

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.*


@SpringBootApplication(exclude = [SecurityAutoConfiguration::class])
@RequestMapping(path = ["/api/resources"])
@RestController
open class ForgottenAuthenticationApplication {

companion object {
@JvmStatic
fun main(args: Array<String>) {
SpringApplication.run(ForgottenAuthenticationApplication::class.java, *args)
}

private val data = mutableMapOf<Int, String>()

fun reset(){
data.clear()
}
}

private fun checkAuthWrongly(auth: String?) = auth == null || (auth == "FOO" || auth == "BAR")
private fun checkAuth(auth: String?) = auth != null && (auth == "FOO" || auth == "BAR")


@PutMapping(path = ["/{id}"])
open fun put(
@RequestHeader("Authorization") auth: String?,
@PathVariable("id") id: Int,
): ResponseEntity<Any> {

if(!checkAuth(auth)) {
return ResponseEntity.status(401).build()
}

if(!data.containsKey(id)){
data[id] = auth!!
return ResponseEntity.status(201).build()
}

val source = data.getValue(id)
if(source != auth){
return ResponseEntity.status(403).build()
}
return ResponseEntity.status(204).build()
}

@GetMapping(path = ["/{id}"])
open fun get(
@RequestHeader("Authorization") auth: String?,
@PathVariable("id") id: Int): ResponseEntity<String> {

if(!checkAuthWrongly(auth)) {
// wrong, leaking forgotten authentication. should return 401
return ResponseEntity.status(401).build()
}

if(!data.containsKey(id)){
return ResponseEntity.status(403).build()
}

val source = data.getValue(id)

if(auth != null && source != auth){
return ResponseEntity.status(403).build()
}

return ResponseEntity.status(200).body(source)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package bar.examples.it.spring.forgottenauthentication

import bar.examples.it.spring.SpringController
import org.evomaster.client.java.controller.AuthUtils
import org.evomaster.client.java.controller.api.dto.auth.AuthenticationDto

class ForgottenAuthenticationController : SpringController(ForgottenAuthenticationApplication::class.java) {

override fun getInfoForAuthentication(): List<AuthenticationDto> {
return listOf(
AuthUtils.getForAuthorizationHeader("FOO","FOO"),
AuthUtils.getForAuthorizationHeader("BAR","BAR"),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package org.evomaster.core.problem.rest.securityrestoracle

import bar.examples.it.spring.forgottenauthentication.ForgottenAuthenticationApplication
import bar.examples.it.spring.forgottenauthentication.ForgottenAuthenticationController
import org.evomaster.core.JdkIssue
import org.evomaster.core.problem.enterprise.SampleType
import org.evomaster.core.problem.httpws.auth.HttpWsAuthenticationInfo
import org.evomaster.core.problem.rest.*
import org.evomaster.core.problem.rest.data.HttpVerb
import org.evomaster.core.problem.rest.data.RestCallResult
import org.evomaster.core.problem.rest.data.RestPath
import org.evomaster.core.problem.rest.oracle.RestSecurityOracle
import org.evomaster.core.problem.rest.service.fitness.AbstractRestFitness
import org.evomaster.core.problem.rest.service.RestIndividualBuilder
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

class ForgottenAuthenticationTest: IntegrationTestRestBase() {


companion object {
@BeforeAll
@JvmStatic
fun init() {
JdkIssue.fixPatchMethod()

initClass(ForgottenAuthenticationController())
}
}

@BeforeEach
fun initializeTest(){
ForgottenAuthenticationApplication.reset()
getEMConfig().security = true
getEMConfig().schemaOracles = false
}

@Test
fun testForgottenAuthentication(){

val pirTest = getPirToRest()

val id42 = 42

val put42 = pirTest.fromVerbPath("PUT", "/api/resources/$id42")!!
val get42 = pirTest.fromVerbPath("GET", "/api/resources/$id42")!!
val get42NotAuth = pirTest.fromVerbPath("GET", "/api/resources/$id42")!!
val get42DifferentAuth = pirTest.fromVerbPath("GET", "/api/resources/$id42")!!

val auth = controller.getInfoForAuthentication()
val foo = HttpWsAuthenticationInfo.fromDto(auth.find { it.name == "FOO" }!!)
val bar = HttpWsAuthenticationInfo.fromDto(auth.find { it.name == "BAR" }!!)

put42.auth = foo
get42.auth = foo
get42DifferentAuth.auth = bar


val authenticated = createIndividual(listOf(put42,get42,get42DifferentAuth), SampleType.SECURITY)
assertEquals(201, (authenticated.evaluatedMainActions()[0].result as RestCallResult).getStatusCode())
assertEquals(200, (authenticated.evaluatedMainActions()[1].result as RestCallResult).getStatusCode())
assertEquals(403, (authenticated.evaluatedMainActions()[2].result as RestCallResult).getStatusCode())

val forgottenAuth = createIndividual(listOf(get42NotAuth), SampleType.SECURITY)
assertEquals(200, (forgottenAuth.evaluatedMainActions()[0].result as RestCallResult).getStatusCode())

val ind = RestIndividualBuilder.merge(authenticated.individual, forgottenAuth.individual)
assertEquals(HttpVerb.PUT, ind.seeMainExecutableActions()[0].verb)
assertEquals(HttpVerb.GET, ind.seeMainExecutableActions()[1].verb)
assertEquals(HttpVerb.GET, ind.seeMainExecutableActions()[2].verb)
assertEquals(HttpVerb.GET, ind.seeMainExecutableActions()[3].verb)

ForgottenAuthenticationApplication.reset()
val ff = injector.getInstance(AbstractRestFitness::class.java)
val ei = ff.calculateCoverage(ind)!!

val r0 = ei.evaluatedMainActions()[0].result as RestCallResult
val r1 = ei.evaluatedMainActions()[1].result as RestCallResult
val r2 = ei.evaluatedMainActions()[2].result as RestCallResult
val r3 = ei.evaluatedMainActions()[3].result as RestCallResult
assertEquals(201, r0.getStatusCode())
assertEquals(200, r1.getStatusCode())
assertEquals(403, r2.getStatusCode())
assertEquals(200, r3.getStatusCode())

val faultDetected = RestSecurityOracle.hasForgottenAuthentication(get42NotAuth.getName(), ei.individual, ei.seeResults())
assertTrue(faultDetected)

//fault should be put on 200 with no authentication
assertEquals(0, r0.getFaults().size)
assertEquals(0, r1.getFaults().size)
assertEquals(0, r2.getFaults().size)
assertEquals(1, r3.getFaults().size)
}


@Test
fun testForgottenAuthenticationWithCreateAndNoAuthRequest(){

val pirTest = getPirToRest()

val id42 = 42

val put42 = pirTest.fromVerbPath("PUT", "/api/resources/$id42")!!
val get42NotAuth = pirTest.fromVerbPath("GET", "/api/resources/$id42")!!

val auth = controller.getInfoForAuthentication()
val foo = HttpWsAuthenticationInfo.fromDto(auth.find { it.name == "FOO" }!!)

put42.auth = foo

val authenticated = createIndividual(listOf(put42), SampleType.SECURITY)
val forgottenAuth = createIndividual(listOf(get42NotAuth), SampleType.SECURITY)

val ind = RestIndividualBuilder.merge(authenticated.individual, forgottenAuth.individual)
assertEquals(HttpVerb.PUT, ind.seeMainExecutableActions()[0].verb)
assertEquals(HttpVerb.GET, ind.seeMainExecutableActions()[1].verb)

ForgottenAuthenticationApplication.reset()
val ff = injector.getInstance(AbstractRestFitness::class.java)
val ei = ff.calculateCoverage(ind)!!

val r0 = ei.evaluatedMainActions()[0].result as RestCallResult
val r1 = ei.evaluatedMainActions()[1].result as RestCallResult
assertEquals(201, r0.getStatusCode())
assertEquals(200, r1.getStatusCode())

// we couldn't say this is forgotten because GET could be open, so we cannot be sure.
val faultDetected = RestSecurityOracle.hasForgottenAuthentication(put42.getName(), ei.individual, ei.seeResults())
assertFalse(faultDetected)

assertEquals(0, r0.getFaults().size)
assertEquals(0, r1.getFaults().size)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@ abstract class EnterpriseIndividual(

fun seeScheduleTaskActions() : List<ScheduleTaskAction> = seeActions(ActionFilter.ONLY_SCHEDULE_TASK) as List<ScheduleTaskAction>

fun seeHostnameActions() : List<HostnameResolutionAction> = seeActions(ActionFilter.ONLY_DNS) as List<HostnameResolutionAction>

/**
* return a list of all external service actions in [this] individual
* that include all the initializing actions among the main actions
Expand Down Expand Up @@ -370,8 +372,14 @@ abstract class EnterpriseIndividual(
groupsView()!!.startIndexForGroupInsertionInclusive(GroupsOfChildren.INITIALIZATION_SCHEDULE_TASK)



fun addInitializingActions(actions: List<EnvironmentAction>){
/**
* Add all input initializing actions to the current ones in this individual.
*
* At time, some actions must be "unique".
* In those cases, we don't crash this function, but rather return the number of
* unneeded actions that are ignored
*/
fun addInitializingActions(actions: List<EnvironmentAction>): Int {

val invalid = actions.filter { it !is SqlAction && it !is MongoDbAction && it !is HostnameResolutionAction }
if(invalid.isNotEmpty()){
Expand All @@ -381,8 +389,17 @@ abstract class EnterpriseIndividual(

addInitializingDbActions(actions = actions.filterIsInstance<SqlAction>())
addInitializingMongoDbActions(actions = actions.filterIsInstance<MongoDbAction>())
addInitializingHostnameResolutionActions(actions = actions.filterIsInstance<HostnameResolutionAction>())
addInitializingScheduleTaskActions(actions = actions.filterIsInstance<ScheduleTaskAction>())

//we don't need duplicates in hostname actions
val hostnameActions = actions
.filterIsInstance<HostnameResolutionAction>()
val uniqueHostnames = hostnameActions
.filter { dns -> this.seeHostnameActions().none { it.hostname == dns.hostname } }

addInitializingHostnameResolutionActions(actions = uniqueHostnames)

return hostnameActions.size - uniqueHostnames.size
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ enum class ExperimentalFaultCategory(
private val fullDescription: String,
) : FaultCategory {


//9xx for experimental, work-in-progress oracles

HTTP_INVALID_PAYLOAD_SYNTAX(901, "Invalid Payload Syntax", "rejectedWithInvalidPayloadSyntax",
Expand Down Expand Up @@ -49,10 +48,12 @@ enum class ExperimentalFaultCategory(
//6xx: mobile

//security

//Likely this one is not really viable
//SECURITY_ALLOW_MODIFICATION_BY_ALL(985, "Resource Created By An User Can Be Modified By All Other Users", "createdResourceCanBeModifiedByEveryone",
// "TODO")
SECURITY_FORGOTTEN_AUTHENTICATION(980, "A Protected Resource Is Accessible Without Providing Any Authentication",
"forgottenAuthentication",
"TODO"),
;

override fun getCode(): Int {
Expand All @@ -70,4 +71,4 @@ enum class ExperimentalFaultCategory(
override fun getFullDescription(): String {
return fullDescription
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ object RestIndividualSelectorUtils {
status: Int? = null,
statusGroup: StatusGroup? = null,
statusCodes: Collection<Int>? = null,
mustBeAuthenticated : Boolean? = null,
authenticated : Boolean? = null,
authenticatedWith: String? = null
) : Boolean {

Expand Down Expand Up @@ -58,9 +58,14 @@ object RestIndividualSelectorUtils {
return false
}

// authenticated or not
if(mustBeAuthenticated == true && action.auth is NoAuth){
return false
if(authenticated != null) {
// authenticated or not
if (authenticated == true && action.auth is NoAuth) {
return false
}
if(authenticated == false && action.auth !is NoAuth) {
return false
}
}

if(authenticatedWith != null && action.auth.name != authenticatedWith){
Expand All @@ -76,7 +81,7 @@ object RestIndividualSelectorUtils {
path: RestPath? = null,
status: Int? = null,
statusGroup: StatusGroup? = null,
authenticated: Boolean = false
authenticated: Boolean? = null
): RestCallAction? {

return findEvaluatedAction(individualsInSolution,verb,path,status,statusGroup,authenticated)
Expand All @@ -90,7 +95,7 @@ object RestIndividualSelectorUtils {
path: RestPath? = null,
status: Int? = null,
statusGroup: StatusGroup? = null,
authenticated: Boolean = false
authenticated: Boolean? = null
): EvaluatedAction? {

val actions = findEvaluatedActions(individualsInSolution,verb,path,status,statusGroup,authenticated)
Expand All @@ -109,7 +114,7 @@ object RestIndividualSelectorUtils {
path: RestPath? = null,
status: Int? = null,
statusGroup: StatusGroup? = null,
authenticated: Boolean = false
authenticated: Boolean? = null
): List<EvaluatedAction> {

if(status != null && statusGroup!= null){
Expand Down Expand Up @@ -137,7 +142,7 @@ object RestIndividualSelectorUtils {
path: RestPath? = null,
status: Int? = null,
statusGroup: StatusGroup? = null,
authenticated: Boolean = false
authenticated: Boolean? = null
): List<EvaluatedAction> {

if(status != null && statusGroup!= null){
Expand Down
Loading
Loading