Skip to content
23 changes: 14 additions & 9 deletions internal/bodyprocessors/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@ func (js *jsonBodyProcessor) ProcessRequest(reader io.Reader, v plugintypes.Tran
// Process with recursion limit
col := v.ArgsPost()
data, err := readJSON(ss, bpo.RequestBodyRecursionLimit)
if err != nil {
return err
}
// The collection is populated before checking the error to still perform a best effort inspection of the payload
for key, value := range data {
Comment thread
M4tteoP marked this conversation as resolved.
col.SetIndex(key, 0, value)
}
if err != nil {
return err
}

// Store the raw JSON in the TX variable for validateSchema
// This is needed because RequestBody is a Single interface without a Set method
Expand All @@ -59,12 +60,13 @@ func (js *jsonBodyProcessor) ProcessResponse(reader io.Reader, v plugintypes.Tra
// Process with no recursion limit as we don't have a directive for response body
col := v.ResponseArgs()
data, err := readJSON(ss, ignoreJSONRecursionLimit)
if err != nil {
return err
}
// The collection is populated before checking the error to still perform a best effort inspection of the payload
for key, value := range data {
Comment thread
M4tteoP marked this conversation as resolved.
col.SetIndex(key, 0, value)
}
if err != nil {
return err
}

// Store the raw JSON in the TX variable for validateSchema
// This is needed because ResponseBody is a Single interface without a Set method
Expand All @@ -80,12 +82,15 @@ func readJSON(s string, maxRecursion int) (map[string]string, error) {
res := make(map[string]string)
key := []byte("json")

json := gjson.Parse(s)
err := readItems(json, key, maxRecursion, res)
if err != nil {
return res, err
}
if !gjson.Valid(s) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please explain why are we parsing the json before verifying out is valid?

return res, errors.New("invalid JSON")
}
json := gjson.Parse(s)
err := readItems(json, key, maxRecursion, res)
return res, err
return res, nil
}

// Transform JSON to a map[string]string
Expand Down
86 changes: 86 additions & 0 deletions testing/engine/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,89 @@ SecRule ARGS:json.test3 "@eq 3" "id: 1010, phase:2, log, block"
SecRule RESPONSE_ARGS:json.test4 "@eq 3" "id: 1011, phase:4, log, block"
`,
})

var _ = profile.RegisterProfile(profile.Profile{
Meta: profile.Meta{
Author: "kabbohus",
Description: "Test if truncated JSON request/response body work",
Enabled: true,
Name: "truncatedjsonyaml",
},
Tests: []profile.Test{
{
Title: "json",
Stages: []profile.Stage{
{
Stage: profile.SubStage{
Input: profile.StageInput{
URI: "/index.php?json.test=456",
Method: "POST",
Headers: map[string]string{
"content-type": "application/json",
},
Data: `{"test":123, "test2": 456, "test3": [22, 44, 55]}`,
},
Output: profile.ExpectedOutput{
TriggeredRules: []int{
100,
101,
1100,
1101,
1104,
1010,
1000,
1011,
1111,
1200,
1201,
},
NonTriggeredRules: []int{
103,
1102,
1103,
1202,
1203,
},
Headers: map[string]string{
"Content-Type": "application/json",
},
Data: `{"test": 123, "test2": 456, "test3": [22, 44, 55], "test4": 3}`,
},
},
},
},
},
},
Rules: `
SecRequestBodyAccess On
SecResponseBodyAccess On
SecResponseBodyMimeType application/json
SecRequestBodyLimitAction ProcessPartial
SecRequestBodyLimit 40
SecRule REQUEST_HEADERS:content-type "application/json" "id: 100, phase:1, pass, log, ctl:requestBodyProcessor=JSON"
SecRule RESPONSE_HEADERS:content-type "application/json" "id:1000, phase:3, pass, log, ctl:responseBodyProcessor=JSON"
SecRule REQBODY_PROCESSOR "JSON" "id: 101,phase:2,log,block"

# This is commented out because we want to check if body processor can work with partial JSON
SecRule REQBODY_ERROR "!@eq 0" "id:1111, phase:2, log, block"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

SecRule REQUEST_BODY "456" "id:103, phase:2, log"
SecRule ARGS:json.test "@eq 123" "id:1100, phase:2, log, block"
SecRule ARGS:json.test3.0 "@eq 22" "id:1101, phase:2, log, block"
SecRule ARGS:json.test3.1 "@eq 44" "id:1102, phase:2, log, block"
SecRule ARGS:json.test3.2 "@eq 55" "id:1103, phase:2, log, block"

# Both GET and POST can be matched for the same key
SecRule ARGS:json.test "@eq 456" "id:1104, phase:2, log, block"

SecRule ARGS:json.test3 "@eq 1" "id: 1010, phase:2, log, block"

# Check ARGS_POST
SecRule ARGS_POST:json.test "@eq 123" "id:1200, phase:2, log, block"
SecRule ARGS_POST:json.test3.0 "@eq 22" "id:1201, phase:2, log, block"
SecRule ARGS_POST:json.test3.1 "@eq 44" "id:1202, phase:2, log, block"
SecRule ARGS_POST:json.test3.2 "@eq 55" "id:1203, phase:2, log, block"

SecRule RESPONSE_ARGS:json.test4 "@eq 3" "id: 1011, phase:4, log, block"
`,
})