Skip to content

Commit 281802f

Browse files
authored
Merge pull request #1612 from WebFuzzing/http-oracle-static-new
Http oracle static new
2 parents 2912cca + 31b0cab commit 281802f

6 files changed

Lines changed: 73 additions & 9 deletions

File tree

core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/main/kotlin/com/foo/rest/examples/spring/openapi/v3/statusoracle/StatusOracleRest.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,19 @@ private class StatisticsRest {
8080
return ResponseEntity.status(403).build()
8181
}
8282

83+
84+
@PostMapping(path = ["/no-304-if-no-get-or-head"])
85+
fun no304IfNoGetOrHead(): ResponseEntity<String>{
86+
return ResponseEntity.status(304).build()
87+
}
88+
89+
@GetMapping(path = ["/no-405-if-no-allow"])
90+
fun no405IfNoAllow(): ResponseEntity<String>{
91+
return ResponseEntity.status(405).build()
92+
}
93+
94+
@GetMapping(path = ["/no-501-if-implemented"])
95+
fun no501IfImplemented(): ResponseEntity<String>{
96+
return ResponseEntity.status(501).build()
97+
}
8398
}

core-tests/e2e-tests/spring/spring-rest-openapi-v3/src/test/kotlin/org/evomaster/e2etests/spring/openapi/v3/statusoracle/StatusOracleEMTest.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import org.junit.jupiter.api.Assertions
99
import org.junit.jupiter.api.Assertions.assertTrue
1010
import org.junit.jupiter.api.BeforeAll
1111
import org.junit.jupiter.api.Test
12+
import javax.ws.rs.POST
1213

1314
class StatusOracleEMTest : SpringTestBase(){
1415

@@ -57,11 +58,20 @@ class StatusOracleEMTest : SpringTestBase(){
5758
//assertHasAtLeastOne(solution, HttpVerb.GET, 204, "/api/statusoracle/no-204-if-content", "Hello")
5859
//assertTrue(faultsCategories.any { it == ExperimentalFaultCategory.HTTP_STATUS_NO_204_IF_CONTENT })
5960

61+
//304
62+
assertHasAtLeastOne(solution, HttpVerb.POST, 304, "/api/statusoracle/no-304-if-no-get-or-head", null)
63+
assertTrue(faultsCategories.any { it == ExperimentalFaultCategory.HTTP_STATUS_NO_304_IF_NO_GET_OR_HEAD })
64+
6065
//401 and 403
6166
assertHasAtLeastOne(solution, HttpVerb.GET, 401, "/api/statusoracle/no-401-if-no-auth", null)
6267
assertHasAtLeastOne(solution, HttpVerb.GET, 403, "/api/statusoracle/no-403-if-no-401", null)
6368
assertTrue(faultsCategories.any { it == ExperimentalFaultCategory.HTTP_STATUS_NO_401_IF_NO_AUTH })
6469
assertTrue(faultsCategories.any { it == ExperimentalFaultCategory.HTTP_STATUS_NO_403_IF_NO_401 })
70+
assertTrue(faultsCategories.any { it == ExperimentalFaultCategory.HTTP_STATUS_NO_401_IF_NO_WWW_AUTHENTICATE })
71+
72+
//405
73+
assertHasAtLeastOne(solution, HttpVerb.GET, 405, "/api/statusoracle/no-405-if-no-allow", null)
74+
assertTrue(faultsCategories.any{ it == ExperimentalFaultCategory.HTTP_STATUS_NO_405_IF_NO_ALLOW})
6575

6676
//406
6777
assertHasAtLeastOne(solution, HttpVerb.POST, 406, "/api/statusoracle/has-406-if-accept", null)
@@ -72,6 +82,10 @@ class StatusOracleEMTest : SpringTestBase(){
7282
assertHasAtLeastOne(solution, HttpVerb.POST, 415, "/api/statusoracle/no-415-if-no-payload", null)
7383
assertTrue(faultsCategories.any { it == ExperimentalFaultCategory.HTTP_STATUS_NO_413_IF_NO_PAYLOAD })
7484
assertTrue(faultsCategories.any { it == ExperimentalFaultCategory.HTTP_STATUS_NO_415_IF_NO_PAYLOAD })
85+
86+
//501
87+
assertHasAtLeastOne(solution, HttpVerb.GET, 501, "/api/statusoracle/no-501-if-implemented", null)
88+
assertTrue(faultsCategories.any { it == ExperimentalFaultCategory.HTTP_STATUS_NO_501_IF_IMPLEMENTED })
7589
}
7690
}
7791
}

