Skip to content

Commit 323bf57

Browse files
authored
Improve security / anti brute force (hestiacp#818)
* Improved changes against brute forcing * Improved changes against brute forcing * Fixed issue with accessing /data/users/{username}/user.conf * Changed ' to " line 64
1 parent bbed383 commit 323bf57

File tree

5 files changed

+132
-36
lines changed

5 files changed

+132
-36
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ All notable changes to this project will be documented in this file.
66
- Added support for configuring individual TTL per DNS record. Thanks to @jaapmarcus!
77
- Added support for Ubuntu Server 20.04 LTS.
88
- Added the ability to set the php cli version per user (using alias).
9-
- Added support for resolving ip addresses based on geoip database for Awstats
9+
- Added support for resolving ip addresses based on geoip database for Awstats
10+
1011

1112
### Bugfixes
1213
- Disable Apache2 Server Status Module by default.
@@ -31,6 +32,8 @@ All notable changes to this project will be documented in this file.
3132
- Implement a validation function to verify the correct version in hestia.conf prior to install a new one.
3233
- Fix autologout issue on cloudflare proxy and rearange 2FA authentification part. Thanks to @rmj-s!
3334
- Roundcube fixes for PHP 7.4 compatibility.
35+
- Added delay when entering wrong username/password/2fa
36+
- Improved "Forgot password" function prevent brute forcing
3437

3538
## [1.1.1] - 2020-03-24 - Hotfix
3639
### Features

bin/v-change-user-rkey

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#!/bin/bash
2+
# info: change user password
3+
# options: USER
4+
#
5+
# The function changes user's password and updates RKEY value.
6+
7+
8+
#----------------------------------------------------------#
9+
# Variable&Function #
10+
#----------------------------------------------------------#
11+
12+
# Argument definition
13+
user=$1
14+
15+
16+
# Importing system enviroment as we run this script
17+
# mostly by cron wich not read it by itself
18+
source /etc/profile
19+
20+
# Includes
21+
source $HESTIA/func/main.sh
22+
source $HESTIA/conf/hestia.conf
23+
24+
#----------------------------------------------------------#
25+
# Verifications #
26+
#----------------------------------------------------------#
27+
28+
check_args '1' "$#" 'USER'
29+
is_format_valid 'user'
30+
is_object_valid 'user' 'USER' "$user"
31+
is_object_unsuspended 'user' 'USER' "$user"
32+
33+
# Perform verification if read-only mode is enabled
34+
check_hestia_demo_mode
35+
36+
#----------------------------------------------------------#
37+
# Action #
38+
#----------------------------------------------------------#
39+
40+
d=$(date +%s)
41+
42+
#----------------------------------------------------------#
43+
# Hestia #
44+
#----------------------------------------------------------#
45+
46+
# Changing RKEY value
47+
update_user_value "$user" '$RKEY' "$(generate_password)"
48+
49+
#check if RKEYEXP exists
50+
if [ -z "$(grep RKEYEXP $USER_DATA/user.conf)" ]; then
51+
sed -i "s/^RKEY/RKEYEXP='$d'\nRKEY/g" $USER_DATA/user.conf
52+
else
53+
update_user_value "$user" '$RKEYEXP' "$d"
54+
fi
55+
56+
# Logging
57+
log_history "forgot password request"
58+
log_event "$OK" "$ARGUMENTS"
59+
60+
exit

web/login/index.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
exec (HESTIA_CMD."v-get-user-salt ".$v_user." ".$v_ip." json" , $output, $return_var);
5353
$pam = json_decode(implode('', $output), true);
5454
if ( $return_var > 0 ) {
55+
sleep(5);
5556
$ERROR = "<a class=\"error\">".__('Invalid username or password')."</a>";
5657
} else {
5758
$user = $_POST['user'];
@@ -85,6 +86,7 @@
8586

8687
// Check API answer
8788
if ( $return_var > 0 ) {
89+
sleep(5);
8890
$ERROR = "<a class=\"error\">".__('Invalid username or password')."</a>";
8991
} else {
9092

@@ -102,9 +104,11 @@
102104
exec(HESTIA_CMD ."v-check-user-2fa ".$v_user." ".$v_twofa, $output, $return_var);
103105
unset($output);
104106
if ( $return_var > 0 ) {
107+
sleep(1);
105108
$ERROR = "<a class=\"error\">".__('Invalid or missing 2FA token')."</a>";
106109
}
107110
} else {
111+
sleep(1);
108112
$ERROR = "<a class=\"error\">".__('Invalid or missing 2FA token')."</a>";
109113
}
110114
}
@@ -148,6 +152,7 @@
148152
}
149153
}
150154
} else {
155+
sleep(1);
151156
$ERROR = "<a class=\"error\">".__('Invalid or missing token')."</a>";
152157
}
153158
}

