Browse Source

Added WebSocket Origin check options

Toby Chui 1 year ago
parent
commit
2143837509

+ 8 - 2
mod/dynamicproxy/proxyRequestHandler.go

@@ -114,7 +114,10 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
 			u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
 		}
 		h.logRequest(r, true, 101, "subdomain-websocket", target.Domain)
-		wspHandler := websocketproxy.NewProxy(u, target.SkipCertValidations)
+		wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
+			SkipTLSValidation: target.SkipCertValidations,
+			SkipOriginCheck:   target.SkipWebSocketOriginCheck,
+		})
 		wspHandler.ServeHTTP(w, r)
 		return
 	}
@@ -178,7 +181,10 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
 			u, _ = url.Parse("wss://" + wsRedirectionEndpoint + r.URL.String())
 		}
 		h.logRequest(r, true, 101, "vdir-websocket", target.Domain)
-		wspHandler := websocketproxy.NewProxy(u, target.SkipCertValidations)
+		wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
+			SkipTLSValidation: target.SkipCertValidations,
+			SkipOriginCheck:   target.parent.SkipWebSocketOriginCheck,
+		})
 		wspHandler.ServeHTTP(w, r)
 		return
 	}

+ 5 - 3
mod/dynamicproxy/typedef.go

@@ -95,9 +95,10 @@ type ProxyEndpoint struct {
 	Domain               string //Domain or IP to proxy to
 
 	//TLS/SSL Related
-	RequireTLS          bool //Target domain require TLS
-	BypassGlobalTLS     bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
-	SkipCertValidations bool //Set to true to accept self signed certs
+	RequireTLS               bool //Target domain require TLS
+	BypassGlobalTLS          bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
+	SkipCertValidations      bool //Set to true to accept self signed certs
+	SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections
 
 	//Virtual Directories
 	VirtualDirectories []*VirtualDirectoryEndpoint
@@ -115,6 +116,7 @@ type ProxyEndpoint struct {
 	DefaultSiteValue  string //Fallback routing target, optional
 
 	Disabled bool //If the rule is disabled
+
 	//Internal Logic Elements
 	parent *Router
 	proxy  *dpcore.ReverseProxy `json:"-"`

+ 4 - 1
mod/sshprox/sshprox.go

@@ -85,7 +85,10 @@ func (m *Manager) HandleHttpByInstanceId(instanceId string, w http.ResponseWrite
 		r.Header.Set("Zr-Origin-Upgrade", "websocket")
 		requestURL = strings.TrimPrefix(requestURL, "/")
 		u, _ := url.Parse("ws://127.0.0.1:" + strconv.Itoa(targetInstance.AssignedPort) + "/" + requestURL)
-		wspHandler := websocketproxy.NewProxy(u, false)
+		wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
+			SkipTLSValidation: false,
+			SkipOriginCheck:   false,
+		})
 		wspHandler.ServeHTTP(w, r)
 		return
 	}

+ 21 - 7
mod/websocketproxy/websocketproxy.go

@@ -47,19 +47,26 @@ type WebsocketProxy struct {
 	//  If nil, DefaultDialer is used.
 	Dialer *websocket.Dialer
 
-	Verbal            bool
-	SkipTlsValidation bool
+	Verbal bool
+
+	Options Options
+}
+
+// Additional options for websocket proxy runtime
+type Options struct {
+	SkipTLSValidation bool //Skip backend TLS validation
+	SkipOriginCheck   bool //Skip origin check
 }
 
 // ProxyHandler returns a new http.Handler interface that reverse proxies the
 // request to the given target.
