Ubiquitous Encryption: Preventing Mixed Web Content Errors

Manually finding mixed content can be extremely time-consuming especially when dealing with includes. When visiting websites using HTTPS in modern web browsers, you get alerted that the site tries to serve you mixed content and displays these as errors and warnings in the JavaScript console. The latest versions even block the content!

When using Google Chrome, you can view these alerts, by opening the Chrome JavaScript console. You can open the console either from the View menu: View -> Developer -> JavaScript Console, or by right-clicking the page, selecting Inspect Element, and then selecting Console.

So what about automation!?! Well, you could write scripts and find the issues one by one. However, for the time being, I do think it is better to let us change/rewrite the output using the webserver instead, by adding output filters to them, substituting / rewriting the content. This way, we can start serving pages immediately over https with next to no issues.

Below you find instructions for both Nginx and Apache, in which we will add modules that "find and replace" - "http" with "https". Ideally, you should use "//". This will automatically keep you using the same protocol you were on. However, since we are talking "Ubiquitous Encryption", let us hardcode https for now!

Instructions

Apache 2.4

Pre-requisites

Check for the existence of the necessary modules:

$ sudo apachectl -t -D DUMP_MODULES | grep substitute
 substitute_module (static)

Example configuration

Place a .htaccess in the root

RewriteEngine On

AddOutputFilterByType SUBSTITUTE text/html
Substitute s/http:/https:/ni

RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R,L]

When using SSL termination (Cloudflare flexible SSL, nginx proxy, 443 to 80 etc.)

RewriteCond %{HTTP:X-Forwarded-Proto} = http [NC]
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R,L]

Nginx

Pre-requisites

The ngx_http_sub_module module is a filter that modifies a response by replacing one specified string by another. This particular module is not built by default. You can add it by adding the "--with-http_sub_module"-configuration parameter.

Example configuration

location / {
  sub_filter_once off;
  sub_filter_types text/html;
  sub_filter "http://" "https://";
}

/etc/nginx/sites-enabled/somedomain.conf (part 1)

server {
    server_name                 somedomain.* www.somedomain.*;
    include /etc/nginx/conf.d/default_listen.conf;
    return 301 https://$host$request_uri;
}

# ...

/etc/nginx/conf.d/default_listen.conf

    server_tokens               off;
    listen                      80;
    listen                      [::]:80;

    if ($http_x_forwarded_proto = "http") {
        return 301 https://$host$request_uri;
    }

    if ($http_x_forwarded_proto = "https") {
        add_header              Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
    }

/etc/nginx/sites-enabled/somedomain.conf (part 2)

# ...

server {
    server_name                 somedomain.* www.somedomain.*;
    root                        /var/www/somedomain/html;

    access_log                  /var/log/nginx/somedomain.access.log;
    error_log                   /var/log/nginx/somedomain.error.log;

    ssl_certificate             /etc/letsencrypt/live/somedomain.net/fullchain.pem;
    ssl_certificate_key         /etc/letsencrypt/live/somedomain.net/privkey.pem;

    include /etc/nginx/conf.d/letsencrypt.conf;
    include /etc/nginx/conf.d/default_site.conf;
    include /etc/nginx/conf.d/default_ssl.conf;
    include /etc/nginx/conf.d/default_php7.conf;
}

/etc/nginx/conf.d/letsencrypt.conf

location /.well-known/acme-challenge {
    default_type  "text/plain";
    root          /var/www/letsencrypt/html;
}

/etc/nginx/conf.d/default_site.conf

    index                       index.php;
    server_tokens               off;
    listen                      443 ssl http2;
    listen                      [::]:443 ssl http2;
    client_max_body_size        20M;

    location ~* \.(js|css|png|jpg|jpeg|gif|ico|html|woff|ttf|svg|eot|otf|xml)$ {
        add_header              "Access-Control-Allow-Origin" "*";
        add_header              Vary "Accept-Encoding";
        add_header              Cache-Control "public";
        access_log              off;
        log_not_found           off;
        expires                 30d;
    }

    error_page 404 = /index.php;
    error_page 403 = /index.php;

    location = /favicon.ico { access_log off; log_not_found off; }
    location ~ /\. { access_log off; log_not_found off; deny all; }
    location ~ ~$ { access_log off; log_not_found off; deny all; }

