Fix reflected XSS in Webmin status messages

* Note: Escape the /webmin/ message parameter, strip restart redirect HTML to plain text, and harden filter_javascript().
This commit is contained in:
Ilia Ross
2026-05-17 14:32:10 +02:00
parent e60d005ab0
commit 2d01675139
4 changed files with 56 additions and 4 deletions

View File

@@ -0,0 +1,39 @@
#!/usr/bin/perl
# Tests for web-lib-funcs.pl filter_javascript.
use strict;
use warnings;
use Test::More;
use File::Basename qw(dirname);
use File::Spec;
my $script = File::Spec->rel2abs(
File::Spec->catfile(dirname(__FILE__), '..', 'web-lib-funcs.pl'));
require $script;
is(
main::filter_javascript('<video/onloadstart=alert(1) src=1>'),
'<video/xonloadstart=alert(1) src=1>',
'slash-separated HTML5 event handler is disabled',
);
is(
main::filter_javascript('<img src=x onload=alert(1)>'),
'<img src=x xonload=alert(1)>',
'classic event handler is disabled',
);
is(
main::filter_javascript('<div onwheel = alert(1) onpointerdown=alert(2)>'),
'<div xonwheel = alert(1) xonpointerdown=alert(2)>',
'multiple modern event handlers are disabled',
);
is(
main::filter_javascript(
'<a data-onload="safe" href="javascript:alert(1)">link</a>'),
'<a data-onload="safe" href="xjavascript:alert(1)">link</a>',
'non-handler attributes are preserved while script URIs are disabled',
);
done_testing();

View File

@@ -10179,10 +10179,15 @@ sub filter_javascript
my ($rv, $type) = @_;
if (!$type || $type eq 'html') {
$rv =~ s/<\s*script[^>]*>([\000-\377]*?)<\s*\/script\s*>//gi;
$rv =~ s/(on(Abort|BeforeUnload|Blur|Change|Click|ContextMenu|Copy|Cut|DblClick|Drag|DragEnd|DragEnter|DragLeave|DragOver|DragStart|DragDrop|Drop|Error|Focus|FocusIn|FocusOut|HashChange|Input|Invalid|KeyDown|KeyPress|KeyUp|Load|MouseDown|MouseEnter|MouseLeave|MouseMove|MouseOut|MouseOver|MouseUp|Move|Paste|PageShow|PageHide|Reset|Resize|Scroll|Search|Select|Submit|Toggle|Unload)=)/x$1/gi;
$rv =~ s/(javascript(:|&colon;|&#58;|&#x3A;))/x$1/gi;
$rv =~ s/(vbscript(:|&colon;|&#58;|&#x3A;))/x$1/gi;
$rv =~ s/<([^>]*\s|)(on\S+=)(.*)>/<$1x$2$3>/gi;
my $event_attr = qr/on[a-z][a-z0-9_:-]*\s*=/i;
my $event_attrs;
do {
$event_attrs = 0;
$event_attrs += $rv =~ s{(<[^>]*?)([\s/]+)($event_attr)}{$1$2x$3}g;
$event_attrs += $rv =~ s{(<)($event_attr)}{$1x$2}g;
} while ($event_attrs);
}
if ($type eq 'pdf') {
$rv =~ s/([\n]*)<<[\n((?:.*?|\n)*?)][\w\s\/]+[\n((?:.*?|\n)*?)][\w\s\/]+JavaScript[\w\s\/]*[\n((?:.*?|\n)*?)][\w\s\/]+\s.*?>>[\n]*/$1/gmsi;

View File

@@ -83,7 +83,7 @@ for(my $i=0; $i<@wlinks; $i++) {
}
}
print &ui_alert_box(&filter_javascript($in{'message'}), 'success', undef, 1,
print &ui_alert_box(&html_escape($in{'message'}), 'success', undef, 1,
&html_escape($in{'title'})) if ($in{'message'});
&icons_table(\@wlinks, \@wtitles, \@wicons);

View File

@@ -1962,7 +1962,15 @@ my ($title, $msg) = @_;
if (!$gconfig{'restart_async'}) {
&restart_miniserv();
my $msg_redir = "";
$msg_redir = "?title=".&urlize($title)."&message=".&urlize($msg) if $msg;
if ($msg) {
$title = defined($title) ? &html_strip($title, " ") : "";
$msg = &html_strip($msg, " ");
$title =~ s/\s+/ /g;
$title =~ s/^\s+|\s+$//g;
$msg =~ s/\s+/ /g;
$msg =~ s/^\s+|\s+$//g;
$msg_redir = "?title=".&urlize($title)."&message=".&urlize($msg);
}
&redirect($msg_redir);
return;
}