An Nginx Port-Forwarding Pitfall
I recently made a serious mistake while configuring Nginx port forwarding. Here is what happened.
Our game server used WSS, and Nginx needed to forward many paths to backend WebSocket ports. For example, wss://xxx.com/10000 should reach ws://xxx.com:10000. The requested range was around ports 10000–11000. I did not want to write a thousand separate rules, so I found this configuration online:
location ~* ^/(?<port>\d+)$ { proxy_pass http://127.0.0.1:$port; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; proxy_set_header Host $host;}At first glance, it seemed to work: take the number after / and use it as the backend port.
Then a colleague noticed the problem. Although our security group exposed only the intended game-server ports, this rule allowed a user to reach any local port indirectly through Nginx’s public port 80 or 443. A small deployment might appear to work normally, but scanning could expose sensitive internal services as traffic grew.
Restrict the destination port
My first thought was to use if. I had also encountered the Nginx community’s “If Is Evil” discussion about unexpected behavior in some configurations, so I decided to restrict the numeric match instead.
These are the two patterns recorded in the original post:
location ~* ^/(?<port>(20[0-9]\{3\}))$ { proxy_pass http://127.0.0.1:$port; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; proxy_set_header Host $host;}Or:
location ~* ^/(?<port>2[0-1]\d\d\d)$ { proxy_pass http://127.0.0.1:$port; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; proxy_set_header Host $host;}The intent was to match only an allowed range and reject other ports, rather than pass arbitrary user-supplied numbers to the upstream.
Translation note: the original post describes a 10000–10999 match, but the two restricted patterns above do not implement that range as written. One also escapes the repetition braces. They are preserved here as historical examples; validate the actual pattern and allowed port range before using the configuration.
Comments
Questions, ideas and experiences are welcome. Please be kind and avoid sharing private information.
Loading comments…