From d7c1e19b495d854d0c17ad65fdb5e339edba40fc Mon Sep 17 00:00:00 2001 From: Sanjar Tulkinov Anvar o'g'li Date: Wed, 24 Jun 2026 22:56:19 -0400 Subject: [PATCH] Bug 30233: Stop running MARC modification replacements as code Koha::SimpleMARC::_modify_values and Koha::Items::apply_regex wrapped the user replacement in a string and ran it through s///ee, so an interpolation block such as \@{[ system(...) ]} executed arbitrary Perl. Expand the replacement as data via Koha::Regex::Replacement::expand_template under a single /e instead, which keeps capture-group replacements working but never runs the input as code. Signed-off-by: Sanjar Tulkinov Anvar o'g'li Signed-off-by: David Cook Signed-off-by: Martin Renvoize Signed-off-by: Baptiste Wojtkowski Signed-off-by: Pedro Amorim --- Koha/Items.pm | 15 +++++++++------ Koha/SimpleMARC.pm | 16 ++++++++++------ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/Koha/Items.pm b/Koha/Items.pm index c47753c2cb..6f1d46a7ee 100644 --- a/Koha/Items.pm +++ b/Koha/Items.pm @@ -32,6 +32,7 @@ use Koha::SearchEngine::Indexer; use Koha::Item::Attributes; use Koha::Item; use Koha::CirculationRules; +use Koha::Regex::Replacement; use base qw(Koha::Objects); @@ -634,22 +635,24 @@ sub apply_regex { my $modifiers = $params->{modifiers} || q{}; my $value = $params->{value}; - $replace =~ s/"/\\"/g; # Protection from embedded code - $replace = '"' . $replace . '"'; # Put in a string for /ee my @available_modifiers = qw( i g ); my $retained_modifiers = q||; for my $modifier ( split //, $modifiers ) { $retained_modifiers .= $modifier if grep { /$modifier/ } @available_modifiers; } + + # The replacement is expanded as DATA (capture-group backreferences and the + # \n \r \t escapes) and never evaluated as code, so @{...}/${...} + # interpolation blocks are inert text, not Perl. if ( $retained_modifiers =~ m/^(ig|gi)$/ ) { - $value =~ s/$search/$replace/igee; + $value =~ s/$search/Koha::Regex::Replacement::expand_template( $replace, [@{^CAPTURE}], {%+} )/ige; } elsif ( $retained_modifiers eq 'i' ) { - $value =~ s/$search/$replace/iee; + $value =~ s/$search/Koha::Regex::Replacement::expand_template( $replace, [@{^CAPTURE}], {%+} )/ie; } elsif ( $retained_modifiers eq 'g' ) { - $value =~ s/$search/$replace/gee; + $value =~ s/$search/Koha::Regex::Replacement::expand_template( $replace, [@{^CAPTURE}], {%+} )/ge; } else { - $value =~ s/$search/$replace/ee; + $value =~ s/$search/Koha::Regex::Replacement::expand_template( $replace, [@{^CAPTURE}], {%+} )/e; } return $value; diff --git a/Koha/SimpleMARC.pm b/Koha/SimpleMARC.pm index ef0edae9c0..6e929f3b03 100644 --- a/Koha/SimpleMARC.pm +++ b/Koha/SimpleMARC.pm @@ -19,6 +19,7 @@ use Modern::Perl; use base 'Exporter'; use Koha::Exceptions; +use Koha::Regex::Replacement; BEGIN { our @EXPORT_OK = qw( @@ -819,8 +820,6 @@ sub _modify_values { if ( $regex and $regex->{search} ) { my $replace = $regex->{replace}; - $replace =~ s/"/\\"/g; # Protection from embedded code - $replace = '"' . $replace . '"'; # Put in a string for /ee $regex->{modifiers} //= q||; my @available_modifiers = qw( i g ); my $modifiers = q||; @@ -828,15 +827,20 @@ sub _modify_values { $modifiers .= $modifier if grep { /$modifier/ } @available_modifiers; } + my $search = $regex->{search}; foreach my $value (@$values) { + + # The replacement is expanded as DATA (capture-group backreferences + # and the \n \r \t escapes) and never evaluated as code, so + # @{...}/${...} interpolation blocks are inert text, not Perl. if ( $modifiers =~ m/^(ig|gi)$/ ) { - $value =~ s/$regex->{search}/$replace/igee; + $value =~ s/$search/Koha::Regex::Replacement::expand_template( $replace, [@{^CAPTURE}], {%+} )/ige; } elsif ( $modifiers eq 'i' ) { - $value =~ s/$regex->{search}/$replace/iee; + $value =~ s/$search/Koha::Regex::Replacement::expand_template( $replace, [@{^CAPTURE}], {%+} )/ie; } elsif ( $modifiers eq 'g' ) { - $value =~ s/$regex->{search}/$replace/gee; + $value =~ s/$search/Koha::Regex::Replacement::expand_template( $replace, [@{^CAPTURE}], {%+} )/ge; } else { - $value =~ s/$regex->{search}/$replace/ee; + $value =~ s/$search/Koha::Regex::Replacement::expand_template( $replace, [@{^CAPTURE}], {%+} )/e; } } }