Skip to content

ModSecurity fails to parse request body with special characters when using JSON requestBodyProcessor #1879

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
msamad opened this issue Aug 27, 2018 · 9 comments
Assignees
Labels
3.x Related to ModSecurity version 3.x RIP - libmodsecurity RIP - Type - Usage Related with usage (not a bug)
Milestone

Comments

@msamad
Copy link

msamad commented Aug 27, 2018

Hi,
ModSecurity (v3.0.2) fails the JSON requestBodyProcessor for a request when there are special characters such as '\r', '\n' etc in the body.

e.g. Following request results in 400 Bad Request where rule 200002 fails.

curl --request POST \
--header "Content-Type:application/json"  \
--data {"test":"Testing new line. \r\n Another line."} \
http://localhost:8080/some-resource

ModSecurity transaction log

{
	"transaction": {
		"client_ip": "172.30.0.1",
		"messages": [{
				"message": "Failed to parse request body.",
				"details": {
					"ver": "",
					"severity": "2",
					"reference": "v862,1",
					"ruleId": "200002",
					"tags": [],
					"rev": "",
					"maturity": "0",
					"file": "/etc/nginx/modsecurity/modsecurity.conf",
					"lineNumber": "44",
					"data": "JSON parsing error: lexical error: invalid string in json text.\n",
					"match": "Matched \"Operator `Eq' with parameter `0' against variable `REQBODY_ERROR' (Value: `1' )",
					"accuracy": "0"
				}
			}
		],
		"producer": {
			"connector": "ModSecurity-nginx v1.0.0",
			"components": ["OWASP_CRS/3.0.2\""],
			"modsecurity": "ModSecurity v3.0.2 (Linux)",
			"secrules_engine": "Enabled"
		},
		"host_port": 8080,
		"request": {
			"headers": {
				"Content-Length": "43",
				"Accept": "*/*",
				"User-Agent": "curl/7.51.0",
				"Host": "localhost:8080",
				"Content-Type": "application/json"
			},
			"http_version": 1.1,
			"method": "POST",
			"uri": "/some-resource"
		},
		"server_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
		"host_ip": "172.30.0.1",
		"client_port": 56170,
		"time_stamp": "Mon Aug 27 03:45:07 2018",
		"response": {
			"http_code": 400
		},
		"id": "15353415074.588818"
	}
}

Nginx access log escapes the request body properly.

{
	"time_local": "27/Aug/2018:03:45:07 +0000",
	"remote_addr": "172.30.0.1",
	"remote_port": "56170",
	"remote_user": "",
	"request": "POST /some-resource HTTP/1.1",
	"status": 400,
	"request_length": "854",
	"request_method": "POST",
	"content_length": "43",
	"content_type": "application/json",
	"bytes_sent": "589",
	"body_bytes_sent": "176",
	"request_body": "{test:Testing new line. \\r\\n Another line.}",
	"http_user_agent": "curl/7.51.0"
}

It seems that ModSecurity is not escaping the '\r', \n' etc which results in a failure when parsing into a JSON object.

Thanks.

@msamad msamad changed the title ModSecurity fails to parse request body with special characters using JSON requestBodyProcessor ModSecurity fails to parse request body with special characters when using JSON requestBodyProcessor Aug 27, 2018
@victorhora victorhora self-assigned this Aug 27, 2018
@victorhora victorhora added RIP - libmodsecurity 3.x Related to ModSecurity version 3.x RIP - Type - Usage Related with usage (not a bug) labels Aug 27, 2018
@victorhora
Copy link
Contributor

@msamad I've managed to get this working fine by copying and pasting the request into Burp Suite like so:

image

The audit logs says:

 ---ndUBCIjQ---A--
[27/Aug/2018:11:16:25 -0400] 153538298564.201965 192.168.37.1 55508 192.168.37.1 8080
---ndUBCIjQ---B--
POST /test.html HTTP/1.1
Host: example.org
Content-Type: application/json
Content-Length: 47

---ndUBCIjQ---C--
{"test":"Testing new line. \r\n Another line."}

---ndUBCIjQ---F--
HTTP/1.1 404
Server: nginx/1.15.2
Date: Mon, 27 Aug 2018 15:16:25 GMT
Content-Length: 169
Content-Type: text/html
Connection: keep-alive

And debug logs suggests the JSON was parsed successfully:

[4] Starting phase REQUEST_BODY. (SecRules 2)
[4] Adding request argument (JSON): name "json.test", value "Testing new line.
 Another line."
[9] This phase consists of 4 rule(s).
[4] (Rule: 200002) Executing operator "Eq" with param "0" against REQBODY_ERROR.
[9] Target value: "0" (Variable: REQBODY_ERROR)
[4] Rule returned 0.