web/reset/index.php

Lines changed: 53 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,33 +13,41 @@
1313
if ((!empty($_POST['user'])) && (empty($_POST['code']))) {
1414
$v_user = escapeshellarg($_POST['user']);
1515
$user = $_POST['user'];
16+
$email = $_POST['email'];
1617
$cmd="/usr/bin/sudo /usr/local/hestia/bin/v-list-user";
1718
exec ($cmd." ".$v_user." json", $output, $return_var);
1819
if ( $return_var == 0 ) {
1920
$data = json_decode(implode('', $output), true);
20-
$rkey = $data[$user]['RKEY'];
21-
$fname = $data[$user]['FNAME'];
22-
$lname = $data[$user]['LNAME'];
23-
$contact = $data[$user]['CONTACT'];
24-
$to = $data[$user]['CONTACT'];
25-
$subject = __('MAIL_RESET_SUBJECT',date("Y-m-d H:i:s"));
26-
$hostname = exec('hostname');
27-
$from = __('MAIL_FROM',$hostname);
28-
if (!empty($fname)) {
29-
$mailtext = __('GREETINGS_GORDON_FREEMAN',$fname,$lname);
30-
} else {
31-
$mailtext = __('GREETINGS');
32-
}
33-
if (in_array(str_replace(':'.$_SERVER['SERVER_PORT'],'.conf',$_SERVER['HTTP_HOST']), array_merge(scandir('/etc/nginx/conf.d'),scandir('/etc/nginx/conf.d/domains'),scandir('/etc/apache2/conf.d/domains'),scandir('/etc/apache2/conf.d')))){
34-
$mailtext .= __('PASSWORD_RESET_REQUEST',$_SERVER['HTTP_HOST'],$user,$rkey,$_SERVER['HTTP_HOST'],$user,$rkey);
35-
if (!empty($rkey)) send_email($to, $subject, $mailtext, $from);
36-
header("Location: /reset/?action=code&user=".$_POST['user']);
37-
exit;
38-
} else {
39-
$ERROR = "<a class=\"error\">".__('Invalid host domain')."</a>";
40-
}
41-
unset($output);
21+
if($email == $data[$user]['CONTACT']){
22+
//genrate new rkey
23+
exec ("/usr/bin/sudo /usr/local/hestia/bin/v-change-user-rkey ".$v_user."", $output, $return_var);
24+
unset($output);
25+
exec ($cmd." ".$v_user." json", $output, $return_var);
26+
$data = json_decode(implode('', $output), true);
27+
$rkey = $data[$user]['RKEY'];
28+
$fname = $data[$user]['FNAME'];
29+
$lname = $data[$user]['LNAME'];
30+
$contact = $data[$user]['CONTACT'];
31+
$to = $data[$user]['CONTACT'];
32+
$subject = __('MAIL_RESET_SUBJECT',date("Y-m-d H:i:s"));
33+
$hostname = exec('hostname');
34+
$from = __('MAIL_FROM',$hostname);
35+
if (!empty($fname)) {
36+
$mailtext = __('GREETINGS_GORDON_FREEMAN',$fname,$lname);
37+
} else {
38+
$mailtext = __('GREETINGS');
39+
}
40+
if (in_array(str_replace(':'.$_SERVER['SERVER_PORT'],'.conf',$_SERVER['HTTP_HOST']), array_merge(scandir('/etc/nginx/conf.d'),scandir('/etc/nginx/conf.d/domains'),scandir('/etc/apache2/conf.d/domains'),scandir('/etc/apache2/conf.d')))){
41+
$mailtext .= __('PASSWORD_RESET_REQUEST',$_SERVER['HTTP_HOST'],$user,$rkey,$_SERVER['HTTP_HOST'],$user,$rkey);
42+
if (!empty($rkey)) send_email($to, $subject, $mailtext, $from);
43+
header("Location: /reset/?action=code&user=".$_POST['user']);
44+
exit;
45+
} else {
46+
$ERROR = "<a class=\"error\">".__('Invalid host domain')."</a>";
47+
}
48+
}
4249
}
50+
unset($output);
4351
}
4452

