Bug 7948 - Web Access doesn't send Strict-Transport-Security header
Summary: Web Access doesn't send Strict-Transport-Security header
Status: CLOSED FIXED
Alias: None
Product: ThinLinc
Classification: Unclassified
Component: Web Access (show other bugs)
Version: trunk
Hardware: PC Unknown
: P2 Normal
Target Milestone: 4.18.0
Assignee: Emelie
URL:
Keywords: linma_tester, relnotes
Depends on:
Blocks:
 
Reported: 2022-06-16 08:00 CEST by Pierre Ossman
Modified: 2024-10-29 22:49 CET (History)
9 users (show)

See Also:
Acceptance Criteria:
MUST: * Admins should be able to choose to use HSTS or not for: - Web Admin - Web Access * Should work the same for people that don't care about HSTS SHOULD: * Should be configurable through: - Command line COULD: * HSTS preloading registration should be documented * Should be configurable through: - Web Admin


Attachments
Certificate required for HSTS testing (627 bytes, application/x-x509-ca-cert)
2024-09-17 14:10 CEST, Emelie
Details

Description Pierre Ossman cendio 2022-06-16 08:00:57 CEST
HTTP has an addition called "Strict Transport Security" (commonly abbreviated HSTS) that is intended to mitigate man-in-the-middle attacks by having the browser know that a site should never be accessed without TLS¹.

It would be beneficial if Web Access could use this feature to increase the security for users.

The main blocker for this is that enabling it affects every service with the same host name². That means if Web Access is running on port 300 and enables this, then browsers will refuse to communicate without TLS with another web server running on e.g. port 80 on the same machine.

As such, we cannot add this unconditionally. It must be a configuration option that is enabled, only when the side effects are acceptable.

¹ Supposedly also never with a certificate exception
² The recommendation is also to enable it for all subdomains, exacerbating the problem
Comment 1 Pierre Ossman cendio 2022-06-16 08:03:23 CEST
Also note that HSTS has a major design flaw; it only works if the browser has visited the site before. Otherwise, it has no knowledge that HSTS is enabled and a man-in-the-middle attack is still possible.

Browsers are trying to mitigate this issue by preloading a list of HSTS enabled hosts. However, this requires manual registration for each domain.

The registration is done here: https://hstspreload.org/
Comment 6 Pierre Ossman cendio 2023-08-19 12:35:10 CEST
An interesting reference is the settings that VMware Horizon provides for things like this:

https://docs.vmware.com/en/VMware-Horizon-7/7.13/horizon-security/GUID-F949EF93-2B0E-45B9-99F1-37FB5F286188.html
Comment 8 Pierre Ossman cendio 2023-09-28 09:38:15 CEST
NICE DCV solves this by having a very general setting of extra HTTP headers:

https://docs.aws.amazon.com/dcv/latest/adminguide/manage-headers.html

This doesn't seem very user-friendly, though, as it requires the users to have detailed technical knowledge about these things. I also wonder how fragile this is if the user adds a header that breaks functionality in the web services.
Comment 10 Steve Moulton 2024-05-01 17:29:48 CEST
I am having to remove https access to ThinLinc to meet United States government hsts header compliance requirements.   I'd rather stick with your product, but it appears after having several interchanges there is no intention to either enable hsts headers, or a mechanism for me to add my own headers.

I know that hsts is flawed in that a browser has to visit the site first, but this compliance requirement is something I have to work with.

I now have to look at other products to meet the remote desktop need.  I would hate to say goodbye to Cendio after all of these years.
Comment 11 Linn cendio 2024-09-13 11:33:07 CEST
HSTS is controlled by the single header "Strict-Transport-Security", which takes 3 different arguments:
 - max-age (required): specifies the number of seconds the browser only requests HTTPS from this site

 - includeSubDomains (optional): Enforces HTTPS on subdomains

 - preload (optional): site has to be preapproved by being in the list from comment 1.

So far, it's not fully clear if these value should be set by us or the customers. Since this is related to policy, it is probably good to let the customers decide which they want to follow.

