CVE-2023-0606: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Cross-site Scripting (XSS) - Reflected in GitHub repository ampache/ampache prior to 5.5.7.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 6.1
Published: 2023-02-01T01:15Z
Repository: https://github.com/ampache/ampache
Language: PHP
Files changed: public/templates/show_alphabet_form.inc.php, public/templates/show_form_browse.inc.php, public/templates/show_form_highest.inc.php, public/templates/show_form_mashup.inc.php, public/templates/show_form_newest.inc.php, public/templates/show_form_popular.inc.php, public/templates/show_form_recent.inc.php, public/templates/show_form_userflag.inc.php, public/templates/show_graphs.inc.php, src/Module/Application/ApplicationRunner.php, src/Module/Application/Playback/PlayAction.php
Commit date: 2023-01-04 13:43:27 +1000
Commit message: sanitize special characters in action gets

Diff: diff --git a/public/templates/show_alphabet_form.inc.php b/public/templates/show_alphabet_form.inc.php
index 18ff0c086c..49b551c9bb 100644
--- a/public/templates/show_alphabet_form.inc.php
+++ b/public/templates/show_alphabet_form.inc.php
@@ -24,7 +24,7 @@ use Ampache\Config\AmpConfig;
 
 ?>
 <form style="display:inline;" name="f" method="get" action="<?php echo AmpConfig::get('web_path') . "/$action"; ?>" enctype="multipart/form-data">
     <label for="match" accesskey="S"><?php echo $text; ?></label>
     <input type="text" id="match" name="match" value="<?php echo $match; ?>" />
-    <input type="hidden" name="action" value="<?php echo scrub_out(filter_input(INPUT_GET, 'action', FILTER_SANITIZE_URL)); ?>">
+    <input type="hidden" name="action" value="<?php echo scrub_out(filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS)); ?>">
 </form>
diff --git a/public/templates/show_form_browse.inc.php b/public/templates/show_form_browse.inc.php
index 40dc109434..44b76b795e 100644
--- a/public/templates/show_form_browse.inc.php
+++ b/public/templates/show_form_browse.inc.php
@@ -26,11 +26,11 @@ use Ampache\Repository\VideoRepositoryInterface;
 
 global $dic;
 
 $videoRepository = $dic->get(VideoRepositoryInterface::class);
 $web_path        = AmpConfig::get('web_path');
-$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
+$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS);
 $showAlbumArtist = AmpConfig::get('show_album_artist');
 $showArtist      = AmpConfig::get('show_artist');
 $albumString     = (AmpConfig::get('album_group'))
     ? 'album'
     : 'album_disk'; ?>
diff --git a/public/templates/show_form_highest.inc.php b/public/templates/show_form_highest.inc.php
index da6aa2691f..d4dc9f6b32 100644
--- a/public/templates/show_form_highest.inc.php
+++ b/public/templates/show_form_highest.inc.php
@@ -26,11 +26,11 @@ use Ampache\Repository\VideoRepositoryInterface;
 
 global $dic;
 
 $videoRepository = $dic->get(VideoRepositoryInterface::class);
 $web_path        = AmpConfig::get('web_path');
-$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
+$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS);
 $showAlbumArtist = AmpConfig::get('show_album_artist');
 $showArtist      = AmpConfig::get('show_artist');
 $albumString     = (AmpConfig::get('album_group'))
     ? 'album'
     : 'album_disk'; ?>
diff --git a/public/templates/show_form_mashup.inc.php b/public/templates/show_form_mashup.inc.php
index 60114907f3..cbdf3b4233 100644
--- a/public/templates/show_form_mashup.inc.php
+++ b/public/templates/show_form_mashup.inc.php
@@ -7,11 +7,11 @@ use Ampache\Repository\VideoRepositoryInterface;
 
 global $dic;
 
 $videoRepository = $dic->get(VideoRepositoryInterface::class);
 $web_path        = AmpConfig::get('web_path');
-$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
+$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS);
 $albumString     = (AmpConfig::get('album_group'))
     ? 'album'
     : 'album_disk'; ?>
 
 <h3 class="box-title"><?php echo T_('Dashboards'); ?></h3>
