corazawaf / coraza-caddy

OWASP Coraza middleware for Caddy. It provides Web Application Firewall capabilities
https://www.coraza.io/
Apache License 2.0
290 stars 35 forks source link

Websocket connections not working as soon as Coraza is active #78

Open TheForcer opened 1 year ago

TheForcer commented 1 year ago

Hello everyone,

as already announced in your Slack channel, I am currently facing issues with Websocket connections in conjunction with coraza-caddy. These connections work fine if I set SecRuleEngine Off or remove the corresponding snippet from my subdomain config in caddy, but as soon as Coraza is in the play, these websocket connections do not seem to be handled correctly, while the rest of HTTP requests seems to be fine. The websocket connections also won't work with SecRuleEngine DetectionOnly set, which makes me assume that this may be some internal Coraza handling problem, and not the OWASP rules, Coraza config etc.

Setup

Linux LXC Debian 11 Caddy 2.6.4 + coraza 3.0.0 + coraza-caddy 2.0.0-rc2 with xcaddy

build-info

go      go1.20.5
path    caddy
mod     caddy   (devel)
dep     filippo.io/edwards25519 v1.0.0  h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
dep     github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96      h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
dep     github.com/BurntSushi/toml      v1.2.1  h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
dep     github.com/Masterminds/goutils  v1.1.1  h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
dep     github.com/Masterminds/semver/v3        v3.2.0  h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
dep     github.com/Masterminds/sprig/v3 v3.2.3  h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
dep     github.com/alecthomas/chroma/v2 v2.5.0  h1:CQCdj1BiBV17sD4Bd32b/Bzuiq/EqoNTrnIhyQAZ+Rk=
dep     github.com/antlr/antlr4/runtime/Go/antlr        v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves=
dep     github.com/aryann/difflib       v0.0.0-20210328193216-ff5ff6dc229b      h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw=
dep     github.com/beorn7/perks v1.0.1  h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
dep     github.com/caddyserver/caddy/v2 v2.6.4  h1:2hwYqiRwk1tf3VruhMpLcYTg+11fCdr8S3jhNAdnPy8=
dep     github.com/caddyserver/certmagic        v0.17.2 h1:o30seC1T/dBqBCNNGNHWwj2i5/I/FMjBbTAhjADP3nE=
dep     github.com/cenkalti/backoff/v4  v4.1.2  h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo=
dep     github.com/cespare/xxhash       v1.1.0  h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
dep     github.com/cespare/xxhash/v2    v2.1.2  h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
dep     github.com/chzyer/readline      v0.0.0-20180603132655-2972be24d48e      h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
dep     github.com/corazawaf/coraza-caddy/v2    v2.0.0-rc.2     h1:HY101opLin5TVKvNlyRv25LQc4HVvdJ9cIiFMgc0UO8=
dep     github.com/corazawaf/coraza-coreruleset v0.0.0-20230405190458-b4d2a6f6bdfh1:EU5PEFuQ4PTzkz2YFoCQwI73gu8GW95txz3dMNGsseA=
dep     github.com/corazawaf/coraza/v3  v3.0.0  h1:GvTzxcgtfQ76LneYL19Nkb1/T+2E/s3BRAOEt6h2sY0=
dep     github.com/corazawaf/libinjection-go    v0.1.2  h1:oeiV9pc5rvJ+2oqOqXEAMJousPpGiup6f7Y3nZj5GoM=
dep     github.com/cpuguy83/go-md2man/v2        v2.0.2  h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
dep     github.com/dgraph-io/badger     v1.6.2  h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8=
dep     github.com/dgraph-io/badger/v2  v2.2007.4       h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o=
dep     github.com/dgraph-io/ristretto  v0.1.0  h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI=
dep     github.com/dgryski/go-farm      v0.0.0-20200201041132-a6ae2369ad13      h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
dep     github.com/dlclark/regexp2      v1.7.0  h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
dep     github.com/dustin/go-humanize   v1.0.1  h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
dep     github.com/felixge/httpsnoop    v1.0.3  h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
dep     github.com/fxamacker/cbor/v2    v2.4.0  h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
dep     github.com/go-chi/chi   v4.1.2+incompatible     h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
dep     github.com/go-kit/kit   v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo=
dep     github.com/go-logfmt/logfmt     v0.5.1  h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
dep     github.com/go-logr/logr v1.2.3  h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
dep     github.com/go-logr/stdr v1.2.2  h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
dep     github.com/go-sql-driver/mysql  v1.6.0  h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
dep     github.com/golang/glog  v1.0.0  h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
dep     github.com/golang/protobuf      v1.5.2  h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
dep     github.com/golang/snappy        v0.0.4  h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
dep     github.com/google/cel-go        v0.13.0 h1:z+8OBOcmh7IeKyqwT/6IlnMvy621fYUqnTVPEdegGlU=
dep     github.com/google/uuid  v1.3.0  h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
dep     github.com/grpc-ecosystem/grpc-gateway  v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
dep     github.com/huandu/xstrings      v1.3.3  h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
dep     github.com/imdario/mergo        v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
dep     github.com/jackc/chunkreader/v2 v2.0.1  h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
dep     github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys=
dep     github.com/jackc/pgio   v1.0.0  h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
dep     github.com/jackc/pgpassfile     v1.0.0  h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
dep     github.com/jackc/pgproto3/v2    v2.3.1  h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y=
dep     github.com/jackc/pgservicefile  v0.0.0-20200714003250-2b9c44734f2b      h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
dep     github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w=
dep     github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E=
dep     github.com/jcchavezs/mergefs    v0.0.0-20230405222254-20429875efdd      h1:wj0PapN9ZM27EnZqqtvVHUpRUWDHEK3/H7gkBFj1qyw=
dep     github.com/klauspost/compress   v1.15.15        h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
dep     github.com/klauspost/cpuid/v2   v2.2.3  h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU=
dep     github.com/libdns/libdns        v0.2.1  h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
dep     github.com/manifoldco/promptui  v0.9.0  h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
dep     github.com/mattn/go-colorable   v0.1.8  h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
dep     github.com/mattn/go-isatty      v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
dep     github.com/matttproud/golang_protobuf_extensions        v1.0.1  h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
dep     github.com/mgutz/ansi   v0.0.0-20200706080929-d51e80ef957d      h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
dep     github.com/mholt/acmez  v1.1.0  h1:IQ9CGHKOHokorxnffsqDvmmE30mDenO1lptYZ1AYkHY=
dep     github.com/micromdm/scep/v2     v2.1.0  h1:2fS9Rla7qRR266hvUoEauBJ7J6FhgssEiq2OkSKXmaU=
dep     github.com/miekg/dns    v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
dep     github.com/mitchellh/copystructure      v1.2.0  h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
dep     github.com/mitchellh/go-ps      v1.0.0  h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
dep     github.com/mitchellh/reflectwalk        v1.0.2  h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
dep     github.com/petar-dambovaliev/aho-corasick       v0.0.0-20211021192214-5ab2d9280aa9        h1:lL+y4Xv20pVlCGyLzNHRC0I0rIHhIL1lTvHizoS/dU8=
dep     github.com/pkg/errors   v0.9.1  h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
dep     github.com/prometheus/client_golang     v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
dep     github.com/prometheus/client_model      v0.3.0  h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
dep     github.com/prometheus/common    v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
dep     github.com/prometheus/procfs    v0.8.0  h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
dep     github.com/quic-go/qpack        v0.4.0  h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
dep     github.com/quic-go/qtls-go1-20  v0.1.0  h1:d1PK3ErFy9t7zxKsG3NXBJXZjp/kMLoIb3y/kV54oAI=
dep     github.com/quic-go/quic-go      v0.32.0 h1:lY02md31s1JgPiiyfqJijpu/UX/Iun304FI3yUqX7tA=
dep     github.com/rs/xid       v1.4.0  h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
dep     github.com/russross/blackfriday/v2      v2.1.0  h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
dep     github.com/shopspring/decimal   v1.2.0  h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
dep     github.com/shurcooL/sanitized_anchor_name       v1.0.0  h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
dep     github.com/sirupsen/logrus      v1.9.0  h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
dep     github.com/slackhq/nebula       v1.6.1  h1:/OCTR3abj0Sbf2nGoLUrdDXImrCv0ZVFpVPP5qa0DsM=
dep     github.com/smallstep/certificates       v0.23.2 h1:7KSx9WfZ3CILV0XlsTrl+PK58YE4CHSgqobB6+ieQWs=
dep     github.com/smallstep/nosql      v0.5.0  h1:1BPyHy8bha8qSaxgULGEdqhXpNFXimAfudnauFVqmxw=
dep     github.com/smallstep/truststore v0.12.1 h1:guLUKkc1UlsXeS3t6BuVMa4leOOpdiv02PCRTiy1WdY=
dep     github.com/spf13/cast   v1.4.1  h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
dep     github.com/spf13/cobra  v1.6.1  h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
dep     github.com/spf13/pflag  v1.0.5  h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
dep     github.com/stoewer/go-strcase   v1.2.0  h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
dep     github.com/tailscale/tscert     v0.0.0-20230124224810-c6dc1f4049b2      h1:TrgfmCXwtWyFw85UkRGXt9qZRzdzt3nWt2Rerdecn0w=
dep     github.com/tidwall/gjson        v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
dep     github.com/tidwall/match        v1.1.1  h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
dep     github.com/tidwall/pretty       v1.2.1  h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
dep     github.com/urfave/cli   v1.22.12        h1:igJgVw1JdKH+trcLWLeLwZjU9fEfPesQ+9/e4MQ44S8=
dep     github.com/x448/float16 v0.8.4  h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
dep     github.com/yuin/goldmark        v1.5.4  h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU=
dep     github.com/yuin/goldmark-highlighting/v2        v2.0.0-20220924101305-151362477c87        h1:Py16JEzkSdKAtEFJjiaYLYBOWGXc1r/xHj/Q/5lA37k=
dep     go.etcd.io/bbolt        v1.3.6  h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
dep     go.mozilla.org/pkcs7    v0.0.0-20210826202110-33d05740a352      h1:CCriYyAfq1Br1aIYettdHZTy8mBTIPo7We18TuO/bak=
dep     go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp   v0.39.0 h1:vFEBG7SieZJzvnRWQ81jxpuEqe6J8Ex+hgc9CqOTzHc=
dep     go.opentelemetry.io/otel        v1.13.0 h1:1ZAKnNQKwBBxFtww/GwxNUyTf0AxkZzrukO8MeXqe4Y=
dep     go.opentelemetry.io/otel/exporters/otlp/internal/retry  v1.4.0  h1:j7AwzDdAQBJjcqayAaYbvpYeZzII7cEe5qJTu+De6UY=
dep     go.opentelemetry.io/otel/exporters/otlp/otlptrace       v1.4.0  h1:lRpP10E8oTGVmY1nVXcwelCT1Z8ca41/l5ce7AqLAss=
dep     go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.4.0  h1:buSx4AMC/0Z232slPhicN/fU5KIlj0bMngct5pcZhkI=
dep     go.opentelemetry.io/otel/metric v0.36.0 h1:t0lgGI+L68QWt3QtOIlqM9gXoxqxWLhZ3R/e5oOAY0Q=
dep     go.opentelemetry.io/otel/sdk    v1.13.0 h1:BHib5g8MvdqS65yo2vV1s6Le42Hm6rrw08qU6yz5JaM=
dep     go.opentelemetry.io/otel/trace  v1.13.0 h1:CBgRZ6ntv+Amuj1jDsMhZtlAPT6gbyIRdaIzFhfBSdY=
dep     go.opentelemetry.io/proto/otlp  v0.12.0 h1:CMJ/3Wp7iOWES+CYLfnBv+DVmPbB+kmy9PJ92XvlR6c=
dep     go.step.sm/cli-utils    v0.7.5  h1:jyp6X8k8mN1B0uWJydTid0C++8tQhm2kaaAdXKQQzdk=
dep     go.step.sm/crypto       v0.23.2 h1:XGmQH9Pkpxop47cjYlUhF10L5roPCbu1BCZXopbeW8I=
dep     go.step.sm/linkedca     v0.19.0 h1:xuagkR35wrJI2gnu6FAM+q3VmjwsHScvGcJsfZ0GdsI=
dep     go.uber.org/atomic      v1.9.0  h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
dep     go.uber.org/multierr    v1.8.0  h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
dep     go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
dep     golang.org/x/crypto     v0.5.0  h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
dep     golang.org/x/exp        v0.0.0-20221205204356-47842c84f3db      h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
dep     golang.org/x/net        v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
dep     golang.org/x/sync       v0.1.0  h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
dep     golang.org/x/sys        v0.8.0  h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
dep     golang.org/x/term       v0.8.0  h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
dep     golang.org/x/text       v0.9.0  h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
dep     google.golang.org/genproto      v0.0.0-20230202175211-008b39050e57      h1:vArvWooPH749rNHpBGgVl+U9B9dATjiEhJzcWGlovNs=
dep     google.golang.org/grpc  v1.52.3 h1:pf7sOysg4LdgBqduXveGKrcEwbStiK2rtfghdzlUYDQ=
dep     google.golang.org/protobuf      v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
dep     gopkg.in/natefinch/lumberjack.v2        v2.2.1  h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
dep     gopkg.in/square/go-jose.v2      v2.6.0  h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
dep     gopkg.in/yaml.v3        v3.0.1  h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
dep     rsc.io/binaryregexp     v0.2.0  h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE=
build   -buildmode=exe
build   -compiler=gc
build   -trimpath=true
build   CGO_ENABLED=0
build   GOARCH=amd64
build   GOOS=linux
build   GOAMD64=v1