core/src/main/kotlin/org/evomaster/core/problem/enterprise/ExperimentalFaultCategory.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ enum class ExperimentalFaultCategory(
4848
HTTP_STATUS_NO_401_IF_NO_AUTH(957, "no-401-if-no-auth", "401WhenNoAuth", "TODO"),
4949
HTTP_STATUS_NO_403_IF_NO_401(958, "no-403-if-no-401", "403WhenNo401", "TODO"),
5050
HTTP_STATUS_HAS_406_IF_ACCEPT(959, "has-406-if-accept", "406WhenValid", "TODO"),
51+
HTTP_STATUS_NO_304_IF_NO_GET_OR_HEAD(960, "no-304-if-no-get-or-head", "", "TODO"),
52+
HTTP_STATUS_NO_401_IF_NO_WWW_AUTHENTICATE(961, "no-401-if-no-www-authenticate", "", "TODO"),
53+
HTTP_STATUS_NO_405_IF_NO_ALLOW(962, "no-405-if-no-allow", "", "TODO"),
54+
HTTP_STATUS_NO_501_IF_IMPLEMENTED(963, "no-501-if-implemented", "", "TODO"),
5155

5256

5357
//3xx: GraphQL
@@ -72,7 +76,7 @@ enum class ExperimentalFaultCategory(
7276
"TODO"),
7377

7478
//5xx: Web Frontend
75-
WEB_BROKEN_LINK(960, "Broken Link", "returnsBrokenLink",
79+
WEB_BROKEN_LINK(980, "Broken Link", "returnsBrokenLink",
7680
"TODO"),
7781
//6xx: mobile
7882

core/src/main/kotlin/org/evomaster/core/problem/httpws/HttpWsCallResult.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ abstract class HttpWsCallResult : EnterpriseActionResult {
2929
const val LOCATION = "LOCATION"
3030
const val ALLOW = "ALLOW"
3131
const val RESPONSE_TIME_MS = "RESPONSE_TIME_MS"
32+
const val HEADER_PREFIX = "HTTP_HEADER_"
3233

3334
const val VULNERABLE_SSRF = "VULNERABLE_SSRF"
3435
const val VULNERABLE_SQLI = "VULNERABLE_SQLI"
@@ -97,6 +98,15 @@ abstract class HttpWsCallResult : EnterpriseActionResult {
9798

9899
fun getLocation(): String? = getResultValue(LOCATION)
99100

101+
fun setHeaders(headers: Map<String, List<String>>?) {
102+
headers?.forEach {
103+
//RFC specs says to concatenate duplicated entries with a ",", but for Set-Cookie
104+
addResultValue("$HEADER_PREFIX${it.key.lowercase()}", it.value.joinToString(","))
105+
}
106+
}
107+
108+
fun getHeader(name: String): String? = getResultValue("$HEADER_PREFIX${name.lowercase()}")
109+
100110
fun setAllow(allow: String?){
101111
if(allow != null) {
102112
addResultValue(ALLOW, allow)

core/src/main/kotlin/org/evomaster/core/problem/rest/oracle/HttpStatusOracle.kt

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,15 @@ import org.evomaster.core.problem.rest.schema.SchemaUtils
2424
* no-201-if-get
2525
* no-201-if-patch
2626
* no-204-if-content
27+
* no-304-if-no-get-or-head
28+
* no-401-if-no-www-authenticate
29+
* no-405-if-no-allow
2730
* no-413-if-no-payload
2831
* no-415-if-no-payload
2932
* no-401-if-no-auth (schema)
3033
* no-403-if-no-401 (schema)
3134
* has-406-if-accept (schema)
35+
* no-501-if-implemented
3236
*
3337
*
3438
* IMPORTANT: in contrast to what done in [HttpSemanticsOracle], here there is no need to construct any test case
@@ -69,24 +73,40 @@ object HttpStatusOracle {
6973

7074
val hasBody = bodyParam != null && bodyParam.primaryGene().getValueAsRawString().isNotEmpty()
7175

72-
if(status == 413 && !hasBody){
73-
faults.add(ExperimentalFaultCategory.HTTP_STATUS_NO_413_IF_NO_PAYLOAD)
76+
if(status == 304 && (verb != HttpVerb.GET && verb != HttpVerb.HEAD)){
77+
faults.add(ExperimentalFaultCategory.HTTP_STATUS_NO_304_IF_NO_GET_OR_HEAD)
7478
}
7579

76-
if(status == 415 && !hasBody){
77-
faults.add(ExperimentalFaultCategory.HTTP_STATUS_NO_415_IF_NO_PAYLOAD)
80+
if(status == 401 && !SchemaUtils.hasAuthDefinition(schema)){
81+
faults.add(ExperimentalFaultCategory.HTTP_STATUS_NO_401_IF_NO_AUTH)
82+
}
83+
84+
if(status == 401 && result.getHeader("www-authenticate").isNullOrEmpty()){
85+
faults.add(ExperimentalFaultCategory.HTTP_STATUS_NO_401_IF_NO_WWW_AUTHENTICATE)
86+
}
87+
88+
if(status == 403 && !SchemaUtils.getDeclaredStatusInResponse(call.endpoint, schema).contains(401)){
89+
faults.add(ExperimentalFaultCategory.HTTP_STATUS_NO_403_IF_NO_401)
90+
}
91+
92+
if(status == 405 && result.getHeader("allow").isNullOrEmpty()){
93+
faults.add(ExperimentalFaultCategory.HTTP_STATUS_NO_405_IF_NO_ALLOW)
7894
}
7995

8096
if(status == 406 && !call.isForRobustnessTesting()){
8197
faults.add(ExperimentalFaultCategory.HTTP_STATUS_HAS_406_IF_ACCEPT)
8298
}
8399

84-
if(status == 401 && !SchemaUtils.hasAuthDefinition(schema)){
85-
faults.add(ExperimentalFaultCategory.HTTP_STATUS_NO_401_IF_NO_AUTH)
100+
if(status == 413 && !hasBody){
101+
faults.add(ExperimentalFaultCategory.HTTP_STATUS_NO_413_IF_NO_PAYLOAD)
86102
}
87103

88-
if(status == 403 && !SchemaUtils.getDeclaredStatusInResponse(call.endpoint, schema).contains(401)){
89-
faults.add(ExperimentalFaultCategory.HTTP_STATUS_NO_403_IF_NO_401)
104+
if(status == 415 && !hasBody){
105+
faults.add(ExperimentalFaultCategory.HTTP_STATUS_NO_415_IF_NO_PAYLOAD)
106+
}
107+
108+
if(status == 501){
109+
faults.add(ExperimentalFaultCategory.HTTP_STATUS_NO_501_IF_IMPLEMENTED)
90110
}
91111

92112
return faults

core/src/main/kotlin/org/evomaster/core/problem/rest/service/fitness/AbstractRestFitness.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,7 @@ abstract class AbstractRestFitness : HttpWsFitness<RestIndividual>() {
828828
rcr.setLocation(response.location?.toString())
829829
rcr.setAllow(response.allowedMethods.joinToString(","))
830830
rcr.setAppliedLink(appliedLink)
831+
rcr.setHeaders(response.stringHeaders)
831832

832833
handlePossibleConnectionClose(response)
833834

0 commit comments

Comments
 (0)