diff --git a/public/templates/show_form_newest.inc.php b/public/templates/show_form_newest.inc.php
index cdedddb2a9..fc97133809 100644
--- a/public/templates/show_form_newest.inc.php
+++ b/public/templates/show_form_newest.inc.php
@@ -26,11 +26,11 @@ use Ampache\Repository\VideoRepositoryInterface;
 
 global $dic;
 
 $videoRepository = $dic->get(VideoRepositoryInterface::class);
 $web_path        = AmpConfig::get('web_path');
-$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
+$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS);
 $showAlbumArtist = AmpConfig::get('show_album_artist');
 $showArtist      = AmpConfig::get('show_artist');
 $albumString     = (AmpConfig::get('album_group'))
     ? 'album'
     : 'album_disk'; ?>
diff --git a/public/templates/show_form_popular.inc.php b/public/templates/show_form_popular.inc.php
index 67c8553151..050f546c9d 100644
--- a/public/templates/show_form_popular.inc.php
+++ b/public/templates/show_form_popular.inc.php
@@ -26,11 +26,11 @@ use Ampache\Repository\VideoRepositoryInterface;
 
 global $dic;
 
 $videoRepository = $dic->get(VideoRepositoryInterface::class);
 $web_path        = AmpConfig::get('web_path');
-$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
+$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS);
 $showAlbumArtist = AmpConfig::get('show_album_artist');
 $showArtist      = AmpConfig::get('show_artist');
 $albumString     = (AmpConfig::get('album_group'))
     ? 'album'
     : 'album_disk'; ?>
diff --git a/public/templates/show_form_recent.inc.php b/public/templates/show_form_recent.inc.php
index 1544ac12b8..c849e9951d 100644
--- a/public/templates/show_form_recent.inc.php
+++ b/public/templates/show_form_recent.inc.php
@@ -26,11 +26,11 @@ use Ampache\Repository\VideoRepositoryInterface;
 
 global $dic;
 
 $videoRepository = $dic->get(VideoRepositoryInterface::class);
 $web_path        = AmpConfig::get('web_path');
-$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
+$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS);
 $showAlbumArtist = AmpConfig::get('show_album_artist');
 $showArtist      = AmpConfig::get('show_artist');
 $albumString     = (AmpConfig::get('album_group'))
     ? 'album'
     : 'album_disk'; ?>
diff --git a/public/templates/show_form_userflag.inc.php b/public/templates/show_form_userflag.inc.php
index e528e23361..ed3ebe07b4 100644
--- a/public/templates/show_form_userflag.inc.php
+++ b/public/templates/show_form_userflag.inc.php
@@ -26,11 +26,11 @@ use Ampache\Repository\VideoRepositoryInterface;
 
 global $dic;
 
 $videoRepository = $dic->get(VideoRepositoryInterface::class);
 $web_path        = AmpConfig::get('web_path');
-$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
+$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS);
 $showAlbumArtist = AmpConfig::get('show_album_artist');
 $showArtist      = AmpConfig::get('show_artist');
 $albumString     = (AmpConfig::get('album_group'))
     ? 'album'
     : 'album_disk'; ?>