Reproduce

Please follow this example to reproduce the issue

  1. Run a instance of RustPad
docker run --rm -dp 3030:3030 ekzhang/rustpad
  1. Run your instance of caddy+Coraza with the following Caddyfile (adapt accordingly)
{
        debug
        auto_https off
        order coraza_waf first
}

(tls) {
        tls /etc/acme/{args.0}_ecc/fullchain.cer /etc/acme/{args.0}_ecc/{args.0}.key
}

(waf) {
        coraza_waf {
          directives `
            SecRuleEngine Off
          `
 }
}

pad.example.com {
        import tls example.com
        import waf
        reverse_proxy localhost:3030
}
  1. When visiting the app via Caddy, you should then see Rustpad Connecting to the server... and then You are connected (or check WS connection in your browsers network inspector) - everything works fine.

  2. In the Caddyfile, replace SecRuleEngine Off with SecRuleEngine DetectionOnly and reload Caddy.

  3. Now, Rustpad wont be able to establish a websocket connection :( DEBUG log output should provide something like:

2023/06/24 14:45:22.808 DEBUG   http.handlers.reverse_proxy     selected upstream{"dial": "10.0.0.110:3030", "total_upstreams": 1}
2023/06/24 14:45:22.809 DEBUG   http.handlers.reverse_proxy     upstream roundtri{"upstream": "10.0.0.110:3030", "duration": 0.000513863, "request": {"remote_ip": "192.168.178.11", "remote_port": "56254", "proto": "HTTP/1.1", "method": "GET", "host": "pad.example.com:11443", "uri": "/api/socket/MCxiai", "headers": {"Connection": ["Upgrade"], "Origin": ["https://pad.example.com:11443"], "Sec-Websocket-Version": ["13"], "Accept-Language": ["de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7"], "Sec-Websocket-Extensions": ["permessage-deflate; client_max_window_bits"], "Sec-Websocket-Key": ["FCOBxo+tJXz+iewTbsv4tQ=="], "X-Forwarded-Proto": ["https"], "Upgrade": ["websocket"], "User-Agent": ["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36"], "Accept-Encoding": ["gzip, deflate, br"], "X-Forwarded-Host": ["pad.example.com:11443"], "Cache-Control": ["no-cache"], "Pragma": ["no-cache"], "X-Forwarded-For": ["192.168.178.11"]}, "tls": {"resumed": true, "version": 772, "cipher_suite": 4865, "proto": "http/1.1", "server_name": "pad.example.com"}}, "headers": {"Connection": ["upgrade"], "Upgrade": ["websocket"], "Sec-Websocket-Accept": ["Aqzjvc/3P75wBozHmowR/c98QBI="], "Date": ["Sat, 24 Jun 2023 14:45:21 GMT"]}, "status": 101}
2023/06/24 14:45:22.809 DEBUG   http.handlers.reverse_proxy     upgrading connection      {"upstream": "10.0.0.110:3030", "duration": 0.000513863, "request": {"remote_ip": "192.168.178.11", "remote_port": "56254", "proto": "HTTP/1.1", "method": "GET", "host": "pad.example.com:11443", "uri": "/api/socket/MCxiai", "headers": {"Connection": ["Upgrade"], "Origin": ["https://pad.example.com:11443"], "Sec-Websocket-Version": ["13"], "Accept-Language": ["de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7"], "Sec-Websocket-Extensions": ["permessage-deflate; client_max_window_bits"], "Sec-Websocket-Key": ["FCOBxo+tJXz+iewTbsv4tQ=="], "X-Forwarded-Proto": ["https"], "Upgrade": ["websocket"], "User-Agent": ["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36"], "Accept-Encoding": ["gzip, deflate, br"], "X-Forwarded-Host": ["pad.example.com:11443"], "Cache-Control": ["no-cache"], "Pragma": ["no-cache"], "X-Forwarded-For": ["192.168.178.11"]}, "tls": {"resumed": true, "version": 772, "cipher_suite": 4865, "proto": "http/1.1", "server_name": "pad.example.com"}}}
2023/06/24 14:45:22.809 DEBUG   http.handlers.reverse_proxy     connection closed{"upstream": "10.0.0.110:3030", "duration": 0.000513863, "request": {"remote_ip": "192.168.178.11", "remote_port": "56254", "proto": "HTTP/1.1", "method": "GET", "host": "pad.example.com:11443", "uri": "/api/socket/MCxiai", "headers": {"Connection": ["Upgrade"], "Origin": ["https://pad.example.com:11443"], "Sec-Websocket-Version": ["13"], "Accept-Language": ["de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7"], "Sec-Websocket-Extensions": ["permessage-deflate; client_max_window_bits"], "Sec-Websocket-Key": ["FCOBxo+tJXz+iewTbsv4tQ=="], "X-Forwarded-Proto": ["https"], "Upgrade": ["websocket"], "User-Agent": ["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36"], "Accept-Encoding": ["gzip, deflate, br"], "X-Forwarded-Host": ["pad.example.com:11443"], "Cache-Control": ["no-cache"], "Pragma": ["no-cache"], "X-Forwarded-For": ["192.168.178.11"]}, "tls": {"resumed": true, "version": 772, "cipher_suite": 4865, "proto": "http/1.1", "server_name": "pad.example.com"}}, "duration": 0.000393468}
2023/06/24 14:45:22.809 DEBUG   http.stdlib     http: response.WriteHeader on hijacked connection from github.com/caddyserver/caddy/v2/modules/caddyhttp.(*responseRecorder).WriteHeader (responsewriter.go:191)

I hope this helps - would be a pity if I couldnt use Coraza with apps that need websockets :) Thanks in advance

