erebe/wstunnel
View on GitHubPowerShell client -> company proxy -> nginx -> wstunnel server scenario issues
Open
#507 opened on May 8, 2026
help wanted
Description
Describe the goal
Trying to tunnel into
wstunnel.exe server --restrict-http-upgrade-path-prefix "/wsproxy" ws://0.0.0.0:5000
with a powershell script as a client, through nginx listening on wss://sitename/wsproxy, config:
location /wsproxy {
proxy_pass http://127.0.0.1:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
Describe what does not work
With proxy_pass http://127.0.0.1:5000; I get
←[2m2026-05-08T10:07:00.552967Z←[0m ←[33m WARN←[0m ←[1mcnx←[0m←[1m{←[0m←[3mpeer←[0m←[2m=←[0m"127.0.0.1:49809"←[1m}←[0m←[2m:←[0m←[1mtunnel←[0m←[1m{←[0m←[3mforwarded_for←[0m←[2m=←[0m"remote IP"←[1m}←[0m←[2m:←[0m ←[2mwstunnel::tunnel::server::server←[0m←[2m:←[0m Rejecting connection with bad upgrade request: /wsproxy
with proxy_pass http://127.0.0.1:5000/;
←[2m2026-05-08T10:07:00.552967Z←[0m ←[33m WARN←[0m ←[1mcnx←[0m←[1m{←[0m←[3mpeer←[0m←[2m=←[0m"127.0.0.1:49809"←[1m}←[0m←[2m:←[0m←[1mtunnel←[0m←[1m{←[0m←[3mforwarded_for←[0m←[2m=←[0m"remote IP"←[1m}←[0m←[2m:←[0m ←[2mwstunnel::tunnel::server::server←[0m←[2m:←[0m Rejecting connection with bad upgrade request: /
PS script used:
# ==========================================
# CONFIGURATION
# ==========================================
$localPort = 8080
$targetUri = "wss://sitename/wsproxy"
# 1. Compile the Header Injector (Essential for bypass)
if (-not ([System.Management.Automation.PSTypeName]'WSInjector').Type) {
Add-Type -TypeDefinition @"
using System;
using System.Net;
using System.Reflection;
using System.Net.WebSockets;
public class WSInjector {
public static void SetOptions(ClientWebSocket ws, IWebProxy proxy) {
var options = ws.Options;
// Native .NET Core 3.1+ approach used by the module
// On PS 5.1, we use reflection to set the internal proxy if the property is missing
try {
typeof(ClientWebSocketOptions).GetProperty("Proxy").SetValue(options, proxy);
} catch {
FieldInfo field = typeof(ClientWebSocketOptions).GetField("_proxy", BindingFlags.NonPublic | BindingFlags.Instance);
if (field != null) field.SetValue(options, proxy);
}
}
}
"@
}
# 2. Setup System Proxy with Credentials
$systemProxy = [System.Net.WebRequest]::GetSystemWebProxy()
$systemProxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials
# 3. Initialize WebSocket Client (Module Logic)
$wsClient = New-Object System.Net.WebSockets.ClientWebSocket
[WSInjector]::SetOptions($wsClient, $systemProxy)
# 4. Connect to Nginx
$cts = New-Object System.Threading.CancellationTokenSource
try {
Write-Host "Connecting to $targetUri..." -ForegroundColor Cyan
$connectTask = $wsClient.ConnectAsync($(New-Object System.Uri($targetUri)), $cts.Token)
$connectTask.Wait()
Write-Host "Connected to Nginx!" -ForegroundColor Green
} catch {
Write-Host "Connection Failed: $($_.Exception.InnerException.Message)" -ForegroundColor Red
exit
}
# 5. Local Listener & Traffic Pump
$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, $localPort)
$listener.Start()
Write-Host "Local proxy listening on 127.0.0.1:$localPort"
$localClient = $listener.AcceptTcpClient()
$localStream = $localClient.GetStream()
# Full-Duplex Pumping (Module-style)
$tasks = @(
[System.Threading.Tasks.Task]::Run({
$buffer = New-Object Byte[] 4096
while ($localClient.Connected -and $wsClient.State -eq 'Open') {
if ($localStream.DataAvailable) {
$read = $localStream.Read($buffer, 0, 4096)
$segment = New-Object System.ArraySegment[Byte]($buffer, 0, $read)
$wsClient.SendAsync($segment, 'Binary', $true, $cts.Token).Wait()
}
Start-Sleep -Milliseconds 10
}
}),
[System.Threading.Tasks.Task]::Run({
$buffer = New-Object Byte[] 4096
while ($localClient.Connected -and $wsClient.State -eq 'Open') {
$segment = New-Object System.ArraySegment[Byte]($buffer)
$res = $wsClient.ReceiveAsync($segment, $cts.Token)
$res.Wait()
if ($res.Result.Count -gt 0) {
$localStream.Write($buffer, 0, $res.Result.Count)
}
}
})
)
[System.Threading.Tasks.Task]::WaitAll($tasks)
PS script fails at step 4
I can verify, that nginx config is ok, company proxy does let websocket connections do the http upgrade (using rustdesk web client), not sure, why Powershell fails with wstunnel server.
Any help is appreciated.