Skip to content

Commit ab12b68

Browse files
committed
request #16213: SQL injection in the "SVN core" commits browser
Also fixes a XSS via the same injection point. Change-Id: Ib33199b7d0ec5f8a07a65d74766477f23df4a75b
1 parent 29022e2 commit ab12b68

6 files changed

+62
-25
lines changed

Diff for: src/common/Widget/Widget_MyLatestSvnCommits.class.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22
/**
3-
* Copyright (c) Enalean, 2013 - 2018. All Rights Reserved.
3+
* Copyright (c) Enalean, 2013 - Present. All Rights Reserved.
44
* Copyright (c) Xerox Corporation, Codendi 2001-2009.
55
*
66
* This file is a part of Tuleap.
@@ -80,7 +80,7 @@ public function getContent()
8080

8181
$html .= '<strong>' . $hp->purify($project->getPublicName()) . '</strong>';
8282
if (! $hide_now) {
83-
list($latest_revisions, $nb_revisions) = svn_get_revisions($project, 0, $this->_nb_svn_commits, '', $user->getUserName(), '', '', 0, false);
83+
list($latest_revisions, $nb_revisions) = svn_get_revisions($project, 0, $this->_nb_svn_commits, '', $user->getUserName(), '', [], 0, false);
8484
$revision_total += $nb_revisions;
8585
if (db_numrows($latest_revisions) > 0) {
8686
$i = 0;

Diff for: src/common/Widget/Widget_ProjectLatestSvnCommits.class.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public function getLatestRevisions()
6161
$pm = ProjectManager::instance();
6262
$project = $pm->getProject($this->group_id);
6363
if ($project && $this->canBeUsedByProject($project)) {
64-
list($this->latest_revisions,) = svn_get_revisions($project, 0, 5, '', '', '', '', 0, false);
64+
list($this->latest_revisions,) = svn_get_revisions($project, 0, 5, '', '', '', [], 0, false);
6565
}
6666
}
6767
return $this->latest_revisions;

Diff for: src/common/svn/SVN_LogFactory.class.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22
/**
3-
* Copyright (c) Enalean, 2012-2018. All Rights Reserved.
3+
* Copyright (c) Enalean, 2012-Present. All Rights Reserved.
44
*
55
* This file is a part of Tuleap.
66
*
@@ -138,7 +138,7 @@ public function getRawRevisionsAndCount($limit, PFUser $author)
138138
'',
139139
$author->getUserName(),
140140
'',
141-
'',
141+
[],
142142
0,
143143
false
144144
);

Diff for: src/www/svn/browse_revision.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
}
8282
}
8383

84-
$order_by = '';
84+
$order_by = [];
8585
if (isset($morder)) {
8686
if (user_isloggedin()) {
8787
if ($morder != user_get_preference('svn_commit_browse_order' . $group_id)) {
@@ -90,7 +90,7 @@
9090
}
9191

9292
if ($morder != '') {
93-
$order_by = ' ORDER BY ' . svn_utils_criteria_list_to_query($morder);
93+
$order_by = svn_utils_criteria_list_to_query($morder);
9494
}
9595
}
9696

Diff for: src/www/svn/svn_utils.php

+50-17
Original file line numberDiff line numberDiff line change
@@ -336,13 +336,30 @@ function svn_utils_add_sort_criteria($criteria_list, $order, $msort)
336336
return(join(',', $arr));
337337
}
338338

339-
// Transform criteria list to SQL query (+ means ascending
340-
// - is descending)
341-
function svn_utils_criteria_list_to_query($criteria_list)
339+
/**
340+
* @psalm-return array<array{order:"ASC"|"DESC", column: "revision"|"commit_id"|"description"|"date"|"whoid"}>
341+
*/
342+
function svn_utils_criteria_list_to_query(string $criteria_list): array
342343
{
343-
$criteria_list = str_replace('>', ' ASC', $criteria_list);
344-
$criteria_list = str_replace('<', ' DESC', $criteria_list);
345-
return $criteria_list;
344+
$order_list = [];
345+
346+
foreach (explode(',', $criteria_list) as $criteria) {
347+
if (preg_match('/^(?<column>[a-z]+)(?<order>[<>]?)$/', $criteria, $matches) === 1) {
348+
$column = $matches['column'];
349+
if (! in_array($column, ['revision', 'commit_id', 'description', 'date', 'whoid'], true)) {
350+
continue;
351+
}
352+
353+
$order = 'ASC';
354+
if ($matches['order'] === '<') {
355+
$order = 'DESC';
356+
}
357+
358+
$order_list[] = ['order' => $order, 'column' => $column];
359+
}
360+
}
361+
362+
return $order_list;
346363
}
347364

348365
// Transform criteria list to readable text statement
@@ -353,13 +370,15 @@ function svn_utils_criteria_list_to_text($criteria_list, $url)
353370
$morder = '';
354371
$arr = explode(',', $criteria_list);
355372