jptosso commented 1 year ago

Hey! We have to take a look on this. Coraza is not designed to handle websockets.

In the meantime the proper configuration is to set handlers to specify which URLs are coraza protected and which URLs are websockets.

Probably something like:

handle_path /ws {
    reverse_proxy websocket_server 
}
handle_path / {
    coraza_waf ...
    reverse_proxy web_app
}

Right now, we cannot assume the connection is a web socket initialization and disable coraza, because that would lead to potential coraza bypasses.

jptosso commented 1 year ago

This is my working example:

    handle /ws {
        @websockets {
            header_regexp Connection Upgrade
            header        Upgrade websocket
        }        
        reverse_proxy @websockets tosso:8080
    }
    handle * {
        coraza_waf {
            load_owasp_crs
            directives `
                Include @coraza.conf-recommended
                SecRuleEngine On
                Include @crs-setup.conf.example
                Include @owasp_crs/*.conf
            `
        }
        reverse_proxy tosso:8080 
    }

You can test it with wscat:

➜  istio-profiles wscat -L --connect wss://tosso.io/ws
Connected (press CTRL+C to quit)
> asdfasdf
< Hello World!
> qewr
< Hello World!

Then you can test that the rest of the site is still blocking:

curl "https://tosso.io/?id=<script>alert(123)</script>"
TheForcer commented 12 months ago

