
Cloud Vulnerability DB
A community-led vulnerabilities database
Fixed in net-ssh 8.1.2 and 7.3.10. Net::SSH::Proxy::HTTPS now verifies the proxy's certificate against the system trust store and checks its hostname by default, and checks the hostname with any verifying :ssl_context.
Upgrade. On older versions, passing an :ssl_context built with set_params verifies the certificate chain but not the hostname. After upgrading, a proxy with a self-signed or private CA certificate needs an :ssl_context whose cert_store trusts it; one with verify_mode = OpenSSL::SSL::VERIFY_NONE restores the old unverified behaviour.
Net::SSH::Proxy::HTTPS exists specifically so that proxy authentication
credentials are sent over an encrypted channel rather than in plaintext (per
its own class documentation: "This has the advantage that proxy authentication
information is not sent in plaintext.").
However, when a caller does not pass an explicit :ssl_context, the class
builds a bare OpenSSL::SSL::SSLContext.new. That default context has
verify_mode == VERIFY_NONE, no trust store, and no hostname verification. The
code then calls SSLSocket#connect on it. As a result the TLS handshake to the
proxy accepts any certificate, including a self-signed certificate issued
for an unrelated hostname.
A network attacker able to intercept the connection to the proxy (the classic
threat model TLS is meant to defend against) can therefore impersonate the
proxy, complete the handshake, and capture the Proxy-Authorization: Basic
credentials that the client sends in the CONNECT request. Because the class's
documented purpose is to keep those credentials confidential, the insecure
default silently defeats the one guarantee the class offers.
Weakness: CWE-295 (Improper Certificate Validation)
Component: net-ssh (RubyGem), class Net::SSH::Proxy::HTTPS
Affected file: lib/net/ssh/proxy/https.rb
Confirmed in version: 7.3.6 (latest at time of writing)
Severity (reporter estimate): Medium — see "Severity" below
Status: Verified with a working, self-contained PoC
lib/net/ssh/proxy/https.rb:
def initialize(proxy_host, proxy_port = 80, options = {})
@ssl_context = options.delete(:ssl_context) ||
OpenSSL::SSL::SSLContext.new # <-- VERIFY_NONE, no trust store, no hostname check
super(proxy_host, proxy_port, options)
end
def establish_connection(connect_timeout)
plain_socket = super(connect_timeout)
OpenSSL::SSL::SSLSocket.new(plain_socket, @ssl_context).tap do |socket|
socket.extend(SSLSocketCompatibility)
socket.connect # <-- no post_connection_check(hostname)
end
endTwo independent gaps:
verify_mode, so OpenSSL defaults to
VERIFY_NONE and the certificate chain is not validated at all.VERIFY_PEER context, the code never sets
the SNI hostname (SSLSocket#hostname=) and never calls
post_connection_check(proxy_host), so hostname validation is not performed.
(The PoC demonstrates gap #1; gap #2 is noted for completeness and would also
warrant a fix.)Reporter estimate: Medium. Rationale:
:ssl_context.
But it is a genuine insecure default that silently nullifies the exact
protection the class advertises, and the fix is low-risk, so it should not be
dismissed as purely configuration.
CVSS 3.1 (reporter estimate): AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:N → 5.9
(Medium). Maintainers should adjust to their own threat model.Make the default context verify by default, and perform hostname verification:
def initialize(proxy_host, proxy_port = 80, options = {})
@ssl_context = options.delete(:ssl_context) || begin
ctx = OpenSSL::SSL::SSLContext.new
ctx.set_params # sets verify_mode = VERIFY_PEER and loads the default CA store
ctx
end
super(proxy_host, proxy_port, options)
end
def establish_connection(connect_timeout)
plain_socket = super(connect_timeout)
OpenSSL::SSL::SSLSocket.new(plain_socket, @ssl_context).tap do |socket|
socket.hostname = proxy_host # SNI + enables hostname check
socket.extend(SSLSocketCompatibility)
socket.connect
socket.post_connection_check(proxy_host)
end
endThis is a behavior change (previously-working MITM'd or self-signed setups will
start failing), so it is appropriate for a minor/major release with a
changelog note. Callers who genuinely need the old behavior can still pass an
:ssl_context with verify_mode = VERIFY_NONE explicitly, making the choice
opt-in and visible rather than a silent default.
A complete, self-contained PoC (poc/poc_verified.rb) was supplied privately to the maintainers. It runs
entirely on 127.0.0.1 with no external network access:
CN=evil-mitm.local
(a hostname the "client" is not connecting to).Net::SSH::Proxy::HTTPS#open('internal-ssh-host.example.com', 22), pointed at
the local listener with :user/:password set but no :ssl_context.gem install net-ssh # tested against 7.3.6
ruby poc_verified.rbnet-ssh 7.3.6 | OpenSSL 3.5.5 27 Jan 2026
Default OpenSSL::SSL::SSLContext.new verify_mode => 0 (0/nil == VERIFY_NONE)
== DEFAULT CONFIG (no :ssl_context) — VULNERABLE ==
client: open() SUCCEEDED (handshake accepted)
>>> MITM captured proxy credentials: "victim:hunter2-proxy-secret"
== CONTROL (explicit VERIFY_PEER) — expected to fail ==
client: open() raised OpenSSL::SSL::SSLError: ... certificate verify failed (self-signed certificate)
endpoint saw: server-side error: ... tlsv1 alert unknown ca (SSL alert number 48)The default-config run completes the handshake against a self-signed
certificate for the wrong hostname and the rogue endpoint recovers the
Base64-decoded credentials victim:hunter2-proxy-secret. The control run shows
that enabling VERIFY_PEER correctly rejects the same certificate, confirming
the behavior is caused by the missing verification and not by the PoC harness.
An attacker in a man-in-the-middle position on the path to the HTTPS proxy can:
CONNECT exchange.
This directly defeats the class's stated security goal. Depending on how the
proxy credentials are used elsewhere, captured credentials may enable further
access. An attacker who both captures the credentials and relays the tunnel can
also observe timing/metadata of the tunneled traffic.
Note on scope: the SSH session carried inside the tunnel is still protected by
SSH host-key verification, so this issue alone does not grant SSH session
takeover unless host-key checking is separately disabled or set to accept-new
on first use. The confirmed, direct impact is disclosure of the proxy
credentials and loss of proxy authenticity.Source: NVD
Free Vulnerability Assessment
Evaluate your cloud security practices across 9 security domains to benchmark your risk level and identify gaps in your defenses.
Get a personalized demo
"Best User Experience I have ever seen, provides full visibility to cloud workloads."
"Wiz provides a single pane of glass to see what is going on in our cloud environments."
"We know that if Wiz identifies something as critical, it actually is."