From 692cc8d3e783552f9ad942d38d06cde866d91707 Mon Sep 17 00:00:00 2001 From: kulikov-a <36099472+kulikov-a@users.noreply.github.com> Date: Wed, 27 Jul 2022 22:49:28 +0300 Subject: [PATCH] www/nginx: tls fingerprints rfc8701 compat. (#3018) * typo * rfc8701 * rfc8701 * includes * version bump * ignore SCSVs (rfc5746 and rfc7507) * add http_post hook so we can add maps if needed * Update pkg-descr --- www/nginx/Makefile | 3 +- www/nginx/pkg-descr | 7 +++++ .../opnsense/scripts/nginx/ngx_functions.js | 26 +++++++++++++---- .../src/opnsense/scripts/nginx/setup.php | 2 +- .../scripts/nginx/tls_ua_fingerprint.php | 28 +++++++++++++------ .../templates/OPNsense/Nginx/http.conf | 2 ++ 6 files changed, 52 insertions(+), 16 deletions(-) diff --git a/www/nginx/Makefile b/www/nginx/Makefile index 14fe6e55e..1f80edd56 100644 --- a/www/nginx/Makefile +++ b/www/nginx/Makefile @@ -1,6 +1,5 @@ PLUGIN_NAME= nginx -PLUGIN_VERSION= 1.28 -PLUGIN_REVISION= 2 +PLUGIN_VERSION= 1.29 PLUGIN_COMMENT= Nginx HTTP server and reverse proxy PLUGIN_DEPENDS= nginx PLUGIN_MAINTAINER= franz.fabian.94@gmail.com diff --git a/www/nginx/pkg-descr b/www/nginx/pkg-descr index 697dfaafe..e623b03e4 100644 --- a/www/nginx/pkg-descr +++ b/www/nginx/pkg-descr @@ -10,6 +10,13 @@ WWW: https://nginx.org/ Plugin Changelog ================ +1.29 + +* fixed a typo in the trusted tls fingerprints db creation part of setup.php +* rfc5746, rfc7507 and rfc8701 are taken into account on compiling and comparing tls fingerprints +* the reason for scoring the connection as intercepted is added to the X-TLS-Client-Intercepted header. check backend settings if using this feature +* http_post hook added to be able to map global variables + 1.28 * add support for connect-src and worker-src in content security policy diff --git a/www/nginx/src/opnsense/scripts/nginx/ngx_functions.js b/www/nginx/src/opnsense/scripts/nginx/ngx_functions.js index 3acd7d9db..bdfdbbbcb 100755 --- a/www/nginx/src/opnsense/scripts/nginx/ngx_functions.js +++ b/www/nginx/src/opnsense/scripts/nginx/ngx_functions.js @@ -1,13 +1,17 @@ var fs = require('fs'); var tls_fingerprints = JSON.parse(fs.readFileSync('/usr/local/etc/nginx/tls_fingerprints.json')); +// ignore GREASE cipher suite values when compiling a browser fingerprint (see rfc8701) +const GREASE = ["0x0a0a", "0x1a1a", "0x2a2a", "0x3a3a", "0x4a4a", "0x5a5a", "0x6a6a", "0x7a7a", "0x8a8a", "0x9a9a", "0xaaaa", "0xbaba", "0xcaca", "0xdada", "0xeaea", "0xfafa"]; +// ignore SCSV cipher suite values when compiling a browser fingerprint (see rfc5746 and rfc7507) +const SCSV = ["TLS_EMPTY_RENEGOTIATION_INFO_SCSV", "TLS_FALLBACK_SCSV"]; function check_cipher_array(r, browser_ciphers, fingerprint_ciphers, result) { - if (result.status == 'Intercepted') { + if (result.status.includes('Intercepted')) { return; } if (browser_ciphers.length > fingerprint_ciphers.length) { - // the proxy supports more cipers than the browser -> intercepted - result.status = "Intercepted"; + // the proxy supports more ciphers than the browser -> intercepted + result.status = "Intercepted; Reason=\"excess suite\""; return; } var browser_cipher; @@ -18,9 +22,9 @@ function check_cipher_array(r, browser_ciphers, fingerprint_ciphers, result) { browser_cipher = browser_ciphers[browser_cipher_index]; current_index = fingerprint_ciphers.indexOf(browser_cipher); if (current_index === -1 || current_index <= last_index) { - // a cipher has been found, which is not supported by the browser + // a cipher has been found, which is not supported by the browser or order of preference changed // such a connection is definitly intercepted - result.status = "Intercepted"; + result.status = "Intercepted; Reason=\"excess suite or wrong order\""; return; } last_index = current_index; @@ -36,11 +40,23 @@ function check_intercept(r) { var ua = r.headersIn['User-Agent']; if (ua in tls_fingerprints) { var fp = tls_fingerprints[ua]; + fp.ciphers = fp.ciphers.filter( function( el ) { + return ((GREASE.indexOf( el ) < 0) && (SCSV.indexOf( el ) < 0)); + } ); + fp.curves = fp.curves.filter( function( el ) { + return GREASE.indexOf( el ) < 0; + } ); var browser_ciphers = r.variables.ssl_ciphers.split(':'); + browser_ciphers = browser_ciphers.filter( function( el ) { + return ((GREASE.indexOf( el ) < 0) && (SCSV.indexOf( el ) < 0)); + } ); check_cipher_array(r, browser_ciphers, fp.ciphers, tls_result); if (r.variables.ssl_curves != '') { var browser_curves = r.variables.ssl_curves.split(':'); + browser_curves = browser_curves.filter( function( el ) { + return GREASE.indexOf( el ) < 0; + } ); check_cipher_array(r, browser_curves, fp.curves, tls_result); } } diff --git a/www/nginx/src/opnsense/scripts/nginx/setup.php b/www/nginx/src/opnsense/scripts/nginx/setup.php index 849b48257..ce440df37 100755 --- a/www/nginx/src/opnsense/scripts/nginx/setup.php +++ b/www/nginx/src/opnsense/scripts/nginx/setup.php @@ -304,7 +304,7 @@ foreach ($nginx->tls_fingerprint->iterateItems() as $tls_fingerprint) { if ((string)$tls_fingerprint->trusted == '1') { $ciphers = explode(':', (string)$tls_fingerprint->ciphers); if (!empty((string)$tls_fingerprint->curves)) { - $curves = explode(':', (string)$tls_fingerprint->ciphers); + $curves = explode(':', (string)$tls_fingerprint->curves); } else { $curves = array(); } diff --git a/www/nginx/src/opnsense/scripts/nginx/tls_ua_fingerprint.php b/www/nginx/src/opnsense/scripts/nginx/tls_ua_fingerprint.php index 7ba8f1b16..89fd7148e 100755 --- a/www/nginx/src/opnsense/scripts/nginx/tls_ua_fingerprint.php +++ b/www/nginx/src/opnsense/scripts/nginx/tls_ua_fingerprint.php @@ -31,13 +31,25 @@ $database_name = '/var/log/nginx/handshakes.json'; function parse_line($line) { + // ignore GREASE cipher suite values when compiling a browser fingerprint (see rfc8701) + $GREASE = array("0x0a0a", "0x1a1a", "0x2a2a", "0x3a3a", "0x4a4a", "0x5a5a", "0x6a6a", "0x7a7a", "0x8a8a", "0x9a9a", "0xaaaa", "0xbaba", "0xcaca", "0xdada", "0xeaea", "0xfafa"); + // ignore SCSV cipher suite values when compiling a browser fingerprint (see rfc5746 and rfc7507) + $SCSV = array("TLS_EMPTY_RENEGOTIATION_INFO_SCSV", "TLS_FALLBACK_SCSV"); $tmp = explode('"', trim($line)); - return array( + $fp = array( 'ua' => $tmp[1], 'ciphers' => $tmp[3], 'curves' => $tmp[5] == '-' ? '' : $tmp[5], 'count' => 1 ); + // exclude GREASE and SCSV suits from fingerprint + $fp_ciphers = explode(':', $fp['ciphers']); + $fp_ciphers = array_diff($fp_ciphers, $GREASE, $SCSV); + $fp['ciphers'] = implode(':', $fp_ciphers); + $fp_curves = explode(':', $fp['curves']); + $fp_curves = array_diff($fp_curves, $GREASE); + $fp['curves'] = implode(':', $fp_curves); + return $fp; } function filter_ua($key) { @@ -61,13 +73,13 @@ $fingerprints = array(); $handle = @fopen($tls_logfile, 'r'); if ($handle) { while (($buffer = fgets($handle)) !== false) { - $md5line = md5($buffer); - if (array_key_exists($md5line, $fingerprints)) { - $fingerprints[$md5line]['count']++; - } else { - $parsed_line = parse_line($buffer); - if ($parsed_line['ciphers'] != '-') { - $fingerprints[$md5line] = $parsed_line; + $parsed_line = parse_line($buffer); + if ($parsed_line['ciphers'] != '-') { + $md5fp = md5($parsed_line['ua'] . $parsed_line['ciphers'] . $parsed_line['curves']); + if (array_key_exists($md5fp, $fingerprints)) { + $fingerprints[$md5fp]['count']++; + } else { + $fingerprints[$md5fp] = $parsed_line; } } } diff --git a/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/http.conf b/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/http.conf index 4b4e0886b..b7655a054 100644 --- a/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/http.conf +++ b/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/http.conf @@ -52,6 +52,8 @@ map $http_upgrade $connection_upgrade { '' close; } +include http_post/*.conf; + # TODO add when core is ready for allowing nginx to serve the web interface # include nginx_web.conf;