CVE-2021-42584: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: A Stored Cross Site Scripting (XSS) issue exists in Convos-Chat before 6.32.
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: 5.4
Published: 2021-12-17T14:15Z
Repository: https://github.com/convos-chat/convos
Language: Other
Files changed: Changes, lib/Convos/Controller/Files.pm, t/data/js.svg, t/web-files.t
Commit date: 2021-10-14 06:51:26 +0900
Commit message: Fix vulnerability: Cannot upload svg with javascript #623

Diff: diff --git a/Changes b/Changes
index 19aa8315..cf6d6645 100644
--- a/Changes
+++ b/Changes
@@ -1,7 +1,10 @@
 Revision history for perl distribution Convos
 
+6.32 Not Released
+ - Fix vulnerability: Cannot upload svg with javascript #623
+
 6.31 2021-10-02T06:51:00+0900
  - Fix turning channel names with "-" into links
  - Have to build a different tag for arm64 (for now) #464
 
 6.30 2021-09-25T18:30:00+0900
diff --git a/lib/Convos/Controller/Files.pm b/lib/Convos/Controller/Files.pm
index 2da2a345..cfc042a3 100644
--- a/lib/Convos/Controller/Files.pm
+++ b/lib/Convos/Controller/Files.pm
@@ -28,10 +28,13 @@ sub upload {
   my $upload = $self->req->upload('file');
   my $err;
   return $self->reply->errors([[$err, '/file']], 400)
     if $err = !$upload ? 'No upload.' : !$upload->filename ? 'Unknown filename.' : '';
 
+  return $self->reply->errors([['SVG contains script.', '/file']], 400)
+    if $upload->asset->contains('<script') != -1;
+
   my %args = (filename => $upload->filename);
   $args{id}         = $self->param('id')         if defined $self->param('id');
   $args{write_only} = $self->param('write_only') if defined $self->param('write_only');
   return $self->files->save_p($upload->asset, \%args)
     ->then(sub { $self->render(openapi => {files => [shift]}) });
diff --git a/t/data/js.svg b/t/data/js.svg
new file mode 100644
index 00000000..cf4f33ca
--- /dev/null
+++ b/t/data/js.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+
+<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
+   <rect width="300" height="100" style="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)" />
+   <script type="text/javascript">
+      alert(document.cookie);
+   </script>
+</svg>
\ No newline at end of file
diff --git a/t/web-files.t b/t/web-files.t
index a6f43830..5d400457 100644
--- a/t/web-files.t
+++ b/t/web-files.t
@@ -109,10 +109,15 @@ subtest 'binary' => sub {
   $t->get_ok("/file/1/$fid")->header_is('Cache-Control', 'max-age=86400')
     ->header_is('Content-Disposition', 'attachment; filename="binary.bin"')
     ->header_is('Content-Type' => 'application/octet-stream');
 };
 
+subtest 'svg with javasript' => sub {
+  $t->post_ok('/api/file', form => {file => {file => 't/data/js.svg'}})->status_is(400)
+    ->json_is('/errors/0/message', 'SVG contains script.');
+};
+
 subtest 'write_only' => sub {
   $t->post_ok('/api/file',
     form => {id => 'irc-localhost-key', file => {file => $asset}, write_only => true})
     ->status_is(200);
   $fid = $t->tx->res->json('/files/0/id');

Fixed code: 6.32 Not Released
 - Fix vulnerability: Cannot upload svg with javascript #623

  return $self->reply->errors([['SVG contains script.', '/file']], 400)
    if $upload->asset->contains('<script') != -1;

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">

<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
   <rect width="300" height="100" style="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)" />
   <script type="text/javascript">
      alert(document.cookie);
   </script>
</svg>
subtest 'svg with javasript' => sub {
  $t->post_ok('/api/file', form => {file => {file => 't/data/js.svg'}})->status_is(400)
    ->json_is('/errors/0/message', 'SVG contains script.');
};