User-level blocking, and page for clearing blocks

This commit is contained in:
Jamie Cameron
2007-06-20 00:43:04 +00:00
parent f55715fb72
commit 196db09190
10 changed files with 197 additions and 21 deletions

View File

@@ -283,6 +283,9 @@ if ($config{'debuglog'}) {
print DEBUG "miniserv.pl starting ..\n";
}
# Write out (empty) blocked hosts file
&write_blocked_file();
# Re-direct STDERR to a log file
if ($config{'errorlog'} ne '-') {
open(STDERR, ">>$config{'errorlog'}") || die "failed to open $config{'errorlog'} : $!";
@@ -547,19 +550,43 @@ while(1) {
# run the unblocking procedure to check if enough time has passed to
# unblock hosts that heve been blocked because of password failures
$unblocked = 0;
if ($config{'blockhost_failures'}) {
$i = 0;
while ($i <= $#deny) {
if ($blockhosttime{$deny[$i]} && $config{'blockhost_time'} != 0 &&
($time_now - $blockhosttime{$deny[$i]}) >= $config{'blockhost_time'}) {
if ($blockhosttime{$deny[$i]} &&
$config{'blockhost_time'} != 0 &&
($time_now - $blockhosttime{$deny[$i]}) >=
$config{'blockhost_time'}) {
# the host can be unblocked now
$hostfail{$deny[$i]} = 0;
splice(@deny, $i, 1);
$unblocked = 1;
}
$i++;
}
}
# Do the same for blocked users
if ($config{'blockuser_failures'}) {
$i = 0;
while ($i <= $#deny) {
if ($blockusertime{$deny[$i]} &&
$config{'blockuser_time'} != 0 &&
($time_now - $blockusertime{$deny[$i]}) >=
$config{'blockuser_time'}) {
# the user can be unblocked now
$userfail{$deny[$i]} = 0;
splice(@denyusers, $i, 1);
$unblocked = 1;
}
$i++;
}
}
if ($unblocked) {
&write_blocked_file();
}
if ($config{'session'} && (++$remove_session_count%50) == 0) {
# Remove sessions with more than 7 days of inactivity,
local $s;
@@ -742,32 +769,61 @@ while(1) {
if ($3) {
# login OK.. no delay
print $outfd "0 0\n";
$wasblocked = $hostfail{$2} ||
$userfail{$1};
$hostfail{$2} = 0;
$userfail{$1} = 0;
if ($wasblocked) {
&write_blocked_file();
}
}
else {
# login failed..
$hostfail{$2}++;
# add the host to the block list if necessary
$userfail{$1}++;
$blocked = 0;
# add the host to the block list,
# if configured
if ($config{'blockhost_failures'} &&
$hostfail{$2} >= $config{'blockhost_failures'}) {
push(@deny, $2);
$hostfail{$2} >=
$config{'blockhost_failures'}) {
push(@deny, $2);
$blockhosttime{$2} = $time_now;
$blocked = 1;
if ($use_syslog) {
local $logtext = "Security alert: Host $2 ".
"blocked after $config{'blockhost_failures'} ".
"failed logins for user $1";
syslog("crit", "%s", $logtext);
local $logtext = "Security alert: Host $2 blocked after $config{'blockhost_failures'} failed logins for user $1";
syslog("crit", "%s",
$logtext);
}
}
else {
$blocked = 0;
# add the user to the user block list,
# if configured
if ($config{'blockuser_failures'} &&
$userfail{$1} >=
$config{'blockuser_failures'}) {
push(@denyusers, $1);
$blockusertime{$1} = $time_now;
$blocked = 2;
if ($use_syslog) {
local $logtext = "Security alert: User $1 blocked after $config{'blockuser_failures'} failed logins";
syslog("crit", "%s",
$logtext);
}
}
# Send back a delay
$dl = $userdlay{$1} -
int(($time_now - $userlast{$1})/50);
int(($time_now - $userlast{$1})/50);
$dl = $dl < 0 ? 0 : $dl+1;
print $outfd "$dl $blocked\n";
$userdlay{$1} = $dl;
# Write out blocked status file
if ($blocked) {
&write_blocked_file();
}
}
$userlast{$1} = $time_now;
}
@@ -1599,12 +1655,18 @@ if (%users) {
return 0;
}
}
else {
elsif ($blocked == 1) {
# when the host has been blocked, give it an error
&http_error(403, "Access denied for $acptip. The host ".
"has been blocked because of too ".
"many authentication failures.");
}
elsif ($blocked == 2) {
# when the user has been blocked, give it an error
&http_error(403, "Access denied. The user ".
"has been blocked because of too ".
"many authentication failures.");
}
}
else {
# Get the real Webmin username
@@ -3759,6 +3821,10 @@ if (!$config{'tempbase'}) {
$config{'pidfile'} =~ /^(.*)\/[^\/]+$/;
$config{'tempbase'} = "$1/cgitemp";
}
if (!$config{'blockedfile'}) {
$config{'pidfile'} =~ /^(.*)\/[^\/]+$/;
$config{'blockedfile'} = "$1/blocked";
}
}
# read_users_file()
@@ -4353,3 +4419,18 @@ foreach my $s (@substrings, @mobile_agents) {
return 0;
}
# write_blocked_file()
# Writes out a text file of blocked hosts and users
sub write_blocked_file
{
open(BLOCKED, ">$config{'blockedfile'}");
foreach my $d (grep { $hostfail{$_} } @deny) {
print BLOCKED "host $d $hostfail{$d} $blockhosttime{$d}\n";
}
foreach my $d (grep { $userfail{$_} } @denyusers) {
print BLOCKED "user $d $userfail{$d} $blockusertime{$d}\n";
}
close(BLOCKED);
chmod(0700, $config{'blockedfile'});
}

View File

@@ -60,3 +60,6 @@ When Webmin's detected OS is automatically updated, Usermin's will be too (if it
---- Changes since 1.340 ----
Added an option to the Proxy Servers form to fallback to a direct connection if the proxy is down.
Added a tab showing details of the current cert, with a link to download in PEM or PKCS12 format.
---- Changes since 1.350 ----
Added an option to the Authentication page to block users with too many failed logins, as well as hosts.
Created the new Blocked Hosts and Users page to show blocks currently in force, and allow them to be cleared.

View File

@@ -9,6 +9,8 @@ require './webmin-lib.pl';
&lock_file($ENV{'MINISERV_CONFIG'});
&get_miniserv_config(\%miniserv);
$miniserv{'passdelay'} = $in{'passdelay'};
# Save blocked hosts
if ($in{'blockhost_on'}) {
$in{'blockhost_time'} =~ /^\d+$/ && $in{'blockhost_time'} > 0 ||
&error($text{'session_eblockhost_time'});
@@ -20,6 +22,20 @@ if ($in{'blockhost_on'}) {
else {
$miniserv{'blockhost_time'} = $miniserv{'blockhost_failures'} = undef;
}
# Save blocked users
if ($in{'blockuser_on'}) {
$in{'blockuser_time'} =~ /^\d+$/ && $in{'blockuser_time'} > 0 ||
&error($text{'session_eblockuser_time'});
$in{'blockuser_failures'} =~ /^\d+$/ && $in{'blockuser_failures'} > 0 ||
&error($text{'session_eblockuser_failures'});
$miniserv{'blockuser_time'} = $in{'blockuser_time'};
$miniserv{'blockuser_failures'} = $in{'blockuser_failures'};
}
else {
$miniserv{'blockuser_time'} = $miniserv{'blockuser_failures'} = undef;
}
$miniserv{'syslog'} = $in{'syslog'};
if ($in{'session'} && $ENV{'HTTP_COOKIE'} !~ /sessiontest=1/i) {
&error($text{'session_ecookie'});

8
webmin/clear_blocked.cgi Executable file
View File

@@ -0,0 +1,8 @@
#!/usr/local/bin/perl
# Re-start Webmin to clear blocks
require './webmin-lib.pl';
&show_restart_page($text{'blocked_title'}, $text{'blocked_restarting'});

View File

@@ -15,15 +15,27 @@ print "<table border>\n";
print "<tr $tb> <td><b>$text{'session_header'}</b></td> </tr>\n";
print "<tr $cb> <td nowrap>\n";
# Bad password delay
printf "<input type=radio name=passdelay value=0 %s> %s<br>\n",
$miniserv{'passdelay'} ? '' : 'checked', $text{'session_pdisable'};
printf "<input type=radio name=passdelay value=1 %s> %s<br>\n",
$miniserv{'passdelay'} ? 'checked' : '', $text{'session_penable'};
# Block bad hosts
printf "&nbsp;&nbsp;&nbsp;<input type=checkbox name=blockhost_on value=1 %s>\n",
$miniserv{'blockhost_failures'} ? "checked" : "";
print &text('session_blockhost',
"<input name=blockhost_failures size=4 value='$miniserv{'blockhost_failures'}'>",
"<input name=blockhost_time size=4 value='$miniserv{'blockhost_time'}'>"),"<br>\n";
&ui_textbox("blockhost_failures", $miniserv{'blockhost_failures'}, 4),
&ui_textbox("blockhost_time", $miniserv{'blockhost_time'}, 4)),"<br>\n";
# Block bad users
printf "&nbsp;&nbsp;&nbsp;<input type=checkbox name=blockuser_on value=1 %s>\n",
$miniserv{'blockuser_failures'} ? "checked" : "";
print &text('session_blockuser',
&ui_textbox("blockuser_failures", $miniserv{'blockuser_failures'}, 4),
&ui_textbox("blockuser_time", $miniserv{'blockuser_time'}, 4)),"<br>\n";
# Log to syslog
eval "use Sys::Syslog qw(:DEFAULT setlogsock)";
if (!$@) {
printf "<input type=checkbox name=syslog value=1 %s> %s\n",

BIN
webmin/images/blocked.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -14,7 +14,7 @@ $ver = &get_webmin_version();
"edit_upgrade.cgi", "edit_session.cgi", "edit_assignment.cgi",
"edit_categories.cgi", "edit_descs.cgi", "edit_themes.cgi",
"edit_referers.cgi", "edit_anon.cgi", "edit_lock.cgi",
"edit_mobile.cgi", "edit_advanced.cgi" );
"edit_mobile.cgi", "edit_blocked.cgi", "edit_advanced.cgi" );
@wtitles = ( $text{'access_title'}, $text{'bind_title'},
$text{'log_title'}, $text{'proxy_title'},
$text{'ui_title'}, $text{'mods_title'},
@@ -24,7 +24,8 @@ $ver = &get_webmin_version();
$text{'categories_title'}, $text{'descs_title'},
$text{'themes_title'}, $text{'referers_title'},
$text{'anon_title'}, $text{'lock_title'},
$text{'mobile_title'}, $text{'advanced_title'} );
$text{'mobile_title'}, $text{'blocked_title'},
$text{'advanced_title'} );
@wicons = ( "images/access.gif", "images/bind.gif", "images/log.gif",
"images/proxy.gif", "images/ui.gif", "images/mods.gif",
"images/os.gif", "images/lang.gif", "images/startpage.gif",
@@ -32,7 +33,7 @@ $ver = &get_webmin_version();
"images/assignment.gif", "images/categories.gif",
"images/descs.gif", "images/themes.gif", "images/referers.gif",
"images/anon.gif", "images/lock.gif", "images/mobile.gif",
"images/advanced.gif" );
"images/blocked.gif", "images/advanced.gif" );
if ($gconfig{'eazel'}) {
push(@wlinks, "edit_syslet.cgi");
push(@wtitles, $text{'syslet_title'});

View File

@@ -19,9 +19,12 @@ advanced_etemp=Fehlendes oder nicht-existentes Verzeichnis f&#252;r tempor&#228;
advanced_header=Erweiterte und experimentelle Optionen
advanced_pass=Mache Passwort anderen Webmin-Programmen verf&#252;gbar?<br><font size=-1>(Funktioniert nicht, wenn Session-Authentication eingeschaltet ist)</font>
advanced_preload=Webmin-Funktionsbibliotheken vorladen?
advanced_showstderr=Perl-Fehler im Browser anzeigen?
advanced_tdir=Verzeichnis
advanced_temp=Verzeichnis f&#252;r tempor&#228;re Dateien
advanced_tempdef=Standard (<tt>/tmp/.webmin</tt>)
advanced_title=Erweiterte Optionen
advanced_tmod=Module
anon_desc=Diese Seite erlaubt Ihnen, Zugriff auf ausgw&#228;hlte Webmin-Module und Pfade zu gew&#228;hren, ohne dass sich die Benutzer anmelden m&#252;ssen. F&#252;r jeden Modul-Pfad, den Sie unten eingeben (so wie <tt>/custom</tt> oder <tt>/passwd</tt>) m&#252;ssen Sie auch den Namen des Webminbenutzers eingeben, dessen Rechte f&#252;r den Zugriff auf die Module benutzt werden soll.
anon_desc2=Sie sollten SEHR vorsichtig sein, wenn Sie anonymen Zugriff gew&#228;hren, da ungen&#252;gende IP-Zugriffskontrolle oder Zugriff auf die falschen Module Angreifern die &#220;bernahme des Systems erm&#246;glichen kann.
anon_err=Konnte Einstellungen f&#252;r Anonymen Zugriff nicht speichern
@@ -79,6 +82,8 @@ ca_stop=Zertifikatsautorit&#228;t herunterfahren
ca_stopmsg=Klicken Sie auf diesen Schaltfl&#228;che, damit Webmin keine existierenden Zertifikate mehr akzeptiert oder neue ausstellt. Dies wird Benutzer zwingen, sich stattdessen mit Benutzernamen und Kennwort anzumelden.
ca_stopok=Ihre Zertifikatsautorit&#228;t wurde erfolgreich heruntergefahren.
ca_title=Zertifikatsautorit&#228;t
cache_ok=Suchen
cache_size=Gr&#246;&#223;e
categories_code=ID&nbsp;
categories_desc=In diesem Dialog k&#246;nnen Sie vorhandene Webmin-Kategorien bearbeiten und neue hinzuf&#252;gen, denen Sie dann einzelne Module zuweisen k&#246;nnen. Im oberen Teil der Maske sehen Sie die vorhandenen Kategorien, deren Beschreibungen Sie hier &#228;ndern k&#246;nnen. In der untersten Zeile k&#246;nnen Sie eine neue Kategorie erzeugen.
categories_ecat=Kategorie-ID $1 ist bereits verwendet.
@@ -307,6 +312,10 @@ os_update=Aktualisiere Webmin, so da&#223; es das erkannte Betriebssystem benutz
os_value=Wert
os_webmin=Betriebssystem, wie Webmin es sieht.
proxy_bind=Quell-IP-Adresse f&#252;r HTTP-Verbindungen
proxy_cache1=Nein
proxy_clear=Cache l&#246;schen
proxy_days=Tage
proxy_daysdef=F&#252;r immer
proxy_desc=Wenn der Host, auf dem Webmin l&#228;uft, sich hinter einer Firewall befindet, kann es sein, dass Sie einen Proxyserver zum Zugriff auf Web- und FTP-Seiten verwenden m&#252;ssen. Einige Module, wie z. B. das Modul <tt>Software-Pakete</tt>, werden diese Proxies benutzen, wenn Sie Dateien oder Programme herunterladen.
proxy_desc2=Wenn Webmin eine Datei von einer SourceForge-URL herunterl&#228;dt, dann wird wenn m&#246;glich automatisch der Mirror genutzt, den Sie hier angeben.
proxy_ebind=Fehlende oder ung&#252;ltige Quell-IP-Adresse
@@ -320,6 +329,7 @@ proxy_header2=Download Sites
proxy_http=HTTP-Proxy
proxy_mirrordef=&lt;Standard (UNC)&gt;
proxy_mirrordef2=Standard (UNC)
proxy_mods0=Alle Module
proxy_nofor=Kein Proxy f&#252;r
proxy_none=Keiner
proxy_osdn=Standard OSDN-Spiegel f&#252;r Downloads
@@ -407,9 +417,13 @@ ssl_key=Private Schl&#252;ssel-Datei
ssl_newfile=Schl&#252;ssel in Datei sichern
ssl_newkey=Dieses Formular kann dazu benutzt werden, um einen neuen SSL-Schl&#252;ssel f&#252;r Ihren Webmin-Server zu erzeugen.
ssl_on=SSL aktivieren, wenn verf&#252;gbar
ssl_pem=PEM-Format
ssl_pkcs12=PKCS12-Format
ssl_redirect=Nicht-SSL-Anfragen auf SSL umleiten?
ssl_return=SSL-Schl&#252;ssel
ssl_size=RSA-Schl&#252;sselgr&#246;sse
ssl_tabcurrent=Aktuelles Zertifikat
ssl_tabssl=SSL Einstellungen
ssl_title=SSL-Verschl&#252;sselung
ssl_usenew=Neuen Schl&#252;ssel sofort benutzen?
ssl_version=SSL-Protokollversion
@@ -439,6 +453,7 @@ syslet_desc=Diese Seite ist zur Konfiguration des automatischen Herunterladens u
syslet_ebase=Ung&#252;ltige Basis-URL
syslet_err=Syslet-Optionen speichern gescheitert
syslet_title=Automatischer Syslet-Download
syslog_errorlog=Webmin Fehler-Log
themes_change=&#196;ndern
themes_default=Standard Webmin-Theme
themes_delete=Dieses Formular kann benutzt werden, um ein derzeit nicht benutztes Theme, welches auf Ihrem System installiert ist, zu l&#246;schen.

View File

@@ -497,6 +497,7 @@ session_header=Authentication and session options
session_pdisable=Disable password timeouts
session_penable=Enable password timeouts
session_blockhost=Block hosts with more than $1 failed logins for $2 seconds.
session_blockuser=Block users with more than $1 failed logins for $2 seconds.
session_syslog2=Log blocked hosts, logins and authentication failures to <tt>syslog</tt>
session_disable=Disable session authentication
session_enable=Enable session authentication
@@ -508,6 +509,8 @@ session_err=Failed to save authentication
session_elogouttime=Missing or invalid logout time
session_eblockhost_time=Missing or invalid blocking time
session_eblockhost_failures=Missing or invalid blocking logins
session_eblockuser_time=Missing or invalid user blocking time
session_eblockuser_failures=Missing or invalid user blocking logins
session_ecookie=Your browser does not support cookies, which are required for session authentication
session_elsof=Local authentication requires the <tt>lsof</tt> program
session_remember=Offer to remember login permanently?
@@ -768,3 +771,14 @@ mobile_agents=Additional user agents for mobile browsers
mobile_err=Failed to save mobile device options
mobile_prefixes=URL hostname prefixes for mobile browsers
blocked_title=Blocked Hosts and Users
blocked_type=Type
blocked_who=Hostname or username
blocked_fails=Login failures
blocked_when=Blocked at
blocked_none=No hosts or users are currently blocked by Webmin.
blocked_user=Webmin user
blocked_host=Client host
blocked_clear=Clear All Blocks
blocked_cleardesc=Click this button to clear all current host and user blocks, by restarting the Webmin server process.
blocked_restarting=The Webmin server process is now restarting to clear blocked hosts and users - please wait for a few seconds before continuing.

View File

@@ -1128,11 +1128,15 @@ closedir(DIR);
return @rv;
}
# show_restart_page([title, msg])
sub show_restart_page
{
&ui_print_header(undef, $text{'restart_title'}, "");
local ($title, $msg) = @_;
$title ||= $text{'restart_title'};
$msg ||= $text{'restart_done'};
&ui_print_header(undef, $title, "");
print "<p>$text{'restart_done'}<p>\n";
print "<p>$msg<p>\n";
&ui_print_footer("", $text{'index_return'});
&restart_miniserv(1);
@@ -1208,6 +1212,28 @@ close(OUT);
return $data;
}
# get_blocked_users_hosts(&miniserv)
# Returns a list of blocked users and hosts from the file written by Webmin
sub get_blocked_users_hosts
{
local ($miniserv) = @_;
local $bf = $miniserv->{'blockedfile'};
if (!$bf) {
$miniserv->{'pidfile'} =~ /^(.*)\/[^\/]+$/;
$bf = "$1/blocked";
}
local @rv;
&open_readfile(BLOCKED, $bf);
while(<BLOCKED>) {
s/\r|\n//g;
local ($type, $who, $fails, $when) = split(/\s+/, $_);
push(@rv, { 'type' => $type,
$type => $who,
'fails' => $fails,
'when' => $when });
}
close(BLOCKED);
return @rv;
}
1;