Thank you @jptosso for providing a nice workaround - can confirm it's working. I made a Caddy snippet to make it easier to integrate with several site in the form of, see below. Maybe this is of help for someone.

Shall I keep the issue open to keep track of this or do you want to close it? I'm fine either way, but would be cool if caddy/coraza could handle this automagically in the future :)

(websocket) {
        handle {args.0} {
            @websockets {
                header_regexp Connection Upgrade
                header        Upgrade websocket
            }
            reverse_proxy @websockets {args.1}
        }
}

...

rustpad.example.com {
        import settings rustpad
        import websocket /api/socket/* 10.0.0.110:3030
        handle {
            import waf
            reverse_proxy 10.0.0.110:3030
        }
}
peasfarmer commented 9 months ago

I had the same problem, in my scenario there was no way to know in advance which websocket URLs there were, so I had to make coraza websocket compatible. I debugged and found that when the url is websocket, net\http\server.go will set conn.hijackedv to true, and then when coraza calls flushWriteHeader in processResponse, it will determine whether hijackedv is true inside conn.WriteHeader. If true An exception will be thrown. So I modified the func (i *rwInterceptor) WriteHeader(statusCode int) function and added an if condition. It seems that websocket can work normally, but I am not sure whether this modification will cause other problems. I hope to get an official answer.

func (i *rwInterceptor) WriteHeader(statusCode int) {
    if i.wroteHeader {
        log.Println("http: superfluous response.WriteHeader call")
        return
    }

    for k, vv := range i.w.Header() {
        for _, v := range vv {
            i.tx.AddResponseHeader(k, v)
        }
    }

    i.statusCode = statusCode

        // add this if
    if statusCode == 101 {
        i.flushWriteHeader()
        return
    }

    if it := i.tx.ProcessResponseHeaders(statusCode, i.proto); it != nil {
        i.statusCode = obtainStatusCodeFromInterruptionOrDefault(it, i.statusCode)
        i.flushWriteHeader()
        return
    }

    i.wroteHeader = true
}
jptosso commented 9 months ago

I had the same problem, in my scenario there was no way to know in advance which websocket URLs there were, so I had to make coraza websocket compatible. I debugged and found that when the url is websocket, net\http\server.go will set conn.hijackedv to true, and then when coraza calls flushWriteHeader in processResponse, it will determine whether hijackedv is true inside conn.WriteHeader. If true An exception will be thrown. So I modified the func (i *rwInterceptor) WriteHeader(statusCode int) function and added an if condition. It seems that websocket can work normally, but I am not sure whether this modification will cause other problems. I hope to get an official answer.

func (i *rwInterceptor) WriteHeader(statusCode int) {
  if i.wroteHeader {
      log.Println("http: superfluous response.WriteHeader call")
      return
  }

  for k, vv := range i.w.Header() {
      for _, v := range vv {
          i.tx.AddResponseHeader(k, v)
      }
  }

  i.statusCode = statusCode

        // add this if
  if statusCode == 101 {
      i.flushWriteHeader()
      return
  }

  if it := i.tx.ProcessResponseHeaders(statusCode, i.proto); it != nil {
      i.statusCode = obtainStatusCodeFromInterruptionOrDefault(it, i.statusCode)
      i.flushWriteHeader()
      return
  }

  i.wroteHeader = true
}

There are no plans to support websocket, please take a look at this comment: https://github.com/corazawaf/coraza-caddy/issues/78#issuecomment-1635797541

lxdlam commented 4 months ago

I've also encountered this problem but in another scenario: server send events, or event source.

It seems that Coraza blocks the response until it receives the complete response, at which point it obtains all the response headers.