Responsibly hacking my login
@reeseycup can you prove it’s your site?
@nakee
Very good question. I suppose the easiest is an e-mail from my host regarding my account with them? Is this sufficient?
http://i57.tinypic.com/rb9j5f.png
You can also see I have PHPmyadmin up for my website as well if you look at the browser tabs
The URL I will give you is on codefundamentals.com
If this is not sufficient then please let me know what proof is necessary and I will provide it :) .
I could upload a file and modify it with any sort of text you would like (confirming I have access.)
There may be users out there that disagree with me, but I would not do it even if you had digitally verifiable copies that the site was yours. The risks of things going wrong unintentionally are just too great.
Having said that, why not go through some of the tutorials and see if you can exploit your own site as a learning tool?
Well as I said the worst that could happen is my site getting DDOS’d (noobie here). I don’t have anything sensitive at all in my database that I couldn’t easily restore.
The thing is, I don’t think basic hacking would work on this login. I use PHP. I filter validate the input, sanitize it, then use PDO prepared statements as well when it comes to inserting.
I have a separate table from users that tracks attempts and limits each IP attempt per second / max attempt you and lock you out.
I used to be a member here years ago but I don’t think any of the basics will work. I’m not sure how to crash test it besides letting responsible hackers take a swing at it.
Thank you. This is the entire class. Upon submitting the form, I filter input validate the information, and then pass it along to my checkCredentials function.
```
<?php
class UserServices
{
private $pdo;
private $error;
public function __construct(PDO $pdo)
{
$this->pdo=$pdo;
}
public function checkCredentials($email,$pass,$remember)
{
if($this->delayLogin())
{
$username=filter_var($email,FILTER_SANITIZE_EMAIL);
$password=filter_var($pass,FILTER_SANITIZE_STRING);
$remember=filter_var($remember,FILTER_VALIDATE_BOOLEAN);
$findUser=$this->pdo->prepare("SELECT * FROM Users WHERE username=:username");
$findUser->execute(array(":username" => $username));
if($findUser->rowCount()===0)
{
$error="username";
return array(false,$error);
}
$userDetails=$findUser->fetch(PDO::FETCH_ASSOC);
if(password_verify($password,$userDetails["password"]))
{
if($remember)
$this->setCookie($userDetails);
$this->setSessions($userDetails,$this->findIP());
return array(true,"");
}
else
{
$error="password";
return array(false,$error);
}
}
else
{
$error="attempts";
return array(false,$error);
}
}
private function delayLogin()
{
$grabIPuser=$this->pdo->prepare(“SELECT * FROM Attempted_Logins WHERE ip_address = :ip_address”);
$grabIPuser->execute(array(“:ip_address” => $this->findIP()));
$ipUserDetails=$grabIPuser->fetch(PDO::FETCH_ASSOC);
if($grabIPuser->rowCount()===0)
{
$insertIPdetails=$this->pdo->prepare(“INSERT INTO Attempted_Logins (ip_address,login_attempts,last_login_time) VALUES(:ip_address,:login_attempts,:last_login_time)”);
$insertIPdetails->execute(array(“:ip_address” => $this->findIP(),“:login_attempts” => 0,“:last_login_time” => $this->getDateTime(“now”,“Y-m-d H:i:s”)));
return true;
}
else
{
$dbLastAttempt=$ipUserDetails[“last_login_time”];
$lastTimeAttempt=$this->getDateTime($dbLastAttempt,false);
$timeDiff=$lastTimeAttempt->diff($this->getDateTime("now",false));
if($timeDiff->format("%s")>2)
{
if($this->maxAttempts($ipUserDetails,$timeDiff->format("%i")))
return true;
else
return false;
}
else
return false;
}
}
private function maxAttempts($dbIpUser,$resetMinutes)
{
if($dbIpUser[“login_attempts”]<5)
{
$updateAttempts=$this->pdo->prepare(“UPDATE Attempted_Logins SET login_attempts=:login_attempts WHERE ip_address=:ip_address”);
$updateAttempts->execute(array(“:ip_address” => $dbIpUser[“ip_address”],“:login_attempts” => $dbIpUser[“login_attempts”]+1));
return true;
}
else
{
if($resetMinutes>=15)
{
$updateAttempts=$this->pdo->prepare(“UPDATE Attempted_Logins SET login_attempts=0,last_login_time=:last_login_time WHERE ip_address=:ip_address”);
$updateAttempts->execute(array(“:ip_address” => $dbIpUser[“ip_address”],“:last_login_time” => $this->getDateTime(“now”,“Y-m-d H:i:s”)));
return true;
}
else
{
$updateAttempts=$this->pdo->prepare(“UPDATE Attempted_Logins SET login_attempts=:login_attempts WHERE ip_address=:ip_address”);
$updateAttempts->execute(array(“:ip_address” => $dbIpUser[“ip_address”],“:login_attempts” => $dbIpUser[“login_attempts”]+1));
return false;
}
}
}
private function setCookie($userInfo)
{
setcookie(“rememberMe”,$userInfo[“authkey”],time() + (86400 * 14),“/”);
}
public function userCookies($loginCookie,$action)
{
$cookieUser=$this->pdo->prepare(“SELECT * FROM Users WHERE authkey=:authkey”);
$cookieUser->execute(array(“:authkey” => $loginCookie));
if($cookieUser->rowCount()===0)
return false;
else
{
$cookieUserInfo=$cookieUser->fetch(PDO::FETCH_ASSOC);
if($action==="compare")
return $cookieUser["authkey"];
else if($action==="set")
$this->setSessions($cookieUser,$this->findIP());
else
return null;
}
}
private function setSessions($userDBinfo,$thisIP)
{
$SESSION[“loggedin”]=true;
$SESSION[“username”]=$userDBinfo[“username”];
$SESSION[“userID”]=$userDBinfo[“id”];
$SESSION[“firstname”]=$userDBinfo[“firstname”];
$resetAttempts=$this->pdo->prepare("UPDATE Attempted_Logins SET login_attempts=0,last_login_time=:last_login_time WHERE ip_address=:ip_address");
$resetAttempts->execute(array(":ip_address" => $thisIP,":last_login_time" => $this->getDateTime("now","Y-m-d H:i:s")));
}
private function getDateTime($when,$format)
{
$instance=new DateTime($when);
if($format)
return $instance->format($format);
else
return $instance;
}
private function findIP()
{
return getenv(“HTTP_CLIENT_IP”)?:
getenv(“HTTP_X_FORWARDED_FOR”)?:
getenv(“HTTP_X_FORWARDED”)?:
getenv(“HTTP_FORWARDED_FOR”)?:
getenv(“HTTP_FORWARDED”)?:
getenv(“REMOTE_ADDR”);
}
}
?>```
The authkey is always a risk, especially when you don’t invalidate it on login/logout. Making the cookie http-only would be a good idea as well.
The attempt limit is easily avoided because findIP uses client-supplied data. This data is also not validated in any way, which could cause problems elsewhere if you expect actual IPs in the database. (And why use getenv there?)
As a side note, I think the naming of many functions is counterintuitive. E.g., delayLogin doesn’t delay anything and actually gives true when login shouldn’t be “delayed”.
On logout the cookie will be destroyed and I’ll be creating a new authkey to replace there. Logging in will keep the authkey until it expires or it logs out.
As far as findIP(), that uses code I found online which said it was reliable to correctly find the IP of the user. If you think you can get a better IP, please let me know how I can do that. That whole return data is based off code I found online.
I’ll go over naming more also - I touched it up but I guess I need to revisit it more.
Yeah I read about the proxy by multiple users and there was a stackoverflow thread where they advocated that method in my code to see which IP would be best.
Ultimately it doesn’t matter too much because the goal is to just delay them logging in repeatedly. As long as I grab some sort of identifiable information (fake iP or not) then that’s fine. They’ll still need to delay and get another IP if they want to continue which still will slow them down.
If more people agree that I should change it to REMOTE_ADDR then I will happily do so :).
My code does feature a 2s delay between attempts and a 5 max attempt so user-submitted code won’t even touch my Users table unless they are slow enough / under 5 attempts (or over 15 minutes in which case it’ll reset the 5 attempt rule).
I’m not seeing TOO much criticism so I’m guessing it’s not the worst code that you all have seen before. It’s somewhat reassuring. First PHP class I’ve ever made. Proud of my baby.