-func ProxyHandler(target *url.URL, skipTlsValidation bool) http.Handler {
-	return NewProxy(target, skipTlsValidation)
+func ProxyHandler(target *url.URL, options Options) http.Handler {
+	return NewProxy(target, options)
 }
 
 // NewProxy returns a new Websocket reverse proxy that rewrites the
 // URL's to the scheme, host and base path provider in target.
-func NewProxy(target *url.URL, skipTlsValidation bool) *WebsocketProxy {
+func NewProxy(target *url.URL, options Options) *WebsocketProxy {
 	backend := func(r *http.Request) *url.URL {
 		// Shallow copy
 		u := *target
@@ -68,7 +75,7 @@ func NewProxy(target *url.URL, skipTlsValidation bool) *WebsocketProxy {
 		u.RawQuery = r.URL.RawQuery
 		return &u
 	}
-	return &WebsocketProxy{Backend: backend, Verbal: false, SkipTlsValidation: skipTlsValidation}
+	return &WebsocketProxy{Backend: backend, Verbal: false, Options: options}
 }
 
 // ServeHTTP implements the http.Handler that proxies WebSocket connections.
@@ -88,7 +95,7 @@ func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
 
 	dialer := w.Dialer
 	if w.Dialer == nil {
-		if w.SkipTlsValidation {
+		if w.Options.SkipTLSValidation {
 			//Disable TLS secure check if target allow skip verification
 			bypassDialer := websocket.DefaultDialer
 			bypassDialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
@@ -171,6 +178,13 @@ func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
 		upgrader = DefaultUpgrader
 	}
 
+	//Fixing issue #107 by bypassing request origin check
+	if w.Options.SkipOriginCheck {
+		upgrader.CheckOrigin = func(r *http.Request) bool {
+			return true
+		}
+	}
+
 	// Only pass those headers to the upgrader.
 	upgradeHeader := http.Header{}
 	if hdr := resp.Header.Get("Sec-Websocket-Protocol"); hdr != "" {

+ 4 - 1
mod/websocketproxy/websocketproxy_test.go

@@ -28,7 +28,10 @@ func TestProxy(t *testing.T) {
 	}
 
 	u, _ := url.Parse(backendURL)
-	proxy := NewProxy(u, false)
+	proxy := NewProxy(u, Options{
+		SkipTLSValidation: false,
+		SkipOriginCheck:   false,
+	})
 	proxy.Upgrader = upgrader
 
 	mux := http.NewServeMux()

+ 9 - 0
reverseproxy.go

@@ -381,6 +381,7 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
 	}
 	bypassGlobalTLS := (bpgtls == "true")
 
+	// Basic Auth
 	rba, _ := utils.PostPara(r, "bauth")
 	if rba == "" {
 		rba = "false"
@@ -388,6 +389,13 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
 
 	requireBasicAuth := (rba == "true")
 
+	// Bypass WebSocket Origin Check
+	strbpwsorg, _ := utils.PostPara(r, "bpwsorg")
+	if strbpwsorg == "" {
+		strbpwsorg = "false"
+	}
+	bypassWebsocketOriginCheck := (strbpwsorg == "true")
+
 	//Load the previous basic auth credentials from current proxy rules
 	targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain)
 	if err != nil {
@@ -402,6 +410,7 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
 	newProxyEndpoint.BypassGlobalTLS = bypassGlobalTLS
 	newProxyEndpoint.SkipCertValidations = skipTlsValidation
 	newProxyEndpoint.RequireBasicAuth = requireBasicAuth
+	newProxyEndpoint.SkipWebSocketOriginCheck = bypassWebsocketOriginCheck
 
 	//Prepare to replace the current routing rule
 	readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint)

+ 1 - 0
test/proxmox/outreq.json

@@ -0,0 +1 @@
+{"Accept":["*/*"],"Accept-Encoding":["gzip, deflate, br"],"Accept-Language":["en-US,en;q=0.5"],"Cookie":["PVEAuthCookie=PVE%3Aroot@pam%3A65DE9998%3A%3APu/u+vEo0CGCqrdvEBvPUs2OsJd6OqUvK8ycyTyErwY+c+udX4bi3RHqSd6qOeMa+aIRQv9TfpESctWjVjfvrWth9nDDzFnZv1zwotY/k3hYpOsKuvcm+QtVDbxIkSHHV+5f0UxQp0zZ1NqJO8AUJXsMMN7bbJFLvsvzHOBjkrTDMMw+XOpY7XNbCy2vKjQgRZ2/Kfnv5rZEGnpNIXnINoHjJ0FPpK16+9bWfI0nR0NOyCwlvO10DXZT6ywet5WQwJZ/HOa0jN8N1pvtNh8kW78kPHTDMrLq1eWfPBwFXohNGn/5zOAUqTRUV+YNWY+qI6PibKBJHmTsJaFS0PBHOw%3D%3D"],"Csrfpreventiontoken":["65DE9998:psURpq1uyJukNU9tepAAWPcZdwIn1Bsxq/V6x86uPB4"],"Referer":["https://vm.localhost/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-origin"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0"],"X-Requested-With":["XMLHttpRequest"]}

+ 1 - 0
test/proxmox/request.json

@@ -0,0 +1 @@
+{"Accept":["*/*"],"Accept-Encoding":["gzip, deflate, br"],"Accept-Language":["en-US,en;q=0.5"],"Cookie":["PVEAuthCookie=PVE%3Aroot@pam%3A65DE9998%3A%3APu/u+vEo0CGCqrdvEBvPUs2OsJd6OqUvK8ycyTyErwY+c+udX4bi3RHqSd6qOeMa+aIRQv9TfpESctWjVjfvrWth9nDDzFnZv1zwotY/k3hYpOsKuvcm+QtVDbxIkSHHV+5f0UxQp0zZ1NqJO8AUJXsMMN7bbJFLvsvzHOBjkrTDMMw+XOpY7XNbCy2vKjQgRZ2/Kfnv5rZEGnpNIXnINoHjJ0FPpK16+9bWfI0nR0NOyCwlvO10DXZT6ywet5WQwJZ/HOa0jN8N1pvtNh8kW78kPHTDMrLq1eWfPBwFXohNGn/5zOAUqTRUV+YNWY+qI6PibKBJHmTsJaFS0PBHOw%3D%3D"],"Csrfpreventiontoken":["65DE9998:psURpq1uyJukNU9tepAAWPcZdwIn1Bsxq/V6x86uPB4"],"Referer":["https://vm.localhost/"],"Sec-Fetch-Dest":["empty"],"Sec-Fetch-Mode":["cors"],"Sec-Fetch-Site":["same-origin"],"Te":["trailers"],"User-Agent":["Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0"],"X-Forwarded-Host":["vm.localhost"],"X-Forwarded-Server":["zoraxy-0dc57054-6e06-42b1-ac84-c6d29aa5208d"],"X-Requested-With":["XMLHttpRequest"]}

+ 14 - 1
web/components/httprp.html

@@ -153,6 +153,13 @@
                 if (requireBasicAuth){
                     checkstate = "checked";
                 }
+
+                let skipWebSocketOriginCheck = payload.SkipWebSocketOriginCheck;
+                let wsCheckstate = "";
+                if (skipWebSocketOriginCheck){
+                    wsCheckstate = "checked";
+                }
+
                 column.empty().append(`<div class="ui checkbox" style="margin-top: 0.4em;">
                     <input type="checkbox" class="RequireBasicAuth" ${checkstate}>
                     <label>Require Basic Auth</label>
@@ -165,6 +172,11 @@
                                 Advance Configs
                             </div>
                             <div class="content">
+                                <div class="ui checkbox" style="margin-top: 0.4em;">
+                                    <input type="checkbox" class="SkipWebSocketOriginCheck" ${wsCheckstate}>
+                                    <label>Skip WebSocket Origin Check<br>
+                                    <small>Check this to allow cross-origin websocket requests</small></label>
+                                </div>
                                 <button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editCustomHeaders('${uuid}');"><i class="heading icon"></i> Custom Headers</button>
                                 <!-- <button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editLoadBalanceOptions('${uuid}');"><i class="blue server icon"></i> Load Balance</button> -->
                             </div>
@@ -215,7 +227,7 @@
         let skipCertValidations = $(row).find(".SkipCertValidations")[0].checked;
         let requireBasicAuth = $(row).find(".RequireBasicAuth")[0].checked;
         let bypassGlobalTLS = $(row).find(".BypassGlobalTLS")[0].checked;
-
+        let bypassWebsocketOrigin = $(row).find(".SkipWebSocketOriginCheck")[0].checked;
         console.log(newDomain, requireTLS, skipCertValidations, requireBasicAuth)
 
         $.ajax({
@@ -228,6 +240,7 @@
                 "bpgtls": bypassGlobalTLS,
                 "tls" :requireTLS,
                 "tlsval": skipCertValidations,
+                "bpwsorg" : bypassWebsocketOrigin,
                 "bauth" :requireBasicAuth,
             },
             success: function(data){