Learn F5 Technologies, Get Answers & Share Community Solutions Join DevCentral

Filter by:
  • Solution
  • Technology
Answers

TLS/1.0, PCI, and a custom message for HTTP response status codes

By June 30, 2018 we would like to turn off TLS/1.0 on all our HTTPS websites, in order to be compliant with PCI requirements.

Instead of just turning TLS/1.0 off, we would like to use that time between now and June 30, 2018 to seamlessly phase the TLS/1.0 out.

To do so, our F5 still supports all TLS protocols (TLS/1.0, TLS/1.1, and TLS/1.2), but only the clients supporting TLS/1.1 and above get the actual website content. Everyone else (clients supporting TLS/1.0 only) gets a custom error page informing them about the browser upgrade requirement.

To achieve that, I'm using the HTTP status code "426 Upgrade Required". Please see:

The expected result is:

HTTP/1.1 426 Upgrade Required
Upgrade: TLS/1.1, HTTP/1.1
Connection: Upgrade

<html>
... Human-readable HTML page describing why the upgrade is required
    and what to do if this text is seen ...
</html>

Unfortunately, F5 gives me the following result:

HTTP/1.1 426 Unknown Code
Upgrade: TLS/1.1, HTTP/1.1
Connection: close

<html>
... Human-readable HTML page describing why the upgrade is required
    and what to do if this text is seen ...
</html>

My iRule looks like this:

# Detect TLSv1.0 protocol and send HTTP 426
#

when HTTP_REQUEST priority 150 {

    if { [SSL::cipher version] equals "TLSv1" } {
        log local0. "TLS/1.0 connection detected from [IP::client_addr] for [HTTP::host]"
        HTTP::respond 426 -version auto content $static::obsolete_browser_page noserver "Upgrade" "TLS/1.1, HTTP/1.1" "Content-Type" "text/html" "Cache-Control" "no-cache" "Retry-After" "60" "Connection" "Close" 
        event disable
        return
    }
}

If I use "Connection" "Upgrade" in the HTTP::respond code, as defined in the aforementioned RFC2817, F5 replaces it with Connection: Keep-Alive in the actual response which is being sent back to the client.

My questions are:

Question #1: is there a way in F5 to replace the message in HTTP response, so that it says:

HTTP/1.1 426 Upgrade Required

instead of

HTTP/1.1 426 Unknown Code

Question #2: is there a way to force F5 to send HTTP header that says:

Connection: Upgrade

instead of

Connection: close

or

Connection: Keep-Alive
0
Rate this Question
Comments on this Question
Comment made 22-Sep-2017 by BinaryCanary

this is not very elegant, but:

it may be possible to use SSL::respond which allows you to inject raw payload directly into the SSL layer, if this is something you have room to experiment with.

If you go this route however, you may have to make sure that this connection cannot be used for other requests, as I am not sure what effect injecting data into the stream will have on the HTTP profile state machine. So you should consider, including Connection: close in your response, calling HTTP::disable, Dropping the SSL session with SSL::session invalidate, and closing the TCP connection with TCP::close once done.

0
Comment made 23-Sep-2017 by Patrik Jonsson 3491

Very well versed question, kudos for that.

I fiddled around a bit with this in my lab but came to he same conclusion as you did. Maybe you could have an Apache/NGINX server that could do this for you? Or maybe iRulesLX?

Just brainstorming...

/Patrik

0
Comment made 25-Sep-2017 by Juraj 170

Thank you both, BinaryCanary and Patrik for your responses.

To be honest, I do not feel that comfortable to fiddle around with the payload directly on the SSL layer. Unfortunately, I'm not that skilled with iRules. If you have a working example that I could use or modify, I would definitely give it a try.

Regarding the repurposing Apache/Nginx for this, it would be a step backwards for us, because we've just recently migrated the entire website from NGINX/HAPROXY to F5.

Regarding the iRulesLX, I don't even know what it is; googling it right now :)

0

Answers to this Question

placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER

Here's a quick and easy one for this situation based on the responses above:

when HTTP_REQUEST {
    switch -exact [SSL::cipher version] {
        "TLSv1.2" {
            # do nothing
        }
        "TLSv1.1" {
            # do nothing
        } default {
            HTTP::respond 403 content {
<!DOCTYPE html>
<html>
<head><title>Access Denied</title></head>
<body>
    <b><font color=red>You are using a Web browser that is not secure for accessing confidential information. Please upgrade to the most recent version./font><b>
</body>
</html>
} "Content-Type" "text/html" Connection close
            TCP::close
            return
        }
    }
}

.

1
placeholder+image
USER ACCEPTED ANSWER & F5 ACCEPTED ANSWER

A client capable of TLSv1.2 should not connect as TLSv1.0 in the initial instance, so there should never be a valid reason to try to get a browser to upgrade from TLSv1.0 to TLSv1.2.

The Client Hello contains a protocol_version that specifies the lowest version that the client supports, and a client_version that contains the highest TLS version the client supports. The server then responds with the highest version it supports within those two limits. The result is that the client should always connect with the highest TLS version that the client and server can support.

Any client that connects as TLSv1.0 can only connect at TLSv1.0. Your response should be a 200 Response that informs the customer that they need to upgrade their browser.

