在 Nginx 日志中记录请求的真实 IP,而非 CloudFlare 的节点 IP

更新日期: 2024-09-18 阅读次数: 1290 字数: 962 分类: Nginx

最近发现 Joomla 搭建的网站中出现了大量的垃圾评论。 从 Nginx 日志里的行为看是通过程序自动提交的,而非手动提交。因为提交成功后,没有自动跳转到成功页。 暂时不确定对方是如何绕过 recaptcha V2 的验证组件的。。。

于是,想先通过简单粗暴的方式,看看对方的请求 IP 是否有规律。(虽然希望渺茫)

但 Nginx access log 记录的是 cloudflare 节点的 IP (reverse proxy network),而非用户的真实 IP。 查询了一下如何通过更改 Nginx 配置来记录访客的真实 IP。

配置

有个 github 上的配置,可以参考:

https://github.com/ergin/nginx-cloudflare-real-ip

简单概括一下就是:

  • 新建一个 /etc/nginx/cloudflare 的文件,内容是里面的配置
  • 在 /etc/nginx/nginx.conf 中的 http block,include 上面的配置
  • nginx -t & reload 配置

实际上,这个配置对我来说无效,下面会说明如何修改。

ngx_http_realip_module

配置中,用到的 set_real_ip_from 和 real_ip_header 属于 ngx_http_realip_module 模块。

The ngx_http_realip_module module is used to change the client address and optional port to those sent in the specified header field. This module is not built by default, it should be enabled with the --with-http_realip_module configuration parameter.

参考:

https://nginx.org/en/docs/http/ngx_http_realip_module.html

查看是否启用了 ngx_http_realip_module

# nginx -V
nginx version: nginx/1.24.0 (Ubuntu)
built with OpenSSL 3.0.10 1 Aug 2023 (running with OpenSSL 3.0.13 30 Jan 2024)
TLS SNI support enabled
configure arguments: --with-cc-opt='-g -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffile-prefix-map=/build/nginx-uqDps2/nginx-1.24.0=. -flto=auto -ffat-lto-objects -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -fdebug-prefix-map=/build/nginx-uqDps2/nginx-1.24.0=/usr/src/nginx-1.24.0-2ubuntu7 -fPIC -Wdate-time -D_FORTIFY_SOURCE=3' --with-ld-opt='-Wl,-Bsymbolic-functions -flto=auto -ffat-lto-objects -Wl,-z,relro -Wl,-z,now -fPIC' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=stderr --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --modules-path=/usr/lib/nginx/modules --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-compat --with-debug --with-pcre-jit --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_v2_module --with-http_dav_module --with-http_slice_module --with-threads --with-http_addition_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_secure_link_module --with-http_sub_module --with-mail_ssl_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-stream_realip_module --with-http_geoip_module=dynamic --with-http_image_filter_module=dynamic --with-http_perl_module=dynamic --with-http_xslt_module=dynamic --with-mail=dynamic --with-stream=dynamic --with-stream_geoip_module=dynamic

可以搜索到,Ubuntu 24.04 LTS 通过 apt 安装的 nginx 默认启用了 ngx_http_realip_module 这个模块。 即,包含

--with-http_realip_module

set_real_ip_from

Defines trusted addresses that are known to send correct replacement addresses.

real_ip_header

Defines the request header field whose value will be used to replace the client address.

测试

从我的电脑发起一个请求,然后去 nginx 日志看,access.log 中存储的还是 cloudflare 节点的 IP。

更改配置

参考这个文章:

https://djangocas.dev/blog/nginx/nginx-access-log-with-real-x-forwarded-for-ip-instead-of-proxy-ip/

  • 一种方式是手动修改 log 的日志格式,修改里面的参数变量;
  • 另一种方式,就是上面的方式,但是配置略有不同
real_ip_header X-Forwarded-For;
real_ip_recursive on;
set_real_ip_from 0.0.0.0/0;

这里用 X-Forwarded-For 替换掉了之前 github 上那个配置中的 CF-Connecting-IP。

但是 set_real_ip_from 我依然保留了之前的配置,没有用这个所有都应用的配置。

然后再次测试,就能在日志中看到真实的请求 IP 了。。。

CF-Connecting-IP 与 X-Forwarded-For 的区别

查了一下,大概的解释是:

  • CF-Connecting-IP 是 CloudFlare 专有的记录真实 IP 的 http 头
  • X-Forwarded-For 是一个通用的 HTTP 扩展头部字段,用于在代理服务器和负载均衡器之间传递客户端的原始 IP 地址。当一个请求经过多个代理服务器时,每个代理服务器都会将客户端的 IP 地址添加到 X-Forwarded-For 头部字段中,用逗号分隔。这使得后端服务器能够获取到客户端的真实 IP 地址。但是这个会记录多个 IP 地址(例如,经过多个代理服务器)

我还是没有理解为何 CF-Connecting-IP 不好使。

real_ip_recursive on

递归的去除所配置中的可信IP。排除set_real_ip_from里面出现的IP。如果出现了未出现这些IP段的IP,那么这个IP将被认为是用户的IP。

更简单粗暴的方式

nginx 不同域名使用不同的 log_format.

例如,在 cf_format 中记录 http_x_forwarded_for 和 http_cf_connecting_ip 的值。

http {
    log_format domain1_format '$remote_addr - $remote_user [$time_local] "$request" '
                              '$status $body_bytes_sent "$http_referer" '
                              '"$http_user_agent" "domain1_specific_info"';

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

    server {
        listen 80;
        server_name domain1.com;
        access_log /path/to/domain1_access.log domain1_format;
        # 其他配置
    }

    server {
        listen 80;
        server_name domain2.com;
        access_log /path/to/domain2_access.log cf_format;
        # 其他配置
    }
}

记录的示例

127.0.0.1 -- [18/Sep/2024:15:00:42 +0800] "GET /a.html HTTP/1.1" 200 22863 "-" "Mozilla/5.0 (compatible; YandexBot/3.0; +http://yandex.com/bots)" - 213.180.203.49, 172.70.143.78, 127.0.0.1 - "213.180.203.49"
172.70.143.78 -- [18/Sep/2024:15:00:42 +0800] "GET /b.html HTTP/2.0" 200 22847 "-" "Mozilla/5.0 (compatible; YandexBot/3.0; +http://yandex.com/bots)" - 213.180.203.49 - "213.180.203.49"

微信关注我哦 👍

大象工具微信公众号

我是来自山东烟台的一名开发者,有感兴趣的话题,或者软件开发需求,欢迎加微信 zhongwei 聊聊, 查看更多联系方式

tags: joomla