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>
This commit is contained in:
Sanjar Tulkinov Anvar o'g'li 2026-06-24 22:56:19 -04:00 committed by Baptiste Wojtkowski
parent 4f021a1f1f
commit 9f6b0da7ac
No known key found for this signature in database
GPG Key ID: 47A8A13E10BC0D40
2 changed files with 27 additions and 25 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);
@ -607,25 +608,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;
}
elsif ( $retained_modifiers eq 'i' ) {
$value =~ s/$search/$replace/iee;
}
elsif ( $retained_modifiers eq 'g' ) {
$value =~ s/$search/$replace/gee;
}
else {
$value =~ s/$search/$replace/ee;
$value =~ s/$search/Koha::Regex::Replacement::expand_template( $replace, [@{^CAPTURE}], {%+} )/ige;
} elsif ( $retained_modifiers eq 'i' ) {
$value =~ s/$search/Koha::Regex::Replacement::expand_template( $replace, [@{^CAPTURE}], {%+} )/ie;
} elsif ( $retained_modifiers eq 'g' ) {
$value =~ s/$search/Koha::Regex::Replacement::expand_template( $replace, [@{^CAPTURE}], {%+} )/ge;
} else {
$value =~ s/$search/Koha::Regex::Replacement::expand_template( $replace, [@{^CAPTURE}], {%+} )/e;
}
return $value;

View File

@ -21,6 +21,8 @@ use Modern::Perl;
use constant LAST_TRANSACTION_FIELD => q/005/; # MARC21/UNIMARC
our (@ISA, @EXPORT_OK);
use Koha::Regex::Replacement;
BEGIN {
require Exporter;
our @ISA = qw(Exporter);
@ -696,8 +698,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||;
@ -705,18 +705,20 @@ sub _modify_values {
$modifiers .= $modifier
if grep {/$modifier/} @available_modifiers;
}
foreach my $value ( @$values ) {
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;
}
elsif ( $modifiers eq 'i' ) {
$value =~ s/$regex->{search}/$replace/iee;
}
elsif ( $modifiers eq 'g' ) {
$value =~ s/$regex->{search}/$replace/gee;
}
else {
$value =~ s/$regex->{search}/$replace/ee;
$value =~ s/$search/Koha::Regex::Replacement::expand_template( $replace, [@{^CAPTURE}], {%+} )/ige;
} elsif ( $modifiers eq 'i' ) {
$value =~ s/$search/Koha::Regex::Replacement::expand_template( $replace, [@{^CAPTURE}], {%+} )/ie;
} elsif ( $modifiers eq 'g' ) {
$value =~ s/$search/Koha::Regex::Replacement::expand_template( $replace, [@{^CAPTURE}], {%+} )/ge;
} else {
$value =~ s/$search/Koha::Regex::Replacement::expand_template( $replace, [@{^CAPTURE}], {%+} )/e;
}
}
}