CVE-2022-21650 (Perl, published 2022-01-04T21:15Z)
Description: value: Convos is an open source multi-user chat that runs in a web browser. You can't use SVG extension in Convos' chat window, but you can upload a file with an .html extension. By uploading an SVG file with an html extension the upload filter can be bypassed. This causes Stored XSS. Also, after uploading a file the XSS attack is triggered upon a user viewing the file. Through this vulnerability, an attacker is capable to execute malicious scripts. Users are advised to update as soon as possible.
Severity: LOW (CVSS v3 base score: 5.4)
Weakness: CWE-79 \u2014 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
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.
Repository: https://github.com/convos-chat/convos
Files changed:
Changes
lib/Convos/Controller/Files.pm
lib/Convos/Core/User/File.pm
t/data/markup.html
t/data/markup.xhtml
t/web-files.t
Commit message: Add more rules for files with Content-Disposition


Vulnerable code:
  my $file = $self->_file(id => $self->stash('fid'), user => $user, types => $self->app->types);
  state $type_can_be_embedded = qr{^(application/javascript|application/json|image|text)};
      unless $ct =~ m!$type_can_be_embedded!;
has saved      => sub { Mojo::Date->new->to_datetime };
has types      => sub { Mojolicious::Types->new };
has user       => undef;
subtest 'binary' => sub {
  is @ids, 14, 'got all files';

Fixed code:
 - Add more rules for files with Content-Disposition
   Contributor: Pocas
   Reference: https://huntr.dev/bounties/ae424798-de01-4972-b73b-2db674f82368/
  my $file = $self->_file(id => $self->stash('fid'), user => $user);
  state $type_can_be_embedded
    = qr{^(application/javascript|application/(json|xhtml|xml)|image|text)};
  state $type_can_be_viewed = qr{^(application/javascript|audio/|image/|text/plain|video/)};
      unless $ct =~ m!$type_can_be_viewed!;
has saved => sub { Mojo::Date->new->to_datetime };
has types => sub { state $types = Mojolicious::Types->new->type(xhtml => 'application/xhtml+xml') };
has user  => undef;
<!DOCTYPE html>
<html></html>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"></html>
subtest 'attachment' => sub {
  note 'binary';

  note 'html';
  $t->post_ok('/api/files', form => {file => {file => 't/data/markup.html'}})->status_is(200);
  $fid = $t->tx->res->json('/files/0/id');
  $t->get_ok("/file/1/$fid.html")
    ->header_is('Content-Disposition', 'attachment; filename="markup.html"')
    ->header_is('Content-Type' => 'text/html;charset=UTF-8')->content_like(qr{^<!DOCTYPE html>});
  $t->get_ok("/file/1/$fid")->header_is('Content-Disposition', undef)
    ->header_is('Content-Type' => 'text/html;charset=UTF-8')->text_is('h1', 'markup.html');
  like $t->tx->res->dom->at('div.le-paste'), qr{&lt;!DOCTYPE}, 'embedded and escaped html';

  note 'xhtml';
  $t->post_ok('/api/files', form => {file => {file => 't/data/markup.xhtml'}})->status_is(200);
  $fid = $t->tx->res->json('/files/0/id');
  $t->get_ok("/file/1/$fid.xhtml")
    ->header_is('Content-Disposition', 'attachment; filename="markup.xhtml"')
    ->header_is('Content-Type' => 'application/xhtml+xml')
    ->content_like(qr{^<!DOCTYPE html PUBLIC});
  $t->get_ok("/file/1/$fid")->header_is('Content-Disposition', undef)
    ->header_is('Content-Type' => 'text/html;charset=UTF-8')->text_is('h1', 'markup.xhtml');
  like $t->tx->res->dom->at('div.le-paste'), qr{&lt;!DOCTYPE html PUBLIC},
    'embedded and escaped xhtml';
  is @ids, 16, 'got all files';