ModSecurity-nginx icon indicating copy to clipboard operation
ModSecurity-nginx copied to clipboard

Wrong http_code in modsec logs when using auth_request

Open jaysee opened this issue 3 months ago • 12 comments

Problem

When auth_request directive is active, ModSecurity-nginx captures the HTTP status code from the auth_request subrequest (usually 200 for "pass") instead of the final status code from the actual backend (proxy_pass).

Setup

location /auth {
	internal;

	modsecurity off;

	proxy_pass http://127.0.0.1:2607/auth?server=$server_name; # => returns 200
	proxy_cache off;
	proxy_pass_request_body	off;

	proxy_set_header X-Forwarded-For $remote_addr;
	proxy_set_header Content-Length "";
	proxy_set_header X-Original-URI $request_uri;

	proxy_intercept_errors	off;
}
auth_request   /auth;
proxy_pass     $upstream; # => returns 404

Scenario

  1. Request comes in: GET /?test=/bin/bash
  2. auth_request /auth → returns 200 (pass)
  3. ModSecurity captures: http_code = 200
  4. proxy_pass $upstream → returns 404
  5. Audit log has: http_code: 200 ❌ (should be 404)

Expected Behavior

ModSecurity should capture the FINAL status code sent to the client, not intermediate subrequest statuses.

EDIT

The whole response content in the log seems to be the reponse datas of the /auth subrequest, not the proxy request.

nginx version: nginx/1.29.3
ModSecurity-nginx: v1.0.4-2-gfd28e6a

jaysee avatar Nov 11 '25 08:11 jaysee

Hello,

if somebody needs help to reproduce the case (or fix my use case), please ping me :)

I really need the corret http_code in the logs for better rule fixing.

jaysee avatar Nov 25 '25 15:11 jaysee

Hi @jaysee,

I apologize that missed out this issue - I try to take a look at this soon.

Anyway, an example to reproduce the issue with the config you given would be helpful.

airween avatar Nov 25 '25 16:11 airween

Here is a minimal setup you can try:

server {
       server_name	nginx;
       listen	nginx:80;

       # basically CRS setup
       include  snippets/security.conf;

       location /auth {
              internal;

              modsecurity off;
              return 200;
       }

       location / {
              auth_request 			/auth;
              proxy_pass 		       http://test:80;
       }

       access_log /var/log/nginx/test.log vhost;
}

Calling curl http://nginx/404 -I

HTTP/1.1 404 Not Found
Date: Thu, 27 Nov 2025 15:45:35 GMT
Content-Type: text/html; charset=iso-8859-1
Connection: keep-alive
Vary: Accept-Encoding
Content-Security-Policy: frame-ancestors 'self'
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff

but modsec log says "http_code": 200

jaysee avatar Nov 27 '25 15:11 jaysee

@jaysee

thanks for detailed info - sorry to ask, but in which log you get 200?

It seems that your request is a regular one without any violation, which would a good reason to trigger a rule. If no rule triggers during the transaction, then you won't get anything in any log.

Now I tried your setup, I also get a 404, but all of my logs (error.log, audit.log) are empty.

airween avatar Nov 27 '25 21:11 airween

Sorry, incomplet configuration in my test config

log all, increase anomaly score

 modsecurity_rules "
SecAuditEngine On
SecAction \"id:900112,phase:1,pass,nolog,setvar:tx.inbound_anomaly_score_threshold=80,setvar:tx.outbound_anomaly_score_threshold=5\"
";

curl -I "http://nginx/404?t=phpinfo"

HTTP/1.1 404 Not Found
Date: Mon, 01 Dec 2025 06:28:57 GMT
Content-Type: text/html; charset=iso-8859-1
Connection: keep-alive
Vary: Accept-Encoding
Content-Security-Policy: frame-ancestors 'self'
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
{
  "transaction": {
    "client_ip": "10.0.9.96",
    "time_stamp": "Mon Dec  1 07:23:31 2025",
    "server_id": "xxx",
    "client_port": 37674,
    "host_ip": "10.0.9.96",
    "host_port": 80,
    "unique_id": "yyy",
    "request": {
      "method": "HEAD",
      "http_version": 1.1,
      "uri": "/404?t=phpinfo",
      "headers": {
        "Host": "nginx",
        "User-Agent": "curl/8.14.1",
        "Accept": "*/*"
      }
    },
    "response": {
      "http_code": 200,
      "headers": {
        "Server": "",
        "Date": "Mon, 01 Dec 2025 06:23:31 GMT",
        "Content-Type": "application/octet-stream",
        "X-Content-Type-Options": "nosniff",
        "Connection": "close",
        "client_body_buffer_size": "",
        "64M": "",
        "X-Frame-Options": "SAMEORIGIN",
        "Content-Security-Policy": "frame-ancestors 'self'",
        "X-Powered-By": ""
      }
    },
    "producer": {
      "modsecurity": "ModSecurity v3.0.14 (Linux)",
      "connector": "ModSecurity-nginx v1.0.4",
      "secrules_engine": "Enabled",
      "components": [
        "OWASP_CRS/4.21.0-dev\""
      ]
    },
    "messages": [
      {
        "message": "PHP Injection Attack: High-Risk PHP Function Name Found",
        "details": {
          "match": "Matched \"Operator `PmFromFile' with parameter `php-function-names-933150.data' against variable `ARGS:t' (Value: `phpinfo' )",
          "reference": "o0,7v12,7",
          "ruleId": "933150",
          "file": "/etc/nginx/modsec/crs/rules/REQUEST-933-APPLICATION-ATTACK-PHP.conf",
          "lineNumber": "320",
          "data": "Matched Data: phpinfo found within ARGS:t: phpinfo",
          "severity": "2",
          "ver": "OWASP_CRS/4.21.0-dev",
          "rev": "",
          "tags": [
            "application-multi",
            "language-php",
            "platform-multi",
            "attack-injection-php",
            "paranoia-level/1",
            "OWASP_CRS",
            "OWASP_CRS/ATTACK-PHP",
            "capec/1000/152/242"
          ],
          "maturity": "0",
          "accuracy": "0"
        }
      }
    ]
  }
}