We should look further in how to test this on our end. Some ideas are:
 - Turn off the 301 redirect.
 - Create a "bad site" as a test case


Questions that remain to be answered:
 - How does HSTS affect other services?
 - Look into how includeSubDomains works
 - How to we handle customers that want to use preload feature?
 - How do other people solve this issue? Is there a bransch standard?
Comment 12 Emelie cendio 2024-09-17 14:02:04 CEST
To check that HSTS works as expected, we did the following:

- Added a send_header-call in our httpserver.py with:
> self.send_header("Strict-transport-security", "max-age=10")

- Firefox: 
  * In .mozilla/firefox/<profile>/SiteSecurityService.[bin/txt]
     * On Firefox 131, the file SiteSecurityService.bin was updated live to
       include the domain of our test server. 
     * On Firefox 115 esr, the file SiteSecurityService.txt was updated when the
       browser was closed. 
  * With HSTS enabled, visiting http adress on the server the network tab showed
    that we went to the https adress instead. When not sending an HSTS-header we 
    see a 301 redirect in firefox's network-tab.
 

- Chrome: 
  * We saw a 307 redirect if HSTS was enabled when visiting an http adress on the
    server. This redirected us to the same url but with https. When not sending an
    HSTS-header we see a 301 redirect in chrome's network-tab.
  * We don't know if chrome://net-internals#hsts could be useful. It's supposed to
    be both dynamic and static preload. We have tried both our own server and 
    external sites e.g. expressen.se and could not see chrome://net-internals#hsts 
    being updated as we expected.
Comment 13 Emelie cendio 2024-09-17 14:10:44 CEST
Created attachment 1236 [details]
Certificate required for HSTS testing

A requirement for HSTS to work is that the certificate must be signed by a
trusted CA.

We have a CA setup for our internal network [1] and we used this to issue a
signed certificate to a lab machine with ThinLinc installed. We have added the
root certificate for our CA in our browser. Without adding the root certificate
to our browser, we get a security warning when we visit web access/webadmin.

This setup was required to ensure we could test HSTS correctly.

[1] https://intranet.lkpg.cendio.se/DriftInfo/SmallStep
Comment 14 Emelie cendio 2024-09-17 14:26:48 CEST
We tested that the max-age directive is respected by Firefox 115-esr and Chromium 127. This was done by setting a 10 second max-age and then seeing that we get the same behavior as a first-site visit.

We still need to decide if max-age should be configurable for the admin or set by us. If we set the argument then we need to decide the tradeoff between:
  1. A small number giving a lower security.
  2. A large number will make turning off HSTS in the future cumbersome.
