louislam / uptime-kuma

A fancy self-hosted monitoring tool
https://uptime.kuma.pet
MIT License
53.23k stars 4.81k forks source link

`/socket.io` is `POST`ed with `content-type: text/plain` and trips `ModSecurity OWASP` rules #4256

Closed revellion closed 5 months ago

revellion commented 7 months ago

⚠️ Please verify that this bug has NOT been raised before.

πŸ›‘οΈ Security Policy

Description

Whenever the dashboards establishes a websocket connection it does it with content-type: text/plain which trips ModSec since it should avoid using that content-type since it will prevent processing of the data to inspect it properly.

I haven't checked yet what content is returned in the POST to propose a proper type but the authors might have a better idea.

πŸ‘Ÿ Reproduction steps

Install Uptime Kuma Put it behind NGINX Reverse Proxy with ModSecurity and OWASP ruleset enabled.

πŸ‘€ Expected behavior

Uptime Kuma works fine without any tweaks

πŸ˜“ Actual Behavior

It errors on establishing websockets and returns a link to enable WebSockets support in the reverse proxy that is already enabled.

🐻 Uptime-Kuma Version

1.23.10

πŸ’» Operating System and Arch

Debian 12 x86_64

🌐 Browser

Google Chrome and tested on Firefox aswell to rule out cookies/cache issues

πŸ‹ Docker Version

No response

🟩 NodeJS Version

No response

πŸ“ Relevant log output

{
  "transaction": {
    "client_ip": "fdd1:ae48:a4e4:33::143",
    "time_stamp": "Tue Dec 19 18:57:02 2023",
    "server_id": "aba4d80e2883a7f6185024eea41603c0fa8bfb0d",
    "client_port": 35742,
    "host_ip": "fdd1:ae48:a4e4:33::179",
    "host_port": 443,
    "unique_id": "",
    "request": {
      "method": "POST",
      "http_version": 2.0,
      "uri": "/socket.io/?EIO=4&transport=polling&t=Oo3ELRm&sid=JPvAoyRDk_SFe1ygAP_J",
      "headers": {
        "referer": "https://status.dynamict.se/dashboard",
        "origin": "https://status.dynamict.se",
        "content-type": "text/plain;charset=UTF-8",
        "accept-encoding": "gzip, deflate, br",
        "content-length": "2",
        "accept-language": "sv-SE,sv;q=0.8,en-US;q=0.5,en;q=0.3",
        "te": "trailers",
        "accept": "*/*",
        "user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:120.0) Gecko/20100101 Firefox/120.0",
        "sec-fetch-site": "same-origin",
        "sec-fetch-mode": "cors",
        "sec-fetch-dest": "empty",
        "host": "status.dynamict.se"
      }
    },
    "response": {
      "body": "<html>\r\n<head><title>403 Forbidden</title></head>\r\n<body>\r\n<center><h1>403 Forbidden</h1></center>\r\n<hr><center>nginx/1.24.0</center>\r\n</body>\r\n</html>\r\n",
      "http_code": 403,
      "headers": {
        "Server": "nginx/1.24.0",
        "Date": "Tue, 19 Dec 2023 17:57:02 GMT",
        "Content-Length": "153",
        "Content-Type": "text/html",
        "Connection": "close"
      }
    },
    "producer": {
      "modsecurity": "ModSecurity v3.0.11 (Linux)",
      "connector": "ModSecurity-nginx v1.0.3",
      "secrules_engine": "Enabled",
      "components": [
        "OWASP_CRS/3.3.5\""
      ]
    },
    "messages": [
      {
        "message": "Request content type is not allowed by policy",
        "details": {
          "match": "Matched \"Operator `Within' with parameter `|application/x-www-form-urlencoded| |multipart/form-data| |multipart/related| |text/xml| |application/xml| |application/soap+xml| |application/json| |application/cloudevents+json| |application/cloudev (16 characters omitted)' against variable `TX:content_type' (Value: `|text/plain|' )",
          "reference": "o0,10v307,24t:lowercase",
          "ruleId": "920420",
          "file": "/usr/share/modsecurity/crs/rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf",
          "lineNumber": "938",
          "data": "|text/plain|",
          "severity": "2",
          "ver": "OWASP_CRS/3.3.5",
          "rev": "",
          "tags": [
            "application-multi",
            "language-multi",
            "platform-multi",
            "attack-protocol",
            "paranoia-level/1",
            "OWASP_CRS",
            "capec/1000/255/153",
            "PCI/12.1"
          ],
          "maturity": "0",
          "accuracy": "0"
        }
      },
      {
        "message": "Inbound Anomaly Score Exceeded (Total Score: 5)",
        "details": {
          "match": "Matched \"Operator `Ge' with parameter `5' against variable `TX:ANOMALY_SCORE' (Value: `5' )",
          "reference": "",
          "ruleId": "949110",
          "file": "/usr/share/modsecurity/crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf",
          "lineNumber": "81",
          "data": "",
          "severity": "2",
          "ver": "OWASP_CRS/3.3.5",
          "rev": "",
          "tags": [
            "application-multi",
            "language-multi",
            "platform-multi",
            "attack-generic"
          ],
          "maturity": "0",
          "accuracy": "0"
        }
      }
    ]
  }
}
revellion commented 7 months ago

Interesting though in an earlier version before i updated this worked fine, so i wonder if something in the code has changed to cause this regression?

louislam commented 7 months ago

Technically:

We are not guaranteed that Uptime Kuma could be working on a special setup, so I changed it to help.

Also the request is made by Socket.io, which unlikely can be fixed in our code base:

"uri": "/socket.io/?EIO=4&transport=polling&t=Oo3ELRm&sid=JPvAoyRDk_SFe1ygAP_J".

If you think it is a bug, you should transfer the issue to their repo with a minimal socket.io reproduce steps.

https://github.com/socketio/socket.io/issues

CommanderStorm commented 7 months ago

Note that this is our supported nginx configuration: https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy#nginx

If there are good reasons for including it in our configuration we can discuss this but currently I don't see them.

Note that the ruleset you are using is kind of infamous for the amount of false positives as far as I have read

revellion commented 7 months ago

Note that this is our supported nginx configuration: https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy#nginx

If there are good reasons for including it in our configuration we can discuss this but currently I don't see them.

Note that the ruleset you are using is kind of infamous for the amount of false positives as far as I have read

I've currently workedarounded it by adding an exception to just that request path for now. Gonna see if i can find any clues in the socket.io upstream project.

CommanderStorm commented 5 months ago

If you think it is a bug, you should transfer the issue to their repo with a minimal socket.io reproduce steps.

https://github.com/socketio/socket.io/issues

I am going to close this issue as I don't see how we can fix it on our side. As mentioned above: please report it upstream with a minimal reproducible ^^