NGINX:在access_log中混淆密码

时间:2019-03-28 11:27:37

标签: nginx logging nginx-location nginx-config

我要在访问日志中记录$request_body

但是某些请求具有一些敏感的JSON字段,例如密码。

示例:

[2019-03-28] 201 - POST /api/user/add HTTP/1.1 - {\x22email\x22:\x22test@test.com\x22,\x22password\x22:\x22myPassword\x22}

是否有一种方法来混淆密码值,以便输出看起来像这样:

[2019-03-28] 201 - POST /api/user/add HTTP/1.1 - {\x22email\x22:\x22test@test.com\x22,\x22password\x22:\x22****\x22}

2 个答案:

答案 0 :(得分:6)

以下是一些正则表达式模式,可用于混淆各种格式的请求正文。

当然,您需要做的第一件事是使用log_format指令将混淆的数据添加到日志文件行格式:

log_format custom '$remote_addr - $remote_user [$time_local] '
                    '"$request" "$obfuscated_request_body" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';

让我们看一下下面的正文数据格式(假设我们需要混淆的字段是password)。

  • 请求正文是JSON字符串(通常是REST API请求)

JSON示例:

{"email":"test@test.com","password":"myPassword"}

转义的JSON字符串:

{\x22email\x22:\x22test@test.com\x22,\x22password\x22:\x22myPassword\x22}

nginx map块:

map $request_body $obfuscated_request_body {
    "~(.*[{,]\\x22password\\x22:\\x22).*?(\\x22[,}].*)" $1********$2;
    default $request_body;
}
  • 请求正文是namevalue对的JSON数组(由jQuery serializeArray()函数返回)

JSON示例:

[{"name":"email","value":"test@test.com"},{"name":"password","value":"myPassword"}]

转义的JSON字符串:

[{\x22name\x22:\x22email\x22,\x22value\x22:\x22test@test.com\x22},{\x22name\x22:\x22password\x22,\x22value\x22:\x22myPassword\x22}]

nginx map块:

map $request_body $obfuscated_request_body {
    "~(.*[\[,]{\\x22name\\x22:\\x22password\\x22,\\x22value\\x22:\\x22).*?(\\x22}[,\]].*)" $1********$2;
    default $request_body;
}
  • 请求正文是一个经过编码的字符串(由带有enctype="application/x-www-form-urlencoded"的HTML表单提交)

POST正文示例:

login=test%40test.com&password=myPassword

nginx map块: nginx map块:

map $request_body $obfuscated_request_body {
    ~(^|.*&)(password=)[^&]*(&.*|$) $1$2********$3;
    default $request_body;
}

如果您需要混淆多个数据字段,则可以链接多个map转换:

log_format custom '$remote_addr - $remote_user [$time_local] '
                  '"$request" "$obfuscated_request_body_2" $status $body_bytes_sent '
                  '"$http_referer" "$http_user_agent"';

map $request_body $obfuscated_request_body_1 {
    "~(.*[{,]\\x22password\\x22:\\x22).*?(\\x22[,}].*)" $1********$2;
    default $request_body;
}

map $obfuscated_request_body_1 $obfuscated_request_body_2 {
    "~(.*[{,]\\x22email\\x22:\\x22).*?(\\x22[,}].*)" $1********$2;
    default $request_body;
}

所有给定的正则表达式只能在escape=default的nginx指令的log_format转义模式下使用!如果由于某种原因您需要将此模式更改为escape=json(可从nginx 1.11.8获得)或escape=none(可从nginx 1.13.10获得),我也为此转义模式构建了正则表达式,但是对于在指定pcre_jit on;指令之前,尽管有些奇怪的原因无法使它们与nginx一起使用(尽管它们通过了其他PCRE测试)。对于那些感兴趣的人,这些正则表达式是

  • 用于escape=json转义模式:
map $request_body $obfuscated_request_body {
    "~(.*[{,]\\\"password\\\":\\\")(?:[^\\]|\\{3}\"|\\{2}[bfnrt]|\\{4})*(\\\"[,}].*)" $1********$2;
    default $request_body;
}