/etc/nginx/conf.d/default_ssl.conf

#   Default SSL settings
    ssl_dhparam                 /etc/nginx/ssl/dh4k.pem;

#    ssl_session_cache           builtin:1000  shared:SSL:2m;
    ssl_session_cache           shared:SSL:10m;
    ssl_session_timeout         5m;
    ssl_protocols               TLSv1.2;
    ssl_prefer_server_ciphers   on;
    ssl_ecdh_curve              secp521r1:secp384r1;

#    Experimenting line with Poly etc
    ssl_ciphers                 ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305-D:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-CHACHA20-POLY1305-D::ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA:DHE-RSA-CAMELLIA256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA:DHE-RSA-CAMELLIA256-SHA:ECDH-RSA-AES256-SHA;

    ssl_stapling                on;
    ssl_stapling_verify         on;

    add_header                  Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
    resolver                    8.8.8.8 8.8.4.4;

/etc/nginx/conf.d/default_php7.conf

    location / {
        try_files                       $uri $uri/ /index.php?$args;
    }

location ~ [^/]\.php(/|$) {
    fastcgi_index                       index.php;
    fastcgi_split_path_info             ^(.+?\.php)(/.*)$;
    if (!-f $document_root$fastcgi_script_name) {
        return 404;
    }
    fastcgi_param                       SCRIPT_FILENAME                 $document_root$fastcgi_script_name;
    fastcgi_param                       PATH_INFO                       $fastcgi_script_name;
    fastcgi_pass                        unix:/var/run/php/php7.0-fpm.sock;
    include                             fastcgi_params;

    fastcgi_buffer_size                 128k;
    fastcgi_buffers                     256 16k;
    fastcgi_busy_buffers_size           256k;
    fastcgi_temp_file_write_size        256k;
}

HSTS: HTTP Strict-Transport-Security

Adding a HSTS (HTTP Strict-Transport-Security) header can be very dangerous. However adding one sends a good and strong message. The above code adds it if your proxy uses https, but also states that all subdomains under your hostname, should be considered HTTPS only. I use this code pretty much everywhere, but especially behind load-balancers, like elastic load-balancers, proxies and cdn's.

    add_header                  Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";

This line communicates to the client / browser that all communication to the hostname and all its subdomains should use secure channels to do so, for a period of 31536000 seconds. On top of that all communication should redirect prior to connect using the preload variable.

Handling mixed content with Content Security Policy

Content security policy (CSP) is a browser feature enabling you management mixed content at scale. The CSP reporting mechanism can be used to track the mixed content on sites, while the enforcement policy can help you protect users by upgrading or blocking mixed content.

You can enable these features for pages by adding Content-Security-Policy or Content-Security-Policy-Report-Only in the response header. You can also set Content-Security-Policy using a <meta> tag in the <head> section of your pages.

Reporting

Add the following header and log all traffic to the endpoint

Content-Security-Policy-Report-Only: default-src https: 'unsafe-inline' 'unsafe-eval'; report-uri https://somedomain/reportingEndpoint

Upgrade connection

Add header or meta line

Content-Security-Policy: upgrade-insecure-requests
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests" />

Block connection

Add header or meta line

Content-Security-Policy: block-all-mixed-content
<meta http-equiv="Content-Security-Policy" content="block-all-mixed-content" />
Author: Angelique Dawnbringer Published: 2017-06-16 22:34:27 Keywords:
  • Ubiquitous Encryption
  • Mod Rewrite
  • Mod Substitute
  • ngx_http_sub_module
  • Apache
  • Prevent Mixed Content
  • Rewrite http to https
  • Redirect http to https
  • HSTS
  • CSP
  • Content Security Policy
Modified: 2017-09-10 17:51:34