373+
$purifier = Codendi_HTMLPurifier::instance();
374+
356375
foreach ($arr as $crit) {
357376
$morder .= ($morder ? "," . $crit : $crit);
358377
$attr = str_replace('>', '', $crit);
359378
$attr = str_replace('<', '', $attr);
360379

361-
$arr_text[] = '<a href="' . $url . '&morder=' . $morder . '#results">' .
362-
svn_utils_field_get_label($attr) . '</a><img src="' . util_get_dir_image_theme() .
380+
$arr_text[] = '<a href="' . $url . '&morder=' . $purifier->purify(urlencode($morder)) . '#results">' .
381+
$purifier->purify(svn_utils_field_get_label($attr)) . '</a><img src="' . util_get_dir_image_theme() .
363382
((substr($crit, -1) == '<') ? 'dn' : 'up') .
364383
'_arrow.png" border="0">';
365384
}
@@ -783,7 +802,10 @@ function svn_utils_is_there_specific_permission($project_svnroot)
783802
return ! $specifics || $specifics != '';
784803
}
785804

786-
function svn_get_revisions(Project $project, $offset, $chunksz, $_rev_id = '', $_commiter = '', $_srch = '', $order_by = '', $pv = 0, $foundRows = true)
805+
/**
806+
* @psalm-param array<array{order:"ASC"|"DESC", column: "revision"|"commit_id"|"description"|"date"|"whoid"}> $order_by
807+
*/
808+
function svn_get_revisions(Project $project, $offset, $chunksz, $_rev_id = '', $_commiter = '', $_srch = '', array $order_by = [], $pv = 0, $foundRows = true)
787809
{
788810
global $_path;
789811

@@ -852,17 +874,28 @@ function svn_get_revisions(Project $project, $offset, $chunksz, $_rev_id = '', $
852874

853875
$where .= $commiter_str . $commit_str . $srch_str . $path_str;
854876

877+
$limit = '';
855878
if (! isset($pv) || ! $pv) {
856879
$limit = " LIMIT " . db_ei($offset) . "," . db_ei($chunksz);
857880
}
858881

859-
// SQLi Warning: no real possibility to escape $order_by here.
860-
// We rely on a proper filtering of user input by calling methods.
861-
if (! isset($order_by) || $order_by == '') {
862-
$order_by = " ORDER BY revision DESC ";
863-
}
864-
865-
$sql = $select . $from . $where . $group_by . $order_by . $limit;
882+
if (empty($order_by)) {
883+
$order_by_sql = " ORDER BY revision DESC ";
884+
} else {
885+
$order_by_sql = ' ORDER BY ';
886+
$order_by_sql .= implode(
887+
',',
888+
array_map(
889+
static function (array $order_by_row) {
890+
return $order_by_row['column'] . ' ' . $order_by_row['order'];
891+
},
892+
$order_by
893+
)
894+
);
895+
$order_by_sql .= ' ';
896+
}
897+
898+
$sql = $select . $from . $where . $group_by . $order_by_sql . $limit;
866899
//echo $sql."<br>\n";
867900
$result = db_query($sql);
868901

@@ -871,7 +904,7 @@ function svn_get_revisions(Project $project, $offset, $chunksz, $_rev_id = '', $
871904
if ($foundRows) {
872905
$sql1 = 'SELECT FOUND_ROWS() as nb';
873906
$result1 = db_query($sql1);
874-
if ($result1 && ! db_error($result1)) {
907+
if ($result1 && ! db_error()) {
875908
$row1 = db_fetch_array($result1);
876909
$totalrows = $row1['nb'];
877910
}

Diff for: tests/psalm/tuleap-baseline.xml

+5-1
Original file line numberDiff line numberDiff line change
@@ -78674,7 +78674,7 @@
7867478674
</UndefinedGlobalVariable>
7867578675
</file>
7867678676
<file src="src/www/svn/svn_utils.php">
78677-
<DeprecatedFunction occurrences="20">
78677+
<DeprecatedFunction occurrences="25">
7867878678
<code>html_build_select_box_from_arrays($userids, $usernames, $name, $checked, true, $text_100, false, '', false, '', CODENDI_PURIFIER_CONVERT_HTML)</code>
7867978679
<code>db_fetch_array($result)</code>
7868078680
<code>db_numrows($result)</code>
@@ -78695,6 +78695,10 @@
7869578695
<code>db_es(htmlspecialchars($_srch))</code>
7869678696
<code>db_ei($offset)</code>
7869778697
<code>db_ei($chunksz)</code>
78698+
<code>db_query($sql)</code>
78699+
<code>db_query($sql1)</code>
78700+
<code>db_fetch_array($sql1)</code>
78701+
<code>db_error()</code>
7869878702
</DeprecatedFunction>
7869978703
<MissingParamType occurrences="51">
7870078704
<code>$params</code>

0 commit comments

Comments
 (0)