用于JSON字符串,

map $request_body $obfuscated_request_body {
    "~(.*[\[,]{\\\"name\\\":\\\"password\\\",\\\"value\\\":\\\")(?:[^\\]|\\{3}\"|\\{2}[bfnrt]|\\{4})*(\\\"}[,\]].*)" $1********$2;
    default $request_body;
}

用于namevalue对的JSON数组。

  • 用于escape=none转义模式:
map $request_body $obfuscated_request_body {
    "~(.*[{,]\"password\":\")(?:[^\\\"]|\\.)*(\"[,}].*)' $1********$2;
    default $request_body;
}

用于JSON字符串,

map $request_body $obfuscated_request_body {
    "~(.*[\[,]{\"name\":\"password\",\"value\":\")(?:[^\\\"]|\\.)*(\"}[,\]].*)" $1********$2;
    default $request_body;
}

用于namevalue对的JSON数组。

奖金-混淆GET请求查询参数

有时,人们还需要混淆作为GET请求查询参数传递的数据。为此,在保留原始nginx访问日志格式的同时,让我们首先看一下默认的访问日志格式:

log_format combined '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';

nginx内置$request变量可以表示为$request_method $request_uri $server_protocol变量序列:

log_format combined '$remote_addr - $remote_user [$time_local] '
                    '"$request_method $request_uri $server_protocol" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';

我们需要混淆$request_uri变量数据的一部分:

log_format custom '$remote_addr - $remote_user [$time_local] '
                  '"$request_method $obfuscated_request_uri $server_protocol" $status $body_bytes_sent '
                  '"$http_referer" "$http_user_agent"';

map $request_uri $obfuscated_request_uri {
    ~(.+\?)(.*&)?(password=)[^&]*(&.*|$) $1$2$3********$4;
    default $request_uri;
}

要混淆多个查询参数,您可以如上所述链接多个map翻译。

更新-安全注意事项

Alvin Thompson评论了OP的问题,提到了一些攻击媒介,例如非常大的压缩请求。 值得一提的是,nginx将以压缩形式“原样”记录这些请求,因此日志文件不会以不可预测的方式增长。

假设我们的日志文件具有以下格式:

log_format debug '$remote_addr - $remote_user [$time_local] '
                 '"$request" $request_length $content_length '
                 '"$request_body" $status $body_bytes_sent '
                 '"$http_referer" "$http_user_agent"';

压缩后的5,000个空格的请求将记录为

127.0.0.1 - - [09/Feb/2020:05:27:41 +0200] "POST /dump.php HTTP/1.1" 193 41 "\x1F\x8B\x08\x00\x00\x00\x00\x00\x00\x0B\xED\xC11\x01\x00\x00\x00\xC2\xA0*\xEB\x9F\xD2\x14~@\x01\x00\x00\x00\x00o\x03`,\x0B\x87\x88\x13\x00\x00" 200 6881 "-" "curl/7.62.0"

如您所见,$request_length$content_length的值(193和41)反映了来自客户端的传入数据的长度,而不是表示已解压缩的字节数数据流。

为了过滤异常大的未压缩请求,您还可以按其长度过滤请求正文:

map $content_length $processed_request_body {
    # Here are some regexes for log filtering by POST body maximum size
    # (only one should be used at a time)

    # Content length value is 4 digits or more ($request_length > 999)
    "~(.*\d{4})" "Too big (request length $1 bytes)";

    # Content length > 499
    "~^((?:[5-9]|\d{2,})\d{2})" "Too big (request length $1 bytes)";

    # Content length > 2999
    "~^((?:[3-9]|\d{2,})\d{3})" "Too big (request length $1 bytes)";

    default $request_body;
}

map $processed_request_body $obfuscated_request_body {
    ...
    default $processed_request_body;
}

答案 1 :(得分:-1)

请参阅此博客,其中讨论了如何为日志屏蔽用户数据:https://www.nginx.com/blog/data-masking-user-privacy-nginscript/