Comment 15 Frida Flodin cendio 2024-09-18 10:20:02 CEST
(In reply to Emelie from comment #12)
> To check that HSTS works as expected, we did the following:
> 
> - Added a send_header-call in our httpserver.py with:
> > self.send_header("Strict-transport-security", "max-age=10")
> 
> - Firefox: 
> ...
>  
> 
> - Chrome: 
> ...

- Safari:
  * We saw a 302 redirect if HSTS was enabled when visiting an http adress on the
    server. This redirected us to the same url but with https. When not sending an
    HSTS-header we see a 301 redirect in safari's network-tab.
Comment 16 Samuel Mannehed cendio 2024-09-19 10:00:08 CEST
We looked at how some other products handled HSTS, here are the results:

Bugzilla

  * Has no settings for this.

WordPress

  * Has settings for all combinations of Strict-Transport-Security header
    directives (arguments). The settings aren't free to set to whatever value,
    there are helpful high-level descriptions for each alternative setting and 
    value:
    1. Set an HSTS policy
       - Switch ON/OFF
    2. Domains included in the HSTS policy (includeSubDomains):
       - All domains
       - All domains and their subdomains
    3. Length of time browsers should remember the HSTS policy (max-age):
       - 5 minutes (for testing)
       - 1 year
    4. Allow browsers to preload the HSTS policy:
       - Checkbox ON/OFF
  * WordPress's documentation, including a screenshot of the settings UI
    can be found here: https://docs.wpvip.com/access-and-routing/enable-hsts/
 
VMware Horizon

  * HSTS is always forced ON, with max-age set to 1 year
  * Two separate settings are available:
    - hstsFlags.1=includeSubDomains
    - hstsFlags.2=preload
    These are minimally documented, and more research is expected.
 
GitLab

  * HSTS is ON by default. GitLab ships with a nginx web server and is fully
    configurable just like a regular nginx server is. The settings are free
    text, but their defaults are:
    - max-age: 2 years
    - no includeSubDomains
    - no preload
 
Jenkins(L) 

  * No setting for this in the main product.
  * An old plugin exists for enabling HSTS, but the project seems dead:
    https://wiki.jenkins-ci.org/JENKINS/HSTS-Filter-Plugin.html
    However, this plugin is still another data-point on how other people
    present HSTS settings. It presents three options:
    1. Send Strict-Transport-Security HTTP Header
       - Checkbox ON/OFF
    2. Max Age (in seconds)
       - free text
    3. Include Sub-Domains
       - Checkbox ON/OFF
Comment 17 Emelie cendio 2024-09-19 14:15:24 CEST
Looking at the above examples of what other products do, we have a first draft of what our settings could look like:

> # warning dont touch if you dont understand
> [/tlwebadm/security]
> 
> hsts_policy=off                         [off/testing/permanent]
> hsts_subdomains_included=false          [true/false]
> hsts_allow_browser_preload=false        [true/false]

The idea is that the first setting is a combination of ON/OFF for HSTS and max-age. We think that a good value for max-age when using "testing" is 5 minutes, according to hstspreload.org [1]. When in production, we think that a max-age of 2 years is good [1]. Instead of having "2-years" as the setting value, we use the word "permanent" to indicate that this choice is difficult to reverse.

Furthermore, when hsts_policy is "off", our thinking is that this means we don't send Strict-Transport-Security at all. The alternative would have been to send the header, but with "max-age=0". Our testing shows that this can help the browser to clear its list of HSTS sites. In the case that "off" would send "max-age=0", their users would still have to revisit our specific web service in order to get the updated value.

[1] https://hstspreload.org/#deployment-recommendations
Comment 18 Emelie cendio 2024-09-19 14:32:23 CEST
NOTE: Potential risk when there are other services on the same domain is that they also need to have trusted signed certificates, otherwise the browsers will block access to them.

For example, in Firefox we tried to visit another service on the same domain as our ThinLinc with HSTS enabled and got the following warning:
> <domain> has a security policy called HTTP Strict Transport Security (HSTS),
> which means that Firefox can only connect to it securely. You can’t add an
> exception to visit this site.
Comment 19 Steve Moulton 2024-09-23 19:19:54 CEST
I am required to have any https service supply the header

  Strict-Transport-Security: max-age=31536000; includeSubDomains

The includeSubDomains option islocal policy.  The applicable US Government document is https://https.cio.gov/hsts/

The SHOULD as documented above would meet our need; another option is to put the appropriate directive in a configuration file (which is actually my preference, as I used Ansible to configure most everything).
Comment 20 Samuel Mannehed cendio 2024-09-24 14:19:20 CEST
Hi Steve,

The current plan is to add the following configuration parameters to /opt/thinlinc/etc/conf.d/webaccess.hconf:

> [/webaccess/hsts]
> 
> policy=off
> subdomains_included=false
> allow_browser_preload=false

As well as matching configuration parameters to /opt/thinlinc/etc/conf.d/tlwebadm.hconf:

> [/tlwebadm/hsts]
> 
> policy=off
> subdomains_included=false
> allow_browser_preload=false

That will also unlock the possibility to configure these settings using tl-config:

> sudo tl-config /webaccess/hsts/policy=permanent
> sudo tl-config /webaccess/hsts/subdomains_included=true
> sudo systemctl restart tlwebaccess

Configuring the parameters as above should result in the following header to each request:

>  Strict-Transport-Security: max-age=63072000; includeSubDomains
Comment 21 Steve Moulton 2024-09-24 15:54:10 CEST
This is good and will drop neatly into our ansible configuration process.

Will there be a mechanism to configure the max-age parameter?   If not, what will it be set to?
Comment 22 Samuel Mannehed cendio 2024-09-24 16:53:26 CEST
(In reply to Steve Moulton from comment #21)
> Will there be a mechanism to configure the max-age parameter?   If not, what
> will it be set to?

Not directly no. However, setting "/webaccess/hsts/policy=permanent" will result in a max-age of 63072000 which is 2 years, according to hstspreload.org recommendations [1]. Setting "/webaccess/hsts/policy=testing" will give a max-age of 600, which corresponds to 5 minutes.

Does that work for you?

[1] https://hstspreload.org/#deployment-recommendations
Comment 23 Samuel Mannehed cendio 2024-09-26 08:25:35 CEST
Regarding the names of the parameters, after internal discussions we have decided to leave them as-is. The alternatives were:

> [webaccess/hsts]
> mode=off
> subdomains=false
> preload=false

* We went with "policy" over "mode" since "mode=off" sounds a bit wrong.
* "subdomains_included" was chosen over "subdomains" since the shorter name could give the impression that the setting would ONLY affect subdomains, and not also the domain of the host.
* We selected "allow_browser_preload" instead of simply "preload". The reason being that the shorter variant could incorrectly indicate that this enables HSTS preload. What the setting actually does is to allow the domain to be added to the preload lists, further steps are required.

In general, we picked clear and longer names on purpose because these settings can break things if the admin isn't careful.
Comment 24 Samuel Mannehed cendio 2024-09-26 08:30:00 CEST
The configuration folder [webaccess/hsts] isn't set in stone. Given that we will likely add other configuration parameters in bug 8191, bug 8319 and bug 8400 - we might want a shared folder for these.

A suggested configuration folder name is [webaccess/security], the hsts-folder could be a subfolder in that.

It should also be noted that we should consider moving other related parameters into these new folders. While it's not ideal to move parameters, we can write migration code in tl-setup to help sysadmins in an upgrade.
Comment 26 Steve Moulton 2024-09-26 18:48:02 CEST
(In reply to Samuel Mannehed from comment #22)
> (In reply to Steve Moulton from comment #21)
> > Will there be a mechanism to configure the max-age parameter?   If not, what
> > will it be set to?
> 
> Not directly no. However, setting "/webaccess/hsts/policy=permanent" will
> result in a max-age of 63072000 which is 2 years, according to
> hstspreload.org recommendations [1]. Setting
> "/webaccess/hsts/policy=testing" will give a max-age of 600, which
> corresponds to 5 minutes.
> 
> Does that work for you?
> 
> [1] https://hstspreload.org/#deployment-recommendations

I would prefer being able to set the max-age parameter myself, but it is not a requirement.    I have a compliance requirement of at least one year, which this meets.
Comment 27 Samuel Mannehed cendio 2024-09-27 08:38:23 CEST
(In reply to Steve Moulton from comment #26)
> I would prefer being able to set the max-age parameter myself, but it is not
> a requirement.    I have a compliance requirement of at least one year,
> which this meets.

Good to hear that this meets your compliance requirements. Thank you
Comment 28 Samuel Mannehed cendio 2024-09-27 09:21:56 CEST
Regarding includeSubdomains, we did some further testing and digging:

The "includeSubdomains" directive only affects the current HSTS-sending host, and subdomains to that host's name. [1, 2]

A ThinLinc Web Access configured with a trusted certbot-certificate as well as /webaccess/hsts/policy=testing and /webaccess/hsts/subdomains_included=true was set up for testing
 * "lab-109.lkpg.cendio.se" was the hostname of the HSTS host
 * "lab-56.lkpg.cendio.se" was another tl host, no trusted cert and no HSTS
 * "subdomain.lab-109.lkpg.cendio.se" was a subdomain set up on lab-109, with
   HTTP-only
Given these machines, I tested how Google Chrome behaved. On the first visit to http://lab-109.., I got a regular 301-redirect. On subsequent visits, I instead got Chrome's HSTS-307 fake redirect. I also verified that I got the "includeSubdomains" directive as part of the HSTS header.

Thereafter, I visited http://lab-56... without problem, in the same browser session, indicating that "includeSubdomains" doesn't affect other domains at the same level.

Lastly, I tried to visit "http://subdomain.lab-109.lkpg.cendio.se", but was blocked. Chrome tries to redirect me to https://subdomain.lab-109..., with a 307: "non-authoritative-reason: HSTS", and then I get a ERR_ADDRESS_UNREACHABLE.

[1] https://datatracker.ietf.org/doc/html/rfc6797#section-6.1.2
[2] https://datatracker.ietf.org/doc/html/rfc6797#section-11.4.2
Comment 35 Madeleine cendio 2024-09-27 16:28:41 CEST
Since many of our customers seem to be using Nessus, we utilized it for evaluating Web Access in the latest build to verify that it detected the HSTS-headers. See attachment 1237 [details], attachment 1238 [details], attachment 1239 [details] for Nessus logs with different configurations.
Comment 41 Emelie cendio 2024-09-30 13:21:18 CEST
> MUST:
> * Admins should be able to choose to use HSTS or not for:
>  - Web Admin ✅
>  - Web Access ✅
> 
> * Should work the same for people that don't care about HSTS ✅
> 
> SHOULD:
> * Should be configurable through:
>   - Command line ✅
> 
> COULD:
> * HSTS preloading registration should be documented ✅
Briefly mentioned in "Configuring HSTS"
> 
> * Should be configurable through:
>   - Web Admin ❌
Not at this point.


This bug should be considered resolved.
Comment 42 Emelie cendio 2024-09-30 13:23:23 CEST
Forgot release-notes
Comment 45 Madeleine cendio 2024-10-02 08:52:02 CEST
Following up on comment 35, after comparing the reports from two Nessus scans — one before and one after HSTS was configured — the following observations were made:

- Before HSTS was configured, there was a report entry showing "HSTS Missing From HTTPS Server" (Plugin ID: 84502). This is absent in the "After HSTS" scan.

- After HSTS was configured, there is a report entry for "Strict Transport Security (STS) Detection" (Plugin ID: 42822), confirming HSTS is now configured.

- After HSTS was configured, the report entry "SSL Anonymous Cipher Suites Supported" (Plugin ID: 31705) with medium severity appeared. This was not flagged in the earlier scan.
	-> This refers to that the domain supports use of anonymous SSL ciphers, which are algorithms that encrypt and decrypt data to secure network communication. These anonymous ciphers don't verify the server's identity, leaving the connection vulnerable to MITM attacks. 


In addition, after the HSTS configuration a total of 17 new "Info" report entries were added: 
- "RPC portmapper Service Detection" (Plugin ID: 10223)
- "Backported Security Patch Detection (SSH)" (Plugin ID: 39520)
- "OS Security Patch Assessment Not Available" (Plugin ID: 117886)
- "OpenSSH Detection" (Plugin ID: 181418)
- "RPC Services Enumeration" (Plugin ID: 11111)
- "SSH Algorithms and Languages Supported" (Plugin ID: 70657)
- "SSH Password Authentication Accepted" (Plugin ID: 149334)
- "SSH SHA-1 HMAC Algorithms Enabled" (Plugin ID: 153588)
- "mDNS Detection (Local Network)" (Plugin ID: 66717)
Comment 46 Emelie cendio 2024-10-02 15:58:32 CEST
We have read all linked support issues and the findings are:

* Only Nessus is mentioned as a security scanner.
* Some issues mentioned desired header options. Given the configuration parameters added to ThinLinc here, the desired header options can now be configured.
Comment 48 Emelie cendio 2024-10-02 16:44:56 CEST
We reviewed the code and made a minor change for easier readability.

It looks good now!
Comment 50 Linn cendio 2024-10-03 12:44:38 CEST
Read through the HSTS documentation, as well as the release notes, looks good to me.

Other parts of testing such as looking through the code changes and seeing how the feature behaves in practice were done as part of the mob programming development.

Note You need to log in before you can comment on or make changes to this bug.