Hi,
Not sure if you would call this a security issue, hence I am asking this on the
mailing list prior to opening a github issue:
I’ve noticed that it is really easy to bypass the check on client certificates
of a domain when the client can present a valid certificate for another domain.
Consider this HAproxy config:
global
log /dev/log len 4096 format rfc3164 syslog info
defaults
log global
mode http
timeout connect 5s
timeout client 1h
timeout server 5s
frontend myfrontend
bind :443 ssl crt /etc/cert/server.pem crt-list /crt-list
log-format "%ci:%cp [%tr] (%ID) %ft %b/%s %TR/%Tw/%Tc/%Tr/%Tt %ST %B %CC %CS
%tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r %sslc %sslv"
capture request header Host len 256
http-request set-header X-SSL-Client %[ssl_c_used] if
{ ssl_c_used }
http-request set-header X-SSL-Client-Session-ID %[ssl_fc_session_id,hex] if
{ ssl_c_used }
http-request set-header X-SSL-Client-Verify %[ssl_c_verify] if
{ ssl_c_used }
http-request set-header X-SSL-Client-Subject-DN %{+Q}[ssl_c_s_dn] if
{ ssl_c_used }
http-request set-header X-SSL-Client-Subject-CN %{+Q}[ssl_c_s_dn(cn)] if
{ ssl_c_used }
http-request set-header X-SSL-Client-Issuer-DN %{+Q}[ssl_c_i_dn] if
{ ssl_c_used }
http-request set-header X-SSL-Client-NotBefore %{+Q}[ssl_c_notbefore] if
{ ssl_c_used }
http-request set-header X-SSL-Client-NotAfter %{+Q}[ssl_c_notafter] if
{ ssl_c_used }
use_backend bob if { hdr(host) -m dom bob.com }
use_backend alice if { hdr(host) -m dom alice.com }
default_backend alice
backend alice
server alice localhost:8080 check
backend bob
server bob localhost:8081 check
---
So this HAproxy hosts two domains alice.com and bob.com. It uses the following
crt-list to make TLS connections:
/etc/cert/server.pem [ca-file /alice.ca.pem verify required] *.alice.com
/etc/cert/server.pem [ca-file /bob.ca.pem verify required] *.bob.com
---
So any client connecting to alice.com must present a certificate signed by the
Alice CA and any client connecting to bob.com must present a certificate signed
by the Bob CA.
However, I noticed that HAproxy does allow me to “spoof” the host header to
bob.com even though I did a TLS handshake with alice.com. The request will be
forwarded to bob.com with the alice.com certificate:
curl -v -k --cert alice.com.crt --key alice.com.key --resolve
www.alice.com:9443:127.0.0.1<http://www.alice.com:9443:127.0.0.1>
https://www.alice.com:9443/headers -H "host: www.bob.com<http://www.bob.com>"
* Added www.alice.com:9443:127.0.0.1 to DNS cache
* Hostname www.alice.com was found in DNS cache
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to www.alice.com (127.0.0.1) port 9443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/cert.pem
CApath: none
(…)
> GET /headers HTTP/1.1
> Host: www.bob.com
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< date: Thu, 24 Jun 2021 13:07:17 GMT
< content-length: 578
< content-type: text/plain; charset=utf-8
<
Hello from Bob!
----- Headers Received -----
Accept : [*/*]
User-Agent : [curl/7.64.1]
X-Ssl-Client : [1]
X-Ssl-Client-Issuer-Dn :
[/C=US/ST=California/L=AliceLand/O=Alice.com/CN=Alice.com Root
CA/[email protected]]
X-Ssl-Client-Notafter : [220624125634Z]
X-Ssl-Client-Notbefore : [210624125634Z]
X-Ssl-Client-Session-Id :
[D941ECCAACAFEBC5CB3AE17794B54DC3DFC7549C401DB20D7EC5ADC48244D3D0]
X-Ssl-Client-Subject-Cn : [Alice]
X-Ssl-Client-Subject-Dn :
[/C=US/ST=Michigan/L=Detroit/O=Alice.com/CN=Alice/[email protected]]
X-Ssl-Client-Verify : [0]
---
So basically anyone who can get a client certificate from Alice.com can use it
to also connect to Bob.com without getting validated against Bob’s CA.
I’ve tested this with HAproxy 2.2.14.
My questions:
* HAproxy does seem to treat SNI (L5) and HTTP Host Header (L7) as
unrelated. Is this true?
* Applications offloading TLS to HAproxy usually trust that mTLS requests
coming in are validated correctly. They usually don’t revalidate the entire
certificate again and only check for the subject’s identity. Is there a way to
make SNI vs host header checking more strict?
* What’s the best practice to dispatch mTLS requests to backends? I’ve used
a host header based approach here but it shows the above vulnerabilities.
Best regards,
Dom