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 <sanjarbiy11@gmail.com>
Signed-off-by: David Cook <dcook@prosentient.com.au>
Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk>
Signed-off-by: Baptiste Wojtkowski <baptiste.wojtkowski@biblibre.com>
Signed-off-by: Pedro Amorim <pedro.amorim@openfifth.co.uk>
This commit is contained in:
Sanjar Tulkinov Anvar o'g'li 2026-06-24 22:56:19 -04:00 committed by Pedro Amorim
parent cda91ee42c
commit d7c1e19b49
No known key found for this signature in database
GPG Key ID: 06B75BA7F3894D55
2 changed files with 19 additions and 12 deletions

View File

@ -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;

View File

@ -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;
}
}
}