jaysee avatar Dec 01 '25 06:12 jaysee

Sorry, unfortunately I don't see exactly what do you want to achive and what configuration you use.

Could you paste here your full vhost config, and modsecurity.conf relevant lines (skip the comments)?

The goal is to be able to reproduce the explained behavior.

airween avatar Dec 01 '25 20:12 airween

I tried to rebuild the configuration based on given information above, and first time I got a different result - the http_code values was 404, but when I compared the other values, realized that I used an old module version (1.0.3). I upgraded my module, and I got the same result as you described.

Probably was some change between 1.0.3 and 1.0.4 which modified the module's behavior.

If I had to bet, I would choose this PR.

Could you confirm that with 1.0.3 you get the expected code in the audit.log?

airween avatar Dec 01 '25 20:12 airween

OK, had to fix compilation of 1.0.3 against my system, but, yep, got 404 in log now!!!

{
  "transaction": {
    "client_ip": "10.0.9.96",
    "time_stamp": "Tue Dec  2 08:52:04 2025",
    "server_id": "xxx",
    "client_port": 39360,
    "host_ip": "10.0.9.96",
    "host_port": 80,
    "unique_id": "yyy",
    "request": {
      "method": "HEAD",
      "http_version": 1.1,
      "uri": "/404?t=phpinfo",
      "headers": {
        "Host": "nginx",
        "User-Agent": "curl/8.14.1",
        "Accept": "*/*"
      }
    },
    "response": {
      "http_code": 404,
      "headers": {
        "Server": "",
        "Date": "Tue, 02 Dec 2025 07:52:04 GMT",
        "Content-Type": "text/html; charset=iso-8859-1",
        "X-Content-Type-Options": "nosniff",
        "Connection": "keep-alive",
        "client_body_buffer_size": "",
        "64M": "",
        "X-Frame-Options": "SAMEORIGIN",
        "Content-Security-Policy": "frame-ancestors 'self'",
        "X-Powered-By": ""
      }
    },
    "producer": {
      "modsecurity": "ModSecurity v3.0.14 (Linux)",
      "connector": "ModSecurity-nginx v1.0.3",
      "secrules_engine": "Enabled",
      "components": [
        "OWASP_CRS/4.21.0-dev\""
      ]
    },
    "messages": [
      {
        "message": "PHP Injection Attack: High-Risk PHP Function Name Found",
        "details": {
          "match": "Matched \"Operator `PmFromFile' with parameter `php-function-names-933150.data' against variable `ARGS:t' (Value: `phpinfo' )",
          "reference": "o0,7v12,7",
          "ruleId": "933150",
          "file": "/etc/nginx/modsec/crs/rules/REQUEST-933-APPLICATION-ATTACK-PHP.conf",
          "lineNumber": "320",
          "data": "Matched Data: phpinfo found within ARGS:t: phpinfo",
          "severity": "2",
          "ver": "OWASP_CRS/4.21.0-dev",
          "rev": "",
          "tags": [
            "application-multi",
            "language-php",
            "platform-multi",
            "attack-injection-php",
            "paranoia-level/1",
            "OWASP_CRS",
            "OWASP_CRS/ATTACK-PHP",
            "capec/1000/152/242"
          ],
          "maturity": "0",
          "accuracy": "0"
        }
      }
    ]
  }
}

jaysee avatar Dec 02 '25 07:12 jaysee

I think this is an interesting situation, because - as I understand - you use an internal redirect.

And the mentioned PR above tried to fix that explained unexpected behavior.

I removed the location /auth block from the vhost context that you showed, so without any internal redirection everything works as you expected.

With that block you always returning with 200, so I risk that the behavior in 1.0.4 is the correct, not the 1.0.3's.

airween avatar Dec 02 '25 10:12 airween

yes: without the auth_request /auth; 1.0.4 logs the code returned by the proxy_pass directive.

actually, the auth_request makes an internal validation and should return 200 if OK, 403/401 if not. If auth_request says 200, then proxy_pass handles the request. and this is this request status I want to see in logs.

In my real use case /auth is not always returning 200 of course. but that's not the point here.

Here we have 2 subrequests auth_request and proxy_pass which one should be handled... I think this should be the code that is finaly returned to the client? (same about the whole response entry in the log)

jaysee avatar Dec 02 '25 14:12 jaysee

Here we have 2 subrequests auth_request and proxy_pass which one should be handled... I think this should be the code that is finaly returned to the client? (same about the whole response entry in the log)

I'm sorry but I don't really get it what you mean here. Could you explain your aim with a few examples?

airween avatar Dec 02 '25 18:12 airween

example as explained in previous comment :

curl -I "http://nginx/404?t=phpinfo" => clients get status=404

auditlog says: "http_code": 200,

I need the same code in auditlog that the client gets as I'm writing a ML engine that eats the modsec logs to target bad bahavior/false positives. "Final" http_code is a good indicator.

If you think the actual behavior is the rigth one (because you know the internal design) and this is not a bug, I will try another nginx configuration that could achieve my goal, no problem.

jaysee avatar Dec 04 '25 13:12 jaysee