I thought I would put it on here to get some thoughts as to where I could improve and spot any holes in the security side of it. I must point out; I’m in no way, shape or form much good at PHP. I've been playing about with it for a week or so and this project has taken be about 3 days to get it where it is now.
This is my first proper project, I’ve built this project off the back of some things I've read on here and the internet. Expect the code to be a bit messy and probably not as efficient as it probably could be. There may also be some redundant code in there from debugging etc! Just so you know the kind of security I have put into my project is probably completely overkill for what is realistically needed! The only obvious weakness I can see is that the username & password is passed through $_POST to the server, so if someone was packet sniffing they could see the plain text password and username. Now sure how I could avoid this tho!!
My main inspiration came from this thread and this blog. So thanks to those who contributed to those sources
The kep aspects to my site are:
Implementing dynamic salting for each user (avoid the users password being 'sniffed')
Implementing filesystem key (peppering) (avoid the users password being 'sniffed')
Users salt is stored on the database and is changed along with their hashed password at each login.
Regenerate session ID with data gathered from /dev/urandom on every page load (avoid session hijacking)
Session timeout after inactivity
Compare session IP with remote IP (if different reject)
Remove IP is stored if client inputs incorrect details, 3 failed attempts blocks the IP for 2 hours. (attempt to stop brute force password attack)
Input validation (avoid SQL and HTML injection)
Feel free to download the code and try it out if you want.
All comments & criticisms welcome!!
secret.php
Code: Select all
<?php
require("common.php");
require_authentication();
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>Secret</title>
</head>
<body>
<h1>This is a Secret Page</h1>
<p>You must have successfully authenticated since you are seeing this page.</p>
<p>Your IP is : <? echo $_SESSION['ip']; ?> </p>
<p>
<a href="<?php echo $_SERVER[PHP_SELF]; ?>">View again?</a>
</p>
<p>
<a href="login.php">Logout?</a>
</p>
</body>
</html>Code: Select all
<?
// mysql_connect
function db_connect(&$server) {
$db_hostname = 'localhost';
$db_username = 'xxusernamexx';
$db_password = 'xxpasswordxx';
$db_server = mysql_connect($db_hostname,$db_username,$db_password);
if (!$db_server) die("Unable to connect to MySQL db: " . $db_database);
$server = $db_server;
}
// Sanitise data function
function mysql_entities_fix_string($string) {
return htmlentities(mysql_fix_string($string));
}
function mysql_fix_string($string) {
if (get_magic_quotes_gpc()) $string = stripslashes($string);
return mysql_real_escape_string($string);
}
// Regenerate session id with random id
function session_regen($bits) {
$fp=fopen("/dev/urandom","rb");
$sid = bin2hex(fread($fp,$bits));
fclose($fp);
session_start();
$tmp = $_SESSION;
session_unset();
session_destroy();
session_id($sid);
session_start();
$_SESSION = $tmp;
$tmp = array();
}
?>Code: Select all
<?
function fskey() {
return "xxx fs key goes here xxx";
}
?>Code: Select all
<?
ini_set('session.save_path', '/home/scatty85/sessions');
ini_set('session.entropy_file', '/dev/urandom');
ini_set('session.entropy_length', 128);
ini_set('session.hash_function', 'sha256');
// Record users ip and time
$b_ip = $_SERVER['REMOTE_ADDR'];
// Connect to database
require_once("common.php");
require_once("include/fskey.php");
db_connect($db_server);
$db_name = "scatty85_phptest";
mysql_select_db($db_name);
// Check ip address for blocks
ip_check();
// Find user on database
$user = mysql_entities_fix_string($_POST['username']);
$user = hash('sha256', $user);
$query = "SELECT u_salt, pass FROM users WHERE user='$user'";
$result = mysql_query($query);
if (!$result) die ("Could not submit data:<br /><br /> " . mysql_error());
unset($query);
mysql_close($db_server);
function getPasswordForUser() {
global $result;
return mysql_fetch_row($result);
}
function validate($response, $password, $salt) {
return hash('sha256', $password.$salt.base64_decode(fskey())) == $response;
}
// Failed login
function ip_fail() {
db_connect($db_server);
$db_name = "scatty85_phptest";
mysql_select_db($db_name);
global $b_ip;
$query = "SELECT * FROM ip_block WHERE ip_add='$b_ip'";
$ip_block = mysql_query($query);
if (!$ip_block) die ("Could not select data:<br /><br /> " . mysql_error());
$ip_d = mysql_fetch_row($ip_block);
// Record IP if not on database
if (mysql_num_rows($ip_block) < 1) {
$query = "INSERT INTO ip_block(ip_add, count) VALUES ('$b_ip', 1)";
$i_sql = mysql_query($query);
if (!$ip_block) die ("Could not submit data:<br /><br /> " . mysql_error());
} else {
// Count attempts
if ($ip_d[2] < 3) {
$count = $ip_d[2] + 1;
$query = "UPDATE ip_block SET count='$count' WHERE ip_add='$b_ip'";
$i_sql = mysql_query($query);
if (!$ip_block) die ("Could not submit data:<br /><br /> " . mysql_error());
} else {
// Record block IP after 3 attempts
$timestamp = time();
$query = "UPDATE ip_block SET block='1', timestamp='$timestamp' WHERE ip_add='$b_ip'";
$i_sql = mysql_query($query);
if (!$ip_block) die ("Could not submit data:<br /><br /> " . mysql_error());
}
}
unset($query);
mysql_close($db_server);
}
// Check if blocked from site by IP
function ip_check() {
db_connect($db_server);
$db_name = "scatty85_phptest";
mysql_select_db($db_name);
global $b_ip;
$query = "SELECT * FROM ip_block WHERE ip_add='$b_ip'";
$ip_block = mysql_query($query);
if (!$ip_block) die ("Could not select data:<br /><br /> " . mysql_error());
$ip_d = mysql_fetch_row($ip_block);
// if IP blocked less than 1 minute ago fail
if (!mysql_num_rows($ip_block) < 1 && $ip_d[4] == 1 && $ip_d[3]+60*60*2 >= time()) {
unset($query);
mysql_close($db_server);
header("Location:login.php?error=".urlencode("CLIEN BLOCKED FOR TWO HOURS!"));
exit;
} else {
// Reset IP block, time, count if login successful
$query = "UPDATE ip_block SET block='0', timestamp='0', count='0' WHERE ip_add='$b_ip' ";
$i_sql = mysql_query($query);
if (!$ip_block) die ("Could not submit data:<br /><br /> " . mysql_error());
}
}
function authenticate() {
if (isset($_POST['username']) && isset($_POST['response'])) {
$data = getPasswordForUser(); // Users password from database
$pass = $data[1]; // server password
$salt = base64_decode($data[0]); // binary server salt
if (validate($pass, $_POST['response'], $salt)) {
session_regen(64);
update_pass_salt($_POST['response'],hash('sha256', $_POST['username'])); // Update users password & salt hash
$_SESSION['authenticated'] = "yes";
$_SESSION['ip'] = $_SERVER['REMOTE_ADDR']; // store login users IP address
$_SESSION['time'] = time(); // Store session time
} else {
destroy_session_and_data();
ip_fail();
header("Location:login.php?error=".urlencode("Failed authentication"));
exit;
}
} else {
destroy_session_and_data();
header("Location:login.php?error=".urlencode("Session expired"));
exit;
}
}
session_start();
authenticate();
header("Location:secret.php");
exit();
?>
Code: Select all
<?
ini_set('session.save_path', '/home/scatty85/sessions');
ini_set('session.entropy_file', '/dev/urandom');
ini_set('session.entropy_length', 128);
ini_set('session.hash_function', 'sha256');
session_start();
session_unset();
require_once("include/mysql_connect.php");
db_connect($db_server);
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Login</title>
<script type="text/javascript">
function login() {
if (validate()) {
var loginForm = document.getElementById("loginForm");
if (loginForm.username.value == "") {
alert("Please enter your user name.");
return false;
}
if (loginForm.password.value == "") {
alert("Please enter your password.");
return false;
}
var submitForm = document.getElementById("submitForm");
submitForm.username.value = loginForm.username.value;
submitForm.response.value = loginForm.password.value;
submitForm.submit();
}
}
</script>
<script type="text/javascript">
function validateUsername(field) {
if (field == "") return "No Username was entered.\n"
else if (field.length < 5)
return "Usernames must be at least 5 characters.\n"
else if (/[^a-zA-Z0-9_-]/.test(field))
return "Only a-z, A-Z, 0-9, - and _ allowed in Usernames.\n"
return ""
}
function validate() {
var form = document.getElementById("loginForm");
fail = validateUsername(form.username.value);
if (fail == "") {
return true
} else {
alert(fail);
return false
}
}
</script>
</head>
<body>
<h1>Please Login</h1>
<form id="loginForm" action="#" method="post">
<table>
<?php if (isset($_GET['error'])) { ?>
<tr>
<td>Error</td>
<td style="color: red;"><?php echo mysql_entities_fix_string($_GET['error']); ?></td>
</tr>
<?php } ?>
<tr>
<td>User Name:</td>
<td><input type="text" name="username"/></td>
</tr>
<tr>
<td>Password:</td>
<td><input type="password" name="password"/></td>
</tr>
<tr>
<td> </td>
<td>
<input type="button" name="submit" value="Login" onclick="login();"/>
</td>
</tr>
</table>
<a href="signup.php">Not a user?</a>
</form>
<form id="submitForm" action="authenticate.php" method="post">
<div>
<input type="hidden" name="username"/>
<input type="hidden" name="response"/>
</div>
</form>
<? mysql_close($db_server); ?>
</body>
</html>
Code: Select all
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Sign up</title>
<style type="text/css">
<!--
.style1 {font-family: Arial, Helvetica, sans-serif}
-->
</style>
</head>
<body>
<p>
<?
// Connect to database
require_once("include/mysql_connect.php");
require_once("include/fskey.php");
db_connect($db_server);
$db_name = "scatty85_phptest";
mysql_select_db($db_name);
$user = mysql_entities_fix_string($_POST['username']);
$fname = mysql_entities_fix_string($_POST['fname']);
$sname = mysql_entities_fix_string($_POST['sname']);
$email = mysql_entities_fix_string($_POST['email']);
$pass = mysql_entities_fix_string($_POST['h_password']); // p
$pass_salt = mysql_entities_fix_string($_POST['u_salt']); // base64 u_salt
$user = hash('sha256', $user); // hash the user name
$pass = hash('sha256',$pass.base64_decode($pass_salt).base64_decode(fskey())); // hash(pass.salt.fskey)
// Check if user already exists
if (e_Check($email) && u_Check($user)) {
$query = "INSERT INTO users VALUES " .
"('$user', '$pass', '$pass_salt', '$fname', '$sname', '$email')";
$result = mysql_query($query);
if (!$result) die ("Could not submit data:<br /><br /> " . mysql_error());
echo "<h1 align='center' class='style1'>Sign up complete!</h1><br /><br /><a href='login.php'>Login?</a>";
} else {
echo "<h1 align='center' class='style1' style='color:Red'>Username or password already used!</h1><br /><br /><a href='signup.php' align='center'>Go back?</a>";
}
mysql_close($db_server);
// END
function e_Check($e) {
$q = "SELECT email FROM users WHERE email='$e'";
$r = mysql_query($q);
return (mysql_num_rows($r) < 1);
}
function u_Check($u) {
$q = "SELECT user FROM users WHERE user='$u'";
$r = mysql_query($q);
return (mysql_num_rows($r) < 1);
}
?>
</body>
</html>
Code: Select all
<?
// Generate user salt
$fp=fopen("/dev/urandom","rb");
$pass_salt = base64_encode(fread($fp,256));
fclose($fp);
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Sign up</title>
<script type="text/javascript">
function f_submit() {
var iForm = document.getElementById("inputForm");
iForm.h_password.value = iForm.pass.value;
iForm.u_salt.value = '<? echo $pass_salt; ?>';
}
</script>
<script type="text/javascript">
// validate user input
function validateForename(field) {
if (field == "") return "No Forename was entered.\n"
return ""
}
function validateSurname(field) {
if (field == "") return "No Surname was entered.\n"
return ""
}
function validateUsername(field) {
if (field == "") return "No Username was entered.\n"
else if (field.length < 5)
return "Usernames must be at least 5 characters.\n"
else if (/[^a-zA-Z0-9_-]/.test(field))
return "Only a-z, A-Z, 0-9, - and _ allowed in Usernames.\n"
return ""
}
function validateEmail(field) {
if (field == "") return "No Email was entered.\n"
else if (!((field.indexOf(".") > 0) && (field.indexOf("@") > 0)) || /[^a-zA-Z0-9.@_-]/.test(field))
return "The Email address is invalid.\n"
return ""
}
function compEmail(field1,field2) {
if (field1 != field2) return "The passwords do not match.\n"
return ""
}
function validate() {
var form = document.getElementById("inputForm");
fail = validateUsername(form.username.value);
fail += validateForename(form.fname.value);
fail += validateSurname(form.sname.value);
fail += validateEmail(form.email.value);
fail += compEmail(form.pass.value,form.pass1.value);
if (fail == "") {
f_submit();
return true
} else {
alert(fail);
return false
}
}
</script>
</head>
<body>
<h1>Sign up</h1>
<form id="inputForm" action="submit.php" method="post" onsubmit="return validate();">
<table>
<tr><td>Username : </td><td><input type="text" name="username" /></td></tr>
<tr><td>First name : </td><td><input type="text" name="fname" /></td></tr>
<tr><td>Surname : </td><td><input type="text" name="sname" /></td></tr>
<tr><td>Email : </td><td><input type="text" name="email" /></td></tr>
<tr><td>Password : </td><td><input type="password" name="pass" /></td></tr>
<tr><td>Confirm password : </td><td><input type="password" name="pass1" /></td></tr>
<tr><td colspan="2" align="center"><input type="submit" value="Submit" /></td>
</table>
<input type="hidden" name="h_password" />
<input type="hidden" name="u_salt" />
</form>
</body>
</html>
Code: Select all
<?php
ini_set('session.save_path', '/home/scatty85/sessions'); // Store sessions
ini_set('session.entropy_file', '/dev/urandom');
ini_set('session.entropy_length', 128);
ini_set('session.hash_function', 'sha256');
require_once("include/mysql_connect.php");
require_once("include/fskey.php");
session_start();
function require_authentication() {
if (!isset($_SESSION['authenticated']) && !$_SESSION['authenticated'] == "yes") {
destroy_session_and_data();
header("Location:login.php?error=".urlencode("Not authenticated"));
exit;
}
if (isset($_SESSION['ip']) && $_SESSION['ip'] != $_SERVER['REMOTE_ADDR']) {
destroy_session_and_data();
header("Location:login.php?error=".urlencode("Fatal error"));
}
// Check session timeout 120s = 2min
if (isset($_SESSION['time']) && $_SESSION['time']+120 >= time()) {
$_SESSION['time'] = time();
session_regen(64);
} else {
destroy_session_and_data();
header("Location:login.php?error=".urlencode("Session expired"));
}
}
function destroy_session_and_data() {
$_SESSION = array();
if (session_id() != "" || isset($_COOKIE[session_name()]))
setcookie(session_name(), '', time() - 2592000, '/');
session_destroy();
}
// Generate new password hash + salt
// Generate new salt
function update_pass_salt($pass,$user) {
$fp=fopen("/dev/urandom","rb");
$n_salt = fread($fp,256);
fclose($fp);
// New hashed password + salt
$h_pass = hash('sha256', $pass.$n_salt.base64_decode(fskey()));
// base64_encode salt for database
$n_salt = base64_encode($n_salt);
// Connect to db
db_connect($db_server);
$db_name = "scatty85_phptest";
mysql_select_db($db_name);
// Update database with new password hash
$query = "UPDATE users SET pass='$h_pass', u_salt='$n_salt' WHERE user='$user'";
$result = mysql_query($query);
if (!$result) die ("Could not update data:<br /><br /> " . mysql_error());
mysql_close($db_server);
}
?>