$ git clone https://thingshare.ion.nu/thingshare.git
commit 9dc0581959233a2c8ddd09256644c2861a18c84e
Author: Alicia <...>
Date: Fri Mar 27 23:07:49 2020 +0100
Added bruteforce protection on login.
diff --git a/db.php b/db.php
index 40c6193..1715fd6 100644
--- a/db.php
+++ b/db.php
@@ -119,6 +119,7 @@ function db_create_tables()
msgread boolean,
latest boolean);');
mysqli_query($db, 'create table userblocks(user integer, blocked text);');
+ mysqli_query($db, 'create table loginfails(ip varchar(256), timestamp datetime);');
}
function db_getuser($id)
diff --git a/login.php b/login.php
index 537f91a..054d4a9 100644
--- a/login.php
+++ b/login.php
@@ -17,31 +17,53 @@
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-// TODO: Protect against excess failed logins from the same IP
if(isset($_POST['user']) && isset($_POST['pass']))
{
include_once('db.php');
- $error=_('Incorrect username/password');
- $user=mysqli_real_escape_string($db, $_POST['user']);
- $res=mysqli_query($db, 'select salt, password, id, status from users where name="'.$user.'"');
- if($res=mysqli_fetch_assoc($res))
+ // Check for bruteforcing attempts
+ $fail_time=getoption('loginfail_time', 3600);
+ $fail_limit=getoption('loginfail_limit', 10);
+ $fail_lockout=getoption('loginfail_lockout', 3600*24);
+ $ip=mysqli_real_escape_string($db, $_SERVER['REMOTE_ADDR']);
+ $oldtime=mysqli_real_escape_string($db, date('Y-m-d H:i:s', time()-$fail_time));
+ mysqli_query($db, 'delete from loginfails where timestamp<"'.$oldtime.'"');
+ $res=mysqli_query($db, 'select count(*) from loginfails where ip="'.$ip.'"');
+ $res=mysqli_fetch_row($res);
+ $fail_count=$res[0];
+ if($fail_count>$fail_limit)
{
- $hash=explode(':', $res['password']);
- $pass=hash($hash[0], $_POST['pass'].$res['salt']);
- if($pass==$hash[1])
+ $error=_('Login attempt limit exceeded');
+ }else{
+ // Check password
+ $error=_('Incorrect username/password');
+ $user=mysqli_real_escape_string($db, $_POST['user']);
+ $res=mysqli_query($db, 'select salt, password, id, status from users where name="'.$user.'"');
+ if($res=mysqli_fetch_assoc($res))
{
- switch($res['status'])
+ $hash=explode(':', $res['password']);
+ $pass=hash($hash[0], $_POST['pass'].$res['salt']);
+ if($pass==$hash[1])
{
- case ACCOUNT_ACTIVE:
- session_start();
- $_SESSION['name']=$_POST['user'];
- $_SESSION['id']=$res['id'];
- header('Location: '.(isset($_GET['returnto'])?urldecode($_GET['returnto']):BASEURL));
- exit();
- case ACCOUNT_BANNED: $error=_('Banned'); break;
- case ACCOUNT_EMAILUNVERIFIED: $error=_('Please check for a verification e-mail'); break;
+ switch($res['status'])
+ {
+ case ACCOUNT_ACTIVE:
+ session_start();
+ $_SESSION['name']=$_POST['user'];
+ $_SESSION['id']=$res['id'];
+ header('Location: '.(isset($_GET['returnto'])?urldecode($_GET['returnto']):BASEURL));
+ exit();
+ case ACCOUNT_BANNED: $error=_('Banned'); break;
+ case ACCOUNT_EMAILUNVERIFIED: $error=_('Please check for a verification e-mail'); break;
+ }
}
}
+ $time=mysqli_real_escape_string($db, date('Y-m-d H:i:s'));
+ mysqli_query($db, 'insert into loginfails(ip, timestamp) values("'.$ip.'", "'.$time.'")');
+ if($fail_count==$fail_limit) // Limit reached, apply lockout
+ {
+ $time=mysqli_real_escape_string($db, date('Y-m-d H:i:s', time()-$fail_time+$fail_lockout));
+ mysqli_query($db, 'update loginfails set timestamp="'.$time.'" where ip="'.$ip.'"');
+ }
}
$error='<div class="error">'.$error.'</div>';
}else{
diff --git a/search.php b/search.php
index 7ed4e2b..ca6ef45 100644
--- a/search.php
+++ b/search.php
@@ -81,7 +81,7 @@ foreach($results as $thing)
}
?>
<div class="sidebar">
- <form>
+ <form action="<?=BASEURL?>/search">
<input type="hidden" name="q" value="<?=(isset($_GET['q'])?$_GET['q']:'')?>" />
<?=_('Results per page (approximately):')?> <input type="number" name="perpage" value="<?=$perpage?>" /><br />
<?=_('Sort by:')?> <select name="sort">