From c87712ef4eeaab0cca2d1dbd90f49dea4c8b81cf Mon Sep 17 00:00:00 2001 From: Ilia Ross Date: Tue, 10 Feb 2026 13:21:56 +0200 Subject: [PATCH] Fix to use quotemeta to prevent shell injection in usermin module Ref.: a8417099-d3bc-468a (VULN-004) --- usermin/change_bind.cgi | 6 +++--- usermin/clone_mod.cgi | 5 +++-- usermin/export_mod.cgi | 3 +-- usermin/newkey.cgi | 9 +++++---- usermin/upgrade.cgi | 15 +++++++++------ usermin/usermin-lib.pl | 25 +++++++++++++++---------- 6 files changed, 36 insertions(+), 27 deletions(-) diff --git a/usermin/change_bind.cgi b/usermin/change_bind.cgi index b397107f4..5332e011e 100755 --- a/usermin/change_bind.cgi +++ b/usermin/change_bind.cgi @@ -46,7 +46,8 @@ if ($in{'ipv6'}) { @newports = &unique(grep { &indexof($_, @oldports) < 0 } @ports); if (&has_command("lsof")) { foreach my $p (@newports) { - $out = &backquote_command("lsof -t -i tcp:$p 2>/dev/null"); + my $qp = quotemeta($p); + $out = &backquote_command("lsof -t -i tcp:$qp 2>/dev/null"); if ($out =~ /\d+/) { &error(&text('bind_elsof', $p)); } @@ -111,7 +112,7 @@ foreach my $mod ("firewall", "firewalld") { $ENV{'WEBMIN_CONFIG'} = $config_directory; &system_logged( &module_root_directory($mod)."/open-ports.pl ". - join(" ", map { $_.":".($_+10) } @newports). + join(" ", map { quotemeta($_).":".quotemeta($_+10) } @newports). " >/dev/null 2>&1"); &reset_environment(); } @@ -121,4 +122,3 @@ foreach my $mod ("firewall", "firewalld") { &webmin_log("bind", undef, undef, \%in); &redirect(""); - diff --git a/usermin/clone_mod.cgi b/usermin/clone_mod.cgi index bf8aace8d..e225426ae 100755 --- a/usermin/clone_mod.cgi +++ b/usermin/clone_mod.cgi @@ -24,7 +24,9 @@ do { # Copy the config directory mkdir("$config{'usermin_dir'}/$dst", 0755); -$out = &backquote_logged("( (cd $config{'usermin_dir'}/$src ; tar cf - .) | (cd $config{'usermin_dir'}/$dst ; tar xpf -) ) 2>&1"); +$qsrc = quotemeta($src); +$qdst = quotemeta($dst); +$out = &backquote_logged("( (cd $config{'usermin_dir'}/$qsrc ; tar cf - .) | (cd $config{'usermin_dir'}/$qdst ; tar xpf -) ) 2>&1"); if ($?) { &error(&text('clone_ecopy', $out)); } @@ -51,4 +53,3 @@ if ($in{'cat'} ne '*') { 'dst' => $dst, 'dstdesc' => $in{'desc'} }); &redirect(""); - diff --git a/usermin/export_mod.cgi b/usermin/export_mod.cgi index 01805a3e4..3e56d1124 100755 --- a/usermin/export_mod.cgi +++ b/usermin/export_mod.cgi @@ -19,7 +19,7 @@ local %miniserv; chdir($miniserv{'root'}); $cmd = "tar chf -"; foreach $m (@mods) { - $cmd .= " $m"; + $cmd .= " ".quotemeta($m); } $cmd .= " | gzip -c >".quotemeta($temp); $out = &backquote_logged("($cmd) 2>&1 ".quotemeta($outtemp)." 2>&1", 0); +$qdays = quotemeta($in{'days'}); +&open_execute_command(CA, "$cmd req -newkey rsa:$size -x509 -nodes -out ".quotemeta($ctemp)." -keyout ".quotemeta($ktemp)." -days $qdays >".quotemeta($outtemp)." 2>&1", 0); print CA ($in{'countryName'} || "."),"\n"; print CA ($in{'stateOrProvinceName'} || "."),"\n"; print CA ($in{'cityName'} || "."),"\n"; @@ -51,8 +52,9 @@ if (!-r $ctemp || !-r $ktemp || $?) { &ui_print_footer("", $text{'index_return'}); exit; } +$qnewfile = quotemeta($in{'newfile'}); &lock_file($in{'newfile'}); -&execute_command("cat ".quotemeta($ctemp)." ".quotemeta($ktemp)." 2>&1 >'$in{'newfile'}'", undef, \$catout); +&execute_command("cat ".quotemeta($ctemp)." ".quotemeta($ktemp)." 2>&1 >$qnewfile", undef, \$catout); &unlink_file($ctemp); &unlink_file($ktemp); if ($catout || $?) { @@ -65,7 +67,7 @@ if ($catout || $?) { &unlock_file($in{'newfile'}); print "

$text{'newkey_ok'}
\n"; -$key = `cat '$in{'newfile'}'`; +$key = `cat $qnewfile`; print "

$key
"; &ui_print_footer("", $text{'index_return'}); @@ -80,4 +82,3 @@ if ($in{'usenew'}) { &restart_usermin_miniserv(); &webmin_log("newkey", undef, undef, \%in); } - diff --git a/usermin/upgrade.cgi b/usermin/upgrade.cgi index b44a1b6fc..fa6cbafac 100755 --- a/usermin/upgrade.cgi +++ b/usermin/upgrade.cgi @@ -124,6 +124,7 @@ elsif ($in{'source'} == 5) { $error && &inst_error($error); } $qfile = quotemeta($file); +$qindir = quotemeta($indir) if (defined($indir)); # gunzip the file if needed open(FILE, "<$file"); @@ -134,7 +135,8 @@ if ($two eq "\037\213") { &inst_error($text{'upgrade_egunzip'}); } $newfile = &transname(); - $out = `gunzip -c $file 2>&1 >$newfile`; + $qnewfile = quotemeta($newfile); + $out = `gunzip -c $qfile 2>&1 >$qnewfile`; if ($?) { unlink($newfile); &inst_error(&text('upgrade_egzip', "$out")); @@ -199,7 +201,7 @@ elsif ($in{'mode'} eq 'deb') { } else { # Check if it is a usermin tarfile - open(TAR, "tar tf $file 2>&1 |"); + open(TAR, "tar tf $qfile 2>&1 |"); while() { if (/^usermin-([0-9\.]+)\//) { $version = $1; @@ -235,7 +237,8 @@ else { # Installing .. extract it in /usr/local and run setup.sh local $root = "/usr/local"; mkdir($root, 0755); - $out = `cd $root ; tar xf $file 2>&1 >/dev/null`; + $qroot = quotemeta($root); + $out = `cd $qroot ; tar xf $qfile 2>&1 >/dev/null`; if ($?) { &inst_error(&text('upgrade_euntar', "$out")); } @@ -268,7 +271,8 @@ else { } # Extract it next to the current directory and run setup.sh - $out = `cd $extract ; tar xf $file 2>&1 >/dev/null`; + $qextract = quotemeta($extract); + $out = `cd $qextract ; tar xf $qfile 2>&1 >/dev/null`; if ($?) { &inst_error(&text('upgrade_euntar', "$out")); } @@ -276,7 +280,7 @@ else { $ENV{'config_dir'} = $config{'usermin_dir'}; $ENV{'webmin_upgrade'} = 1; $ENV{'autothird'} = 1; - $setup = $indir ? "./setup.sh '$indir'" : "./setup.sh"; + $setup = $indir ? "./setup.sh $qindir" : "./setup.sh"; if ($in{'delete'}) { $ENV{'deletedold'} = 1; $cmd = "(cd $extract/usermin-$version && $setup && rm -rf \"$miniserv{'root'}\")"; @@ -340,4 +344,3 @@ unlink($file) if ($need_unlink); &error($_[0]); exit; } - diff --git a/usermin/usermin-lib.pl b/usermin/usermin-lib.pl index bd1a63792..151fb60a8 100755 --- a/usermin/usermin-lib.pl +++ b/usermin/usermin-lib.pl @@ -420,6 +420,7 @@ if (&is_readonly_mode()) { open(MFILE, "<".$file); read(MFILE, $two, 2); close(MFILE); +local $qfile = quotemeta($file); if ($two eq "\037\235") { if (!&has_command("uncompress")) { unlink($file) if ($need_unlink); @@ -427,13 +428,15 @@ if ($two eq "\037\235") { } local $temp = $file =~ /\/([^\/]+)\.Z/i ? &transname("$1") : &transname(); - local $out = `uncompress -c "$file" 2>&1 >$temp`; + local $qtemp = quotemeta($temp); + local $out = `uncompress -c $qfile 2>&1 >$qtemp`; unlink($file) if ($need_unlink); if ($?) { unlink($temp); return &text('install_ecomp2', $out); } $file = $temp; + $qfile = quotemeta($file); $need_unlink = 1; } elsif ($two eq "\037\213") { @@ -443,13 +446,15 @@ elsif ($two eq "\037\213") { } local $temp = $file =~ /\/([^\/]+)\.gz/i ? &transname("$1") : &transname(); - local $out = `gunzip -c "$file" 2>&1 >$temp`; + local $qtemp = quotemeta($temp); + local $out = `gunzip -c $qfile 2>&1 >$qtemp`; unlink($file) if ($need_unlink); if ($?) { unlink($temp); return &text('install_egzip2', $out); } $file = $temp; + $qfile = quotemeta($file); $need_unlink = 1; } @@ -462,7 +467,7 @@ open(TYPE, "<../install-type"); chop($type = ); close(TYPE); if ($type eq 'rpm' && $file =~ /\.rpm$/i && - ($out = `rpm -qp $file 2>/dev/null`)) { + ($out = `rpm -qp $qfile 2>/dev/null`)) { # Looks like an RPM of some kind, hopefully an RPM usermin module # or theme local ($out, %minfo, %tinfo); @@ -471,7 +476,7 @@ if ($type eq 'rpm' && $file =~ /\.rpm$/i && return $text{'install_erpm'}; } $redirect_to = $name = $2; - $out = &backquote_logged("rpm -Uv \"$file\" 2>&1"); + $out = &backquote_logged("rpm -Uv $qfile 2>&1"); if ($?) { unlink($file) if ($need_unlink); return &text('install_eirpm', "$out"); @@ -515,7 +520,7 @@ else { # Check if this is a valid module (a tar file of multiple module or # theme directories) local (%mods, %hasfile); - local $tar = `tar tf "$file" 2>&1`; + local $tar = `tar tf $qfile 2>&1`; if ($?) { unlink($file) if ($need_unlink); return &text('install_etar', $tar); @@ -548,7 +553,7 @@ else { next if (!$hasfile{$m,"module.info"}); push(@realmods, $m); local %minfo; - system("cd $tmpdir ; tar xf \"$file\" $m/module.info ./$m/module.info >/dev/null 2>&1"); + system("cd $tmpdir ; tar xf $qfile $m/module.info ./$m/module.info >/dev/null 2>&1"); if (!&read_file("$tmpdir/$m/module.info", \%minfo)) { $err = &text('install_einfo', "$m"); } @@ -600,7 +605,7 @@ else { } # Extract all the modules and update perl path and ownership - local $out = `cd $miniserv{'root'} ; tar xf "$file" 2>&1 >/dev/null`; + local $out = `cd $miniserv{'root'} ; tar xf $qfile 2>&1 >/dev/null`; if ($?) { unlink($file) if ($need_unlink); return &text('install_eextract', $out); @@ -800,6 +805,7 @@ sub delete_usermin_module { local $m = $_[0]; return undef if (!$m); +local $qm = quotemeta($m); local %minfo = &get_usermin_module_info($m); %minfo = &get_usermin_theme_info($m) if (!%minfo); return undef if (!%minfo); @@ -832,7 +838,7 @@ else { @lst = stat("$miniserv{'root'}/$l"); if (-l "$miniserv{'root'}/$l" && $lst[1] == $mst[1]) { &unlink_logged("$miniserv{'root'}/$l"); - &system_logged("rm -rf $config{'usermin_dir'}/$l"); + &system_logged("rm -rf ".quotemeta("$config{'usermin_dir'}/$l")); push(@clones, $l); } } @@ -848,7 +854,7 @@ else { "$mdir", $size); if ($type eq 'rpm') { # This module was installed from an RPM .. rpm -e it - &system_logged("rpm -e ubm-$m"); + &system_logged("rpm -e ubm-$qm"); } else { # Module was installed from a .wbm file .. just rm it @@ -1071,4 +1077,3 @@ chmod(0755, $wrapper); } 1; -