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" />