From c44fd4a067bf5979762aec5fd130512be1216254 Mon Sep 17 00:00:00 2001 From: David Cook Date: Thu, 17 Oct 2024 05:35:56 +0000 Subject: [PATCH] Bug 37727: Prevent CSV Formula injection via DataTables This change prevents CSV Formula injection on DataTables exports by escaping formula with a single quote prefix as per OWASP recommendations. Test plan: 0. Apply patch 1. Go to http://localhost:8081/cgi-bin/koha/members/memberentry.pl ?op=edit_form&destination=circ&borrowernumber=51 2. Add the following in a "Circulation note" =SUM(1+1) 3. Go to http://localhost:8081/cgi-bin/koha/members/member.pl ?quicksearch=1&circsearch=1&searchmember=koha 4. Click "Export" and choose "Excel" and "CSV" 5. Open those downloaded files in Excel 6. Note that the =SUM(1+1) function is prefixed with a single quote, and is not automatically executed Signed-off-by: Magnus Enger Works as advertised. The problematic "cell" is exported as "'=SUM(1+1)". Signed-off-by: Chris Cormack --- .../prog/en/includes/columns_settings.inc | 21 +++++++++++++++++-- koha-tmpl/intranet-tmpl/prog/js/datatables.js | 21 +++++++++++++++++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/columns_settings.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/columns_settings.inc index b14872e5afb..7200033b874 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/columns_settings.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/columns_settings.inc @@ -90,6 +90,23 @@ function KohaTable(id_selector, dt_parameters, table_settings, add_filters, defa exportColumns = dt_parameters["exportColumns"]; } + const export_format_spreadsheet = { + body: function ( data, row, column, node ) { + var newnode = $(node); + + if ( newnode.find(".noExport").length > 0 ) { + newnode = newnode.clone(); + newnode.find(".noExport").remove(); + } + let trimmed_str = newnode.text().replace( /\n/g, ' ' ).trim(); + const unsafeCharacters = /^[=+\-@\t\r]/; + if ( unsafeCharacters.test(trimmed_str) ){ + trimmed_str = "'" + trimmed_str; + } + return trimmed_str; + } + } + var export_format = { body: function ( data, row, column, node ) { var newnode = $(node); @@ -124,7 +141,7 @@ function KohaTable(id_selector, dt_parameters, table_settings, add_filters, defa exportOptions: { columns: exportColumns, rows: exportRows, - format: export_format + format: export_format_spreadsheet }, }, { @@ -155,7 +172,7 @@ function KohaTable(id_selector, dt_parameters, table_settings, add_filters, defa exportOptions: { columns: exportColumns, rows: exportRows, - format: export_format + format: export_format_spreadsheet }, } ); diff --git a/koha-tmpl/intranet-tmpl/prog/js/datatables.js b/koha-tmpl/intranet-tmpl/prog/js/datatables.js index 9813505421b..eb4362fc887 100644 --- a/koha-tmpl/intranet-tmpl/prog/js/datatables.js +++ b/koha-tmpl/intranet-tmpl/prog/js/datatables.js @@ -606,6 +606,23 @@ function _dt_buttons(params){ exportColumns = settings["exportColumns"]; } + const export_format_spreadsheet = { + body: function ( data, row, column, node ) { + var newnode = $(node); + + if ( newnode.find(".noExport").length > 0 ) { + newnode = newnode.clone(); + newnode.find(".noExport").remove(); + } + let trimmed_str = newnode.text().replace( /\n/g, ' ' ).trim(); + const unsafeCharacters = /^[=+\-@\t\r]/; + if ( unsafeCharacters.test(trimmed_str) ){ + trimmed_str = "'" + trimmed_str; + } + return trimmed_str; + } + } + var export_format = { body: function ( data, row, column, node ) { var newnode = $(node); @@ -624,14 +641,14 @@ function _dt_buttons(params){ extend: 'excelHtml5', exportOptions: { columns: exportColumns, - format: export_format + format: export_format_spreadsheet }, }, { extend: 'csvHtml5', exportOptions: { columns: exportColumns, - format: export_format + format: export_format_spreadsheet }, }, { -- 2.39.5