[PATCH] Escape strings produced by the `%V` format specification
Joshua Spence
josh at joshuaspence.com
Tue Jan 3 06:50:37 UTC 2017
# HG changeset patch
# User Joshua Spence <josh at joshuaspence.com>
# Date 1483420178 -39600
# Tue Jan 03 16:09:38 2017 +1100
# Branch error_log_escape
# Node ID 30a9ce42aadfcf135f3ad95a296c95e7044d5f9d
# Parent a42afc225e98afe1f7c3b428c81c8af7eba362c0
Escape strings produced by the `%V` format specification
Currently nginx error logs loosely adhere to the following pattern:
```
%{TIMESTAMP} [%{SEVERITY}] %{PROCESS_ID}#%{THREAD_ID}:( *%{CONNECTION_ID})?%{MESSAGE}(, %{KEY}: "%{VALUE}")*
```
In our environment we are using Filebeat to ship nginx error logs into Logstash. Our Logstash filter for parsing nginx error logs is as follows:
```
filter {
grok {
match => {
"message" => "(?<timestamp>%{YEAR}/%{MONTHNUM2}/%{MONTHDAY} %{TIME}) \[%{LOGLEVEL:severity}\] %{POSINT:process_id:int}#%{NONNEGINT:thread_id:int}:(?: \*%{NONNEGINT:connection_id:int})? (?<message>[^,]*)(?:, %{GREEDYDATA:nginx_keyvalue_data})?"
}
overwrite => ["message"]
}
date {
match => ["timestamp", "yyyy/MM/dd HH:mm:ss"]
remove_field => ["timestamp"]
}
kv {
field_split => ", "
remove_field => "nginx_keyvalue_data"
source => "nginx_keyvalue_data"
value_split => ": "
}
}
```
The problem, however, is that it is difficult to unambigiously parse the key-value data from the nginx error log format because of a lack of data sanitization. Specifically, consider the following entry from our nginx error logs:
```
2016/11/15 03:45:51 [error] 23770#23770: *21535000 open() "/REDACTED/+"BauGutachter"++41+2016+.ch&tbo=1" failed (2: No such file or directory), client: REDACTED, server: REDACTED, request: "GET /job/+%22BauGutachter%22++41+2016+.ch&tbo=1 HTTP/1.1", host: "REDACTED", referrer: "https://www.google.com.au/search?hl=en&q="BauGutachter" +41 2016 .ch&num=100&start=0"
```
Specifically, the problem in the above sample is the `referrer` field, the value of which contains double quotes. I think that it makes sense to escape `\` and `"` characters before they are written to the error log.
diff -r a42afc225e98 -r 30a9ce42aadf src/core/ngx_string.c
--- a/src/core/ngx_string.c Tue Dec 27 17:23:08 2016 +0300
+++ b/src/core/ngx_string.c Tue Jan 03 16:09:38 2017 +1100
@@ -15,7 +15,7 @@
const u_char *basis, ngx_uint_t padding);
static ngx_int_t ngx_decode_base64_internal(ngx_str_t *dst, ngx_str_t *src,
const u_char *basis);
-
+static uintptr_t ngx_escape_string(u_char *dst, u_char *src, size_t size);
void
ngx_strlow(u_char *dst, u_char *src, size_t n)
@@ -235,7 +235,7 @@
v = va_arg(args, ngx_str_t *);
len = ngx_min(((size_t) (last - buf)), v->len);
- buf = ngx_cpymem(buf, v->data, len);
+ buf = (u_char *) ngx_escape_string(buf, v->data, len);
fmt++;
continue;
@@ -1844,6 +1844,43 @@
}
+static uintptr_t
+ngx_escape_string(u_char *dst, u_char *src, size_t size)
+{
+ u_char ch;
+ ngx_uint_t len;
+
+ if (dst == NULL) {
+ len = 0;
+
+ while (size) {
+ ch = *src++;
+
+ if (ch == '\\' || ch == '"') {
+ len++;
+ }
+
+ size--;
+ }
+
+ return (uintptr_t) len;
+ }
+
+ while (size) {
+ ch = *src++;
+
+ if (ch == '\\' || ch == '"') {
+ *dst++ = '\\';
+ }
+
+ *dst++ = ch;
+ size--;
+ }
+
+ return (uintptr_t) dst;
+}
+
+
void
ngx_str_rbtree_insert_value(ngx_rbtree_node_t *temp,
ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel)
More information about the nginx-devel
mailing list