I think it might be something to with the way the cURL is sending the content on the wire. If I simply add single quotes (') to the "--data" parameter like this:

curl --request POST
--header "Content-Type:application/json"
--data '{"test":"Testing new line. \r\n Another line."}'
http://localhost:8080/some-resource

The request also went by the JSON parser just fine.

@msamad
Copy link
Author

msamad commented Aug 27, 2018

thanks @victorhora for looking into this.
Yes, adding a single quote in curl gets it working, I purposely put it without quote in the cURL since the same scenario happens when sending request from a java client - haven't tried any other language. If the server side is Java again, it receives it properly i.e. takes care of escaping during parsing.

Nginx seems to be doing the similar thing i.e. taking care of escaping - at least during the access log.

{
	"request_body": "{test:Testing new line. \\r\\n Another line.}",
}

It will be nice if ModSecurity takes care of it when parsing. Your views?

Thanks.

@victorhora
Copy link
Contributor

@msamad, Afaik, the library that ModSecurity uses for parsing JSON (YAJL) should attempt to escape control chars such as \r which would explain why it gets parsed correctly when sending the request properly using HTTP clients. You can also test how YAJL parses data by using the json_verify tool that comes with YAJL

root@debian:~# echo {\"test\":\"Testing new line. \r\n Another line.\"} | json_verify
JSON is valid

I've triggered the requests again using cURL without quotes (') to the "--data" parameter and the bytes on the wire (PCAP) shows that this leads to the JSON data being sent without double quotes (") around the values:

image

While using cURL with quotes (') to the "--data" parameter leads to the JSON having double quotes (") around the values as per JSON standard (RFC-8259#section-7:

image

Note also how the HTTP dissector of Wireshark is also able to correctly parse the data from packet.

That being said, I believe that the behaviour of ModSecurity and YAJL is correct and no extra escaping is needed at least when using cURL to reproduce this.

Maybe your Java client/library is not wrapping the values in double quotes for some reason? You may try a using a similar approach to confirm.

@msamad
Copy link
Author

msamad commented Aug 28, 2018

Thanks @victorhora
I'll try the similar approach on my client and check how it sends the data.

@msamad
Copy link
Author

msamad commented Sep 21, 2018

Hi @victorhora,
I tried it with WireShark - sorry took me a while to get back to this one.

So I sent the request using a java client to ModSecurity (Nginx) and got back a 400 as ModSecurity failed to parse the request body. WireShark though sees it as a proper JSON object, image below.

image

I enabled request body logging in nginx access log '"request_body": "$request_body", ' and in ModSecurity SecAuditLogParts ABCIJDEHZ

Below is what I noticed when I tried calling the same using curl and java client.

curl - using the single quote - the request passes without any issue

curl --request PUT \
--header "Content-Type:application/json"  \
--data '{"test":"Testing new line. \r\n Another line."}' \
http://localhost:8443/some-resource

This is what I get in nginx access and modsecurity logs.

//nginx access log: 
"request_body": "{\"test\":\"Testing new line. \\r\\n Another line.\"}"

As this request passes, I purposely added base64 keyword in the field value for modsecurity to reject it and produce log

//nginx access log
"request_body": "{\"test\":\"Testing new line. \\r\\n Another line.base64\"}"

//modsecurity log
"body": "{\"test\":\"Testing new line. \\r\\n Another line.base64\"}"

Now, without using the single quote - this gets rejected of course.

curl --request PUT \
--header "Content-Type:application/json"  \
--data {"test":"Testing new line. \r\n Another line."} \
http://localhost:8443/some-resource

I get this in nginx access and modsecurity logs.

//nginx access log
"request_body": "{test:Testing new line. \\r\\n Another line.}"

//modsecurity log
"body": "{test:Testing new line. \\r\\n Another line.}"

The above is bad because double quotes have been removed and even WireShark doesn't recognize it as valid json.

Now when I make the call using Java client. I get this in nginx access and modsecurity logs.

//nginx access log
"request_body": "{\"test\":\"Testing new line. \r\n Another line.\"}"

//modsecurity log
"body": "{\"test\":\"Testing new line. \r\n Another line.\"}"

The request gets rejected by modsecurity with failed to parse request body error but WireShark is able to recognize it as valid json. Notice that double quotes have not been removed and also no extra escape \ for \r and \n.

I verified the curl (using single quote) result and java result using json_verify and all are valid.

# curl results
# without double quotes
bash-4.2$ echo {\"test\":\"Testing new line. \\r\\n Another line.base64\"} | json_verify
JSON is valid
# with double quotes
bash-4.2$ echo "{\"test\":\"Testing new line. \\r\\n Another line.base64\"}" | json_verify
JSON is valid

# java results
# without double quotes
bash-4.2$ echo {\"test\":\"Testing new line. \r\n Another line.\"} | json_verify
JSON is valid
# with double quotes
bash-4.2$ echo "{\"test\":\"Testing new line. \r\n Another line.\"}" | json_verify
JSON is valid

if json_verify is successfully validating these both results with or without double quotes around the same message in echo, then why would modsecurity work with one (curl) and not the other (java)?

@msamad
Copy link
Author

msamad commented Sep 21, 2018

Also tried it with Python script and got 400 because modsecurity failed to parse request body.

Script:

import requests
url = 'http://localhost:8443/some-resource'
data = '{"test":"Testing new line. \r\n Another line."}'
response = requests.post(url, data=data,headers={"Content-Type": "application/json"})
print(response)
print(response.text)

I get this in nginx access and modsecurity logs.

//nginx access log
"request_body": "{\"test\":\"Testing new line. \r\n Another line.\"}"

//modsecurity log
"body": "{\"test\":\"Testing new line. \r\n Another line.\"}"

This is same as what I get for java client.

@victorhora
Copy link
Contributor

Hi @msamad

I believe that the problem here is that some of the clients that generate the requests are actually interpreting the \r\n characters and instead of sending the \r\n characters in their "raw" ASCII format (0x5c 0x72 0x5c 0x6e), they are instead sending the \r\n as their actual control characters sequence (0x0d 0x0a).

If you pay attention to the Wireshark capture compared to yours you can notice the differences clearly:

image

In the same fashion, I've checked that the Python script example you posted also interprets the \r\n and send the newline bytes in the request with 0x0d 0x0a (probably due to the way Python or the Requests HTTP library interpret this and hence why it triggers the rule.

"A string is a sequence of Unicode code points wrapped with quotation marks
(U+0022). All characters may be placed within the quotation marks except for the
characters that must be escaped: quotation mark (U+0022), reverse solidus
(U+005C), and the control characters U+0000 to U+001F. There are two-character
escape sequence representations of some characters.
"
https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf

This leads me to believe that the JSON standard, prohibit control characters (0x0d 0x0a) directly on the JSON key or value fields. It seems to be the case with the json_verify tool:

image

The content invalid.json file being like so:

root@debian:~/tmp/json# cat -A invalid.json
{"test":"Testing new line. ^M$
 Another line."}^M$
root@debian:~/tmp/json#

Note: cat invoked with -A to show non printable characters (0x0d 0x0a)

Hence why requests that get sent to ModSecurity and that are handed over to the JSON library (YAJL) for processing that contains these characters will always result in an error.

Now, if you could somehow tell your client application to not process/ignore the \r\n and send the string as is, ModSecurity/YAJL should be able to process it properly.

Alternatively, you could try adding an extra scape ("\"). If I try this with the Python script you've provided it works fine:

image

And this is what ModSecurity says:

debug_log:

[9] Appending request body: 47 bytes. Limit set to: 13107200.000000
[4] Starting phase REQUEST_BODY. (SecRules 2)
[4] Adding request argument (JSON): name "json.test", value "Testing new line.
 Another line."
[9] This phase consists of 3 rule(s).

audit_log:

---x7m9xteG---B--
GET / HTTP/1.1
Host: localhost:8080
Content-Length: 47
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: python-requests/2.4.3 CPython/2.7.9 Linux/3.16.0-4-amd64
Connection: keep-alive
Content-Type: application/json

---x7m9xteG---C--
{"test":"Testing new line. \r\n Another line."}

---x7m9xteG---E--
TEST OK\x0a

---x7m9xteG---F--
HTTP/1.1 200
Server: nginx/1.13.11
Date: Fri, 21 Sep 2018 19:17:19 GMT
Content-Length: 8
Content-Type: text/html
Last-Modified: Fri, 21 Sep 2018 19:14:00 GMT
Connection: keep-alive
ETag: "5ba542f8-8"

@msamad
Copy link
Author

msamad commented Sep 24, 2018

Hi @victorhora,
Thank you very much for the details

Escaping the \r and \n gets it to pass their "raw" ASCII format (0x5c 0x72 0x5c 0x6e).

The following python script results in ASCII sequence instead of control character sequence.

import requests
url = 'http://localhost:8443/some-resource'
data = '{"test":"Testing new line. \\r\\n Another line."}'
response = requests.post(url, data=data,headers={"Content-Type": "application/json"})
print(response)
print(response.text)

image

I needed to escape the json escape sequence for client's language/platform - feel dumb for not doing that but good to know the details.

Some good explanation on stackflow
https://stackoverflow.com/questions/42068/how-do-i-handle-newlines-in-json

As most of programming languages uses \ for quoting you should escape escape syntax (double-escape - once for language/platform, once for Json itself):

@msamad
Copy link
Author

msamad commented Sep 24, 2018

One more thing to mention that the following python script will be successful without escaping \r \n as the language parses the json properly and takes care of any escaping since the data is being passed json as object and not json as string.

import requests
url = 'http://localhost:8443/some-resource'
data = {"test":"Testing new line. \r\n Another line."}
response = requests.post(url, json=data,headers={"Content-Type": "application/json"})
print(response)
print(response.text)

Notice the json=data part which is different from data=data where we escape \r \n

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.x Related to ModSecurity version 3.x RIP - libmodsecurity RIP - Type - Usage Related with usage (not a bug)
Projects
None yet
Development

No branches or pull requests

2 participants