4553
if ((!empty($_POST['user'])) && (!empty($_POST['code'])) && (!empty($_POST['password'])) ) {
@@ -52,24 +60,34 @@
5260
$data = json_decode(implode('', $output), true);
5361
$rkey = $data[$user]['RKEY'];
5462
if (hash_equals($rkey, $_POST['code'])) {
55-
$v_password = tempnam("/tmp","vst");
56-
$fp = fopen($v_password, "w");
57-
fwrite($fp, $_POST['password']."\n");
58-
fclose($fp);
59-
$cmd="/usr/bin/sudo /usr/local/hestia/bin/v-change-user-password";
60-
exec ($cmd." ".$v_user." ".$v_password, $output, $return_var);
61-
unlink($v_password);
62-
if ( $return_var > 0 ) {
63-
$ERROR = "<a class=\"error\">".__('An internal error occurred')."</a>";
64-
} else {
65-
$_SESSION['user'] = $_POST['user'];
66-
header("Location: /");
67-
exit;
63+
unset($output);
64+
exec("/usr/bin/sudo /usr/local/hestia/bin/v-get-user-value ".$v_user." RKEYEXP", $output,$return_var);
65+
if($output[0] > time() - 900){
66+
$v_password = tempnam("/tmp","vst");
67+
$fp = fopen($v_password, "w");
68+
fwrite($fp, $_POST['password']."\n");
69+
fclose($fp);
70+
$cmd="/usr/bin/sudo /usr/local/hestia/bin/v-change-user-password";
71+
exec ($cmd." ".$v_user." ".$v_password, $output, $return_var);
72+
unlink($v_password);
73+
if ( $return_var > 0 ) {
74+
sleep(5);
75+
$ERROR = "<a class=\"error\">".__('An internal error occurred')."</a>";
76+
} else {
77+
$_SESSION['user'] = $_POST['user'];
78+
header("Location: /");
79+
exit;
80+
}
81+
}else{
82+
sleep(5);
83+
$ERROR = "<a class=\"error\">".__('Code has been expired')."</a>";
6884
}
6985
} else {
86+
sleep(5);
7087
$ERROR = "<a class=\"error\">".__('Invalid username or code')."</a>";
7188
}
7289
} else {
90+
sleep(5);
7391
$ERROR = "<a class=\"error\">".__('Invalid username or code')."</a>";
7492
}
7593
} else {

web/templates/reset_1.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@
3030
<input tabindex="1" type="text" size="20px" style="width:240px" name="user" class="vst-input">
3131
</td>
3232
</tr>
33+
<tr>
34+
<td style="padding: 12px 0 0 2px;">
35+
<?php print __('Email');?>
36+
</td>
37+
</tr>
38+
<tr>
39+
<td>
40+
<input tabindex="1" type="text" size="20px" style="width:240px" name="email" class="vst-input">
41+
</td>
42+
</tr>
3343
<tr>
3444
<td style="padding: 20px 0 12px 0;">
3545
<input type="button" class="button cancel" value="<?php print __('Back');?>" onclick="location.href='/login/'">&nbsp;&nbsp;

0 commit comments

Comments
 (0)