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
This commit is contained in:
kulikov-a
2022-07-27 21:49:28 +02:00
committed by GitHub
parent 5119e66a0f
commit 692cc8d3e7
6 changed files with 52 additions and 16 deletions
+1 -2
View File
@@ -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
+7
View File
@@ -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
@@ -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);
}
}
@@ -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();
}
@@ -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;
}
}
}
@@ -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;