Bug 42360: Fix SQL injection

This change fixes an SQL injection in the acq stats.

Test plan:
0. Apply the patch
1. sudo koha-plack --restart kohadev
2. Go to /cgi-bin/koha/reports/acquisitions_stats.pl
3. Try out a bunch of options
4. Try to break it
5. Note that you just get output and no errors
NOTE: In a fresh KTD you likely don't have any actual statistics

Signed-off-by: Sanjar Tulkinov Anvar o'g'li <sanjarbiy11@gmail.com>

Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
This commit is contained in:
David Cook 2026-05-22 03:32:32 +00:00 committed by Lucas Gass
parent 67ff112866
commit d2e7b1e8e7
No known key found for this signature in database
GPG Key ID: 60B447398B0D36CA
1 changed files with 84 additions and 32 deletions

View File

@ -40,16 +40,7 @@ Plugin that shows a stats on borrowers
=cut
my $input = CGI->new;
my $do_it = $input->param('do_it');
my $fullreportname = "reports/acquisitions_stats.tt";
my $line = $input->param("Line");
my $column = $input->param("Column");
my @filters = $input->multi_param("Filter");
my $podsp = $input->param("PlacedOnDisplay");
my $rodsp = $input->param("ReceivedOnDisplay");
my $calc = $input->param("Cellvalue");
my $output = $input->param("output");
my $basename = $input->param("basename");
my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
{
@ -60,19 +51,74 @@ my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
}
);
# Validate line and column
if (
$do_it
&& ( !$line
|| $line !~ /^(aqbasket|aqorders|aqbooksellers|items|biblioitems|aqbudgets)\.\w+$/
|| !$column
|| $column !~ /^(aqbasket|aqorders|aqbooksellers|items|biblioitems|aqbudgets)\.\w+$/ )
)
{
C4::Output::output_error( $input, '403' );
exit;
my %allowed_fields = (
"aqbasket.closedate" => 1,
"aqorders.datereceived" => 1,
"aqbooksellers.name" => 1,
"items.homebranch" => 1,
"items.ccode" => 1,
"biblioitems.itemtype" => 1,
"aqbudgets.budget_code" => 1,
);
my $do_it = $input->param('do_it') ? 1 : 0;
my $line = "aqbasket.closedate";
my $line_input = $input->param("Line");
my $column = "aqbooksellers.name";
my $column_input = $input->param("Column");
my @filters = $input->multi_param("Filter");
my $podsp;
my $podsp_input = $input->param("PlacedOnDisplay");
my $rodsp;
my $rodsp_input = $input->param("ReceivedOnDisplay");
my $calc = 1;
my $calc_input = $input->param("Cellvalue");
my $output = "screen";
my $output_input = $input->param("output");
my $basename = "Export";
my $basename_input = $input->param("basename");
#NOTE: Validate Line parameter
if ( $line_input && $allowed_fields{$line_input} ) {
$line = $line_input;
}
#NOTE: Validate Column parameter
if ( $column_input && $allowed_fields{$column_input} ) {
$column = $column_input;
}
#NOTE: Validate PlacedOnDisplay parameter
if ( $podsp_input && $podsp_input =~ /^\d+$/ ) {
$podsp = $podsp_input;
}
#NOTE: Validate ReceivedOnDisplay parameter
if ( $rodsp_input && $rodsp_input =~ /^\d+$/ ) {
$rodsp = $rodsp_input;
}
#NOTE: Validate Cellvalue parameter
if ( $calc_input && $calc_input =~ /^\d+$/ ) {
$calc = $calc_input;
}
#NOTE: Validate output parameter
if ( $output_input && ( $output_input eq "screen" || $output_input eq "file" ) ) {
$output = $output_input;
}
#NOTE: Validate basename parameter
if ($basename_input) {
$basename_input =~ s/[^A-Za-z0-9-]+/_/g;
if ($basename_input) {
$basename = $basename_input;
}
}
#NOTE: Ideally it would be nice to validate the Filter parameter,
#but that would take a fair bit of work...
our $sep = C4::Context->csv_delimiter( scalar $input->param("sep") );
$template->param(
@ -435,41 +481,47 @@ sub calculate {
$strcalc .= " AND aqorders.datereceived IS NOT NULL AND aqorders.datereceived <> '' "
if ( $process == 5 );
@$filters[0] =~ s/\*/%/g if ( @$filters[0] );
$strcalc .= " AND aqbasket.closedate >= '" . @$filters[0] . "'"
$strcalc .= " AND aqbasket.closedate >= ?"
if ( @$filters[0] );
@$filters[1] =~ s/\*/%/g if ( @$filters[1] );
$strcalc .= " AND aqbasket.closedate <= '" . @$filters[1] . "'"
$strcalc .= " AND aqbasket.closedate <= ?"
if ( @$filters[1] );
@$filters[2] =~ s/\*/%/g if ( @$filters[2] );
$strcalc .= " AND aqorders.datereceived >= '" . @$filters[2] . "'"
$strcalc .= " AND aqorders.datereceived >= ?"
if ( @$filters[2] );
@$filters[3] =~ s/\*/%/g if ( @$filters[3] );
$strcalc .= " AND aqorders.datereceived <= '" . @$filters[3] . "'"
$strcalc .= " AND aqorders.datereceived <= ?"
if ( @$filters[3] );
@$filters[4] =~ s/\*/%/g if ( @$filters[4] );
$strcalc .= " AND aqbooksellers.name LIKE '" . @$filters[4] . "'"
$strcalc .= " AND aqbooksellers.name LIKE ?"
if ( @$filters[4] );
$strcalc .= " AND items.homebranch = '" . @$filters[5] . "'"
$strcalc .= " AND items.homebranch = ?"
if ( @$filters[5] );
@$filters[6] =~ s/\*/%/g if ( @$filters[6] );
$strcalc .= " AND items.ccode = '" . @$filters[6] . "'"
$strcalc .= " AND items.ccode = ?"
if ( @$filters[6] );
@$filters[7] =~ s/\*/%/g if ( @$filters[7] );
$strcalc .= " AND biblioitems.itemtype LIKE '" . @$filters[7] . "'"
$strcalc .= " AND biblioitems.itemtype LIKE ?"
if ( @$filters[7] );
@$filters[8] =~ s/\*/%/g if ( @$filters[8] );
$strcalc .= " AND aqbudgets.budget_code LIKE '" . @$filters[8] . "'"
$strcalc .= " AND aqbudgets.budget_code LIKE ?"
if ( @$filters[8] );
@$filters[9] =~ s/\*/%/g if ( @$filters[9] );
$strcalc .= " AND aqorders.sort1 LIKE '" . @$filters[9] . "'"
$strcalc .= " AND aqorders.sort1 LIKE ?"
if ( @$filters[9] );
@$filters[10] =~ s/\*/%/g if ( @$filters[10] );
$strcalc .= " AND aqorders.sort2 LIKE '" . @$filters[10] . "'"
$strcalc .= " AND aqorders.sort2 LIKE ?"
if ( @$filters[10] );
$strcalc .= " GROUP BY $linefield, $colfield ORDER BY $linefield,$colfield";
my $dbcalc = $dbh->prepare($strcalc);
$dbcalc->execute;
my @bind = ();
foreach my $filter (@$filters) {
if ($filter) {
push( @bind, $filter );
}
}
$dbcalc->execute(@bind);
my $emptycol;
while ( my ( $row, $col, $value ) = $dbcalc->fetchrow ) {