Add a Directive to get a Proxied Client's IP Address (Closes #341)
As written in #341, this patch adds functionality to obtain a client's IP address, if the request has been proxied using the Proxy Protocol.
Besides the check, if the option is enabled, I added a check if the Proxy Protocol is actually being used.
Quality Gate passed
Issues
0 New issues
0 Accepted issues
Measures
0 Security Hotspots
0.0% Coverage on New Code
0.0% Duplication on New Code
Please let me know, if the whitespace changes in the readme should be removed. I made the changes using VS Codium and didn't know that it forces a newline encoding.
Please let me know, if the whitespace changes in the readme should be removed.
No worries, that's in a good place.
Anyway, thanks for the PR - I'm thinking about we should add new test cases for each new PR (which adds a new feature - like this). I mean please take a look at the regression test file of this repository, how could we make a config where we can see that the modified code does what the author wants. And this is important, because if someone in the future sends a new PR, we must be sure this behavior does not change. Do you have any idea?
Yeah, I have an idea for a test case. curl not only supports the Proxy Protocol ^1, but also the option to set the client's IP address ^2.
If that's alright with you, I would add 2 new endpoints to the test configuration:
- one that returns the client's ip with the new directive enabled
- another one hat returns the client's ip with the new directive disabled
What do you think of that?
Hi @thekief,
I tried to check your patch, but may be I'm doing something wrong, I can't see the client's IP in my log.
Here is my config:
server {
listen 8088;
server_name _;
root /var/www/html;
index index.html;
modsecurity on;
modsecurity_rules_file /etc/nginx/modsecurity_includes.conf;
modsecurity_proxy_protocol_ip on;
error_log /var/log/nginx/backend_error.log info;
access_log /var/log/nginx/backend_acces.log;
location / {
try_files $uri $uri/ =404;
}
}
server {
listen 8080;
server_name proxytest;
modsecurity off;
location / {
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://127.0.0.1:8088;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_buffering off;
proxy_redirect http:// https://;
}
}
As you can see I created a simple vhost for static files, there I turned on modsecurity, load rules and turned on your new feature. I also set a separated logs (both access and error).
Below that I created a proxy which sends all requests to that site.
Here is how I checked that:
curl --interface 172.35.40.12 -H "Host: proxytest" "http://localhost:8080/?q=/bin/bash"
where 172.35.40.12 is the IP of my physical interface, but the request sent to localhost (with that IP) with HTTP header Host: proxytest (see above the proxy config).
With this request I get this line in my error.log:
2025/02/19 21:04:29 [info] 255143#255143: *3 ModSecurity: Warning. Matched "Operator `ValidateByteRange' with parameter `38,44-46,48-58,61,65-90,95,97-122' against variable `ARGS:q' (Value: `/bin/bash' ) [file "/home/airween/src/coreruleset/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf"] [line "1723"] [id "920273"] [rev ""] [msg "Invalid character in request (outside of very strict set)"] [data "ARGS:q=/bin/bash"] [severity "2"] [ver "OWASP_CRS/4.12.0-dev"] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-protocol"] [tag "paranoia-level/4"] [tag "OWASP_CRS"] [tag "capec/1000/210/272"] [hostname "127.0.0.1"] [uri "/"] [unique_id "173999546951.849862"] [ref "o0,1o4,1v8,9t:urlDecodeUni"], client: 127.0.0.1, server: _, request: "GET /?q=/bin/bash HTTP/1.1", host: "proxytest"
Where should I see my client IP instead of 127.0.0.1 (which is the proxy's IP)?
Or may be I misunderstand something.
To clear up a small misunderstanding, the "Proxy Protocol" I am talking about, is specified in the listen-directive ^1.
I will add an example configuration tomorrow.
To clear up a small misunderstanding, the "Proxy Protocol" I am talking about, is specified in the
listen-directive 1.I will add an example configuration tomorrow.
Thanks - then I can help you to create test cases. And of course, examples are always helpful.
Just a quick update that I couldn't get to it yet, as some other things were more pressing. I should have something working by Monday 😅
@thekief any update on this?
Hi, I'm really sorry for my late reply, I had to deal with other things -.-
Thanks - then I can help you to create test cases. And of course, examples are always helpful. As for the test, I would propose the following:
curl has the following two options in regard to the Proxy Protocol:
--haproxy-clientip <ip> Set address in HAProxy PROXY
--haproxy-protocol Send HAProxy PROXY protocol v1 header
So, for example we could use curl --haproxy-protocol --haproxy-clientip 123.123.123.123 localhost:8443 as the test command.
If modsecurity_proxy_protocol_ip on; is set and a rule is triggered, 123.123.123.123 should be visible in the error log. If modsecurity_proxy_protocol_ip off; is set 127.0.0.1 or localhost should be visible in the log files.
Regarding configuration, I would have done the following:
nginx.conf:
pid /tmp/nginx.pid;
error_log /dev/stdout info;
events {
worker_connections 2000;
}
user www-data;
http {
server {
listen 8080 proxy_protocol;
root /var/www/html;
index index.html;
modsecurity on;
modsecurity_rules_file /etc/nginx/modsecurity_includes.conf;
modsecurity_proxy_protocol_ip off;
location / {
return 200;
}
}
server {
listen 8081 proxy_protocol;
root /var/www/html;
index index.html;
modsecurity on;
modsecurity_rules_file /etc/nginx/modsecurity_includes.conf;
modsecurity_proxy_protocol_ip on;
location / {
return 200;
}
}
}
REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf:
SecRuleRemoveByTag OWASP_CRS
SecRule REQUEST_HEADERS "@contains worm" \
"id:1012 \
,phase:1 \
,deny \
,t:none \
,status:500 \
,log \
"
Resulting in the following output when executing curl --haproxy-clientip 123.123.123.123 --haproxy-protocol -v -k -H "Animal: worm" localhost:8080:
{"transaction":{"client_ip":"172.21.0.1","time_stamp":"Wed Jun 11 13:32:17 2025","server_id":"575f816b3c1769ef25a49fc7dcabe0a096d605ee","client_port":37068,"host_ip":"172.21.0.2","host_port":8080,"unique_id":"17496487374.770509","request":{"method":"GET","http_version":1.1,"uri":"/","headers":{"Host":"localhost:8080","User-Agent":"curl/8.14.1","Accept":"*/*","Animal":"worm"}}...
If curl --haproxy-clientip 123.123.123.123 --haproxy-protocol -v -k -H "Animal: worm" localhost:8081 is executed, the following output is in the logs:
{"transaction":{"client_ip":"123.123.123.123","time_stamp":"Wed Jun 11 13:32:12 2025","server_id":"575f816b3c1769ef25a49fc7dcabe0a096d605ee","client_port":42630,"host_ip":"172.21.0.2","host_port":8081,"unique_id":"174964873292.593363","request":{"method":"GET","http_version":1.1,"uri":"/","headers":{"Host":"localhost:8081","User-Agent":"curl/8.14.1","Accept":"*/*","Animal":"worm"}}...