diff --git a/public/templates/show_graphs.inc.php b/public/templates/show_graphs.inc.php
index 7620ff3fca..19787058c7 100644
--- a/public/templates/show_graphs.inc.php
+++ b/public/templates/show_graphs.inc.php
@@ -79,11 +79,11 @@ if (AmpConfig::get('geolocation')) { ?>
         </dd>
     </dl>
     <input type="hidden" name="user_id" value="<?php echo $user_id; ?>" />
     <input type="hidden" name="object_type" value="<?php echo $object_type; ?>" />
     <input type="hidden" name="object_id" value="<?php echo $object_id; ?>" />
-    <input type="hidden" name="action" value="<?php echo filter_input(INPUT_GET, 'action', FILTER_SANITIZE_URL); ?>" />
+    <input type="hidden" name="action" value="<?php echo filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS); ?>" />
     <input type="hidden" name="type" value="<?php echo $type; ?>" />
 </form>
 <script>
     $('#start_date').datetimepicker({
         format: 'Y-m-d H:i',
diff --git a/src/Module/Application/ApplicationRunner.php b/src/Module/Application/ApplicationRunner.php
index a579789701..98caf03d78 100644
--- a/src/Module/Application/ApplicationRunner.php
+++ b/src/Module/Application/ApplicationRunner.php
@@ -66,11 +66,11 @@ final class ApplicationRunner
     public function run(
         ServerRequestInterface $request,
         array $action_list,
         string $default_action
     ): void {
-        $action_name = $request->getParsedBody()['action'] ?? $request->getQueryParams()['action'] ?? '';
+        $action_name = htmlspecialchars($request->getParsedBody()['action'] ?? $request->getQueryParams()['action'] ?? '');
 
         if (array_key_exists($action_name, $action_list) === false) {
             $action_name = $default_action;
         }
 
diff --git a/src/Module/Application/Playback/PlayAction.php b/src/Module/Application/Playback/PlayAction.php
index 29f8e9b767..f98c9dde51 100644
--- a/src/Module/Application/Playback/PlayAction.php
+++ b/src/Module/Application/Playback/PlayAction.php
@@ -417,11 +417,11 @@ final class PlayAction implements ApplicationActionInterface
 
                     return null;
                 }
 
                 // play the song instead of going through all the crap
-                header('Location: ' . $media->play_url('', $player, false, $user->id, $user->streamtoken));
+                header('Location: ' . $media->play_url('', $player, false, $user->id, $user->streamtoken), true, 303);
 
                 return null;
             }
             debug_event('play/index', "Error: DEMOCRATIC song could not be found", 3);
             header('HTTP/1.1 404 File not found');
@@ -457,11 +457,11 @@ final class PlayAction implements ApplicationActionInterface
 
                         return null;
                     }
 
                     // play the song instead of going through all the crap
-                    header('Location: ' . $media->play_url('', $player, false, $user->id, $user->streamtoken));
+                    header('Location: ' . $media->play_url('', $player, false, $user->id, $user->streamtoken), true, 303);
 
                     return null;
                 }
                 debug_event('play/index', "Error: RANDOM song could not be found", 3);
                 header('HTTP/1.1 404 File not found');
@@ -531,11 +531,11 @@ final class PlayAction implements ApplicationActionInterface
         } else {
             // No catalog, must be song preview or something like that => just redirect to file
             if ($type == "song_preview" && $media instanceof Song_Preview) {
                 $media->stream();
             } else {
-                header('Location: ' . $media->file);
+                header('Location: ' . $media->file, true, 303);
 
                 return null;
             }
         }
         // load the cache file or the local file

Vulnerable code:     <input type="hidden" name="action" value="<?php echo scrub_out(filter_input(INPUT_GET, 'action', FILTER_SANITIZE_URL)); ?>">
$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES);
    <input type="hidden" name="action" value="<?php echo filter_input(INPUT_GET, 'action', FILTER_SANITIZE_URL); ?>" />
        $action_name = $request->getParsedBody()['action'] ?? $request->getQueryParams()['action'] ?? '';
                header('Location: ' . $media->play_url('', $player, false, $user->id, $user->streamtoken));
                    header('Location: ' . $media->play_url('', $player, false, $user->id, $user->streamtoken));
                header('Location: ' . $media->file);
Fixed code:     <input type="hidden" name="action" value="<?php echo scrub_out(filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS)); ?>">
$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS);
$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS);
$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS);
$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS);
$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS);
$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS);
$filter_str      = (string) filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS);
    <input type="hidden" name="action" value="<?php echo filter_input(INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS); ?>" />
        $action_name = htmlspecialchars($request->getParsedBody()['action'] ?? $request->getQueryParams()['action'] ?? '');
                header('Location: ' . $media->play_url('', $player, false, $user->id, $user->streamtoken), true, 303);
                    header('Location: ' . $media->play_url('', $player, false, $user->id, $user->streamtoken), true, 303);
                header('Location: ' . $media->file, true, 303);