I'll also add that RFC2817 was intended to drive a TLS protocol upgrade within an unencrypted tcp connection, similar to STARTTLS, allowing virtual hosting for encrypted connections. The adoption of Server Name Indication for SSL has provided a suitable solution to this problem, so RFC2817 is still a proposal after 17 years with no strong drivers for implementation.

0
Comments on this Answer
Comment made 25-Sep-2017 by Juraj 170

Hi S Blakely,

Thank you for your response.

To clarify a couple of things:

  • I do not have a problem with TLS/1.2 clients. Both, TLS/1.1 and TLS/1.2 clients get "200 OK" with the actual content of the page. That's something I have mentioned in my original post. [SSL::cipher version] returns "TLSv1.1" for TLS/1.1 client, or "TLSv1.2" for TLS/1.2 clients. So, these clients will never match the if { [SSL::cipher version] equals "TLSv1" } statement in my iRule. Only TLS/1.0 clients match that statement, and only TLS/1.0 will get back the "426 Unknown Code" (they should be getting "426 Upgrade Required", but F5 doesn't seem to understand what 426 is).
  • I need to return something else than "200 OK" to the TLS/1.0 clients. I need to make sure that any searchbot that stumbles upon our website and speaks TLS/1.0-only, won't index the error message we send back to TLS/1.0-only clients. I have found "426 Upgrade Required" to be the most reasonable code. Another option would probably be "503 Service Unavailable". But definitely not "200 OK".

To give you an example of what the different TLS clients get when talking to my website:

Here's what TLS/1.0-only client currently gets when accessing my website:

curl https://my_f5_site -I --tlsv1.0
HTTP/1.1 426 Unknown Code
Upgrade: TLS/1.1, HTTP/1.1
Content-Type: text/html
Cache-Control: no-cache
Retry-After: 60
Connection: close
Content-Length: 4520

Here's what TLS/1.1-only client currently gets when accessing my website:

curl https://my_f5_site -I --tlsv1.1
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 11388
Date: Mon, 25 Sep 2017 20:54:57 GMT
Connection: Keep-Alive
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Referrer-Policy: no-referrer, strict-origin-when-cross-origin

And here's what TLS/1.2 client currently gets when accessing my website:

curl https://my_f5_site -I --tlsv1.2
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 11388
Date: Mon, 25 Sep 2017 20:55:43 GMT
Connection: Keep-Alive
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Referrer-Policy: no-referrer, strict-origin-when-cross-origin

Anyways, regardless the HTTP code I'm sending back to the client, I'm trying to solve the following two issues:

  • how to customize the HTTP message that goes with the HTTP code back to the clients
  • how to force F5 to send exactly the HTTP header I want, instead of replacing it with something else

Thanks a lot for any help or advice; much appreciated.

0
Comment made 25-Sep-2017 by S Blakely

Juraj,

My point is that an HTTP 426 response is not a useful or appropriate response to the TLSv1.0 request. What do you expect the connecting client to do with the 426 response?

503 Service Unavailable is an option, or a 403 Forbidden with a content body explaining that TLSv1.0 is not supported.

Otherwise, you can use SSL::respond to craft your plaintext quite easily:

SSL::respond "HTTP/1.1 426 Upgrade Required\r\nConnection: Upgrade\r\nContent-Type: text/html\r\nCache-Control: no-cache\r\n\r\n<html>Please upgrade to a browser that supports TLSv1.2</html>"

But again - what are you expecting the client do with that response?

1
Comment made 25-Sep-2017 by Juraj 170

I do not expect clients to do anything with the response. I was just looking for something that would allow me to show a human readable error message (a web page) in a browser to all TLS/1.0-only clients, while at the same time it would prevent any searchbots to index such error page. 4xx and 5xx are the only options, and I thought 426 would be appropriate, with 503 being an option too.

I am not sure what a searchbot (e.g. googlebot) would do with 403. Ignore the content, index the error message?

0
Comment made 25-Sep-2017 by Juraj 170

Thank you for the SSL:respond example, I'll try to play with it a bit :)

Is there anything that would also allow me to do something similar (a custom HTTP message with an HTTP code) for an HTTP traffic (on port 80/tcp)?

0
Comment made 25-Sep-2017 by S Blakely

I do not expect clients to do anything with the response.

Then 426 is definitely the wrong solution. The 426 response requires the client upgrade the connection protocol within the same tcp connection, or close the connection.

Some research indicates that googlebot deindexes 403 errors, and may deindex a 503 error if it is not resolved (within a couple of months).

YMMV

0
Comment made 25-Sep-2017 by S Blakely

For non-encrypted traffic, you can use TCP::respond

I will note that you need to use TCP::close after both SSL::respond and TCP::respond, otherwise the request will be passed back to the pool member and you will get the pool member response as well.

1
Comment made 25-Sep-2017 by Juraj 170

Thanks S Blakely, I'll pass your response about the 426/404/503 codes back to our web team, and let them decide what HTTP code they would prefer.

Initially, I was going to go with a 302 redirect to a 503 page, but then I thought I could just return 503 right away and skip the 302. Then, I got a suggestion from our web team to use 426 instead, and that's when my issues started :)

Anyways, thanks for your help. I think I have a couple of ideas about how to address my issues/questions; your comments and suggestions were very helpful.

0
Comment made 24-Jan-2018 by Mike P. 236

Juraj,

I am looking at doing a similar thing for the same reasons. Did you ever come up with a good solution that you can share?

0