Hallo!

Kunnen jullie mij helpen mijn login systeem zo veilig mogelijk te maken? Hieronder staan mijn codes voor het login script. Alvast bedankt!

index.php

<?php
session_start(); // Start de session \\

// IMPORTEER ALLE BESTANDEN \\
REQUIRE_ONCE 'paneel/include/config.php';
REQUIRE_ONCE 'paneel/include/functions.php';
REQUIRE_ONCE 'paneel/include/login_script.php';

// ACTIVEER DE FUNCTIE 'LOGGED_IN' \\
if(logged_in()) {
	header('Location: /dashboard'); // Hier wordt je doorgelinkt naar '/dashboard' als je bent ingelogd \\
	die(); // Als je bent ingelogd, volgt de volgende code niet \\
}
// ONDERHOUD INSTELLINGEN \\
$currentLocation = "index"; // PAS DE LOCATIE NIET AAN \\
include 'lib/classes/site.status.php';
?>
<!DOCTYPE html>
<html>
	<head>
		<script>
			<?php echo $disable_form;?>
		</script>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
		<meta name="viewport" content="width=1,initial-scale=1,user-scalable=1" />
		<title>
			Inloggen - <?php echo $_CONFIG['bedrijf']; ?>
		</title>
		<link rel="shortcut icon" type="image/gif" href="<?php echo $_CONFIG['bedrijf_logo']; ?>" />
		<link rel="stylesheet" type="text/css" href="paneel/style/css/index.css" />
		<link href="http://fonts.googleapis.com/css?family=Lato:100italic,100,300italic,300,400italic,400,700italic,700,900italic,900" rel="stylesheet" type="text/css">
		<link rel="stylesheet" type="text/css" href="paneel/style/bootstrap/css/bootstrap.min.css" />
	</head>
	<body>
		<section class="container">
			<section class="login-form">
			
			<section>
				<p style="margin-bottom: -7px; margin-top: -15px; color: black; font-size: 18pt; font-weight: bold;"><?php echo $_CONFIG['bedrijf']; ?></p>
			</section>
			<div class="panel panel-default">
				<div class="panel-body">
					<form method="post" id="disable" action="" role="login">
						<div class="form-group">
							<p style="text-align: center;">Inloggen, geen account? <a class="disable" href='/registreer'>Maak er één!</a></p>
							<p style="text-align: center; color: red;"><?php echo $error; ?></p>
							<label>Gebruikersnaam</label>
							<input autofocus title="Maximaal 50 tekens." type="text" name="gebruikersnaam" maxlength="50" required class="form-control" />
						</div>
						
						<div class="form-group">
							<label>Wachtwoord</label>
							<input title="Maximaal 100 tekens." type="password" name="wachtwoord" maxlength="100" required class="form-control" />
						</div>
						
						<input type="checkbox" name="onthoud" value="1" /> Onthoud mijn gegevens.
						<button type="submit" name="inloggen" class="btn btn-primary btn-block">Inloggen</button>
					</form>
				</div>
				<div class="panel-footer">
					<a href="#">Wachtwoord vergeten?</a>
				</div>
			</div>
			<section>
				<a href="<?php $_SERVER["REQUEST_URI"]; ?>">Copyright &copy; <?php echo date("Y"); echo ' <b>'; echo $_CONFIG['bedrijf']; echo '</b>'; ?></a>
			</section>
			
			</section>
		</section>
		<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
		<script src="paneel/style/bootstrap/js/bootstrap.min.js"></script>
	</body>


config.php

<?php
error_reporting(0);
// DE DATABASE INFORMATIE \\
$setting = parse_ini_file('datafile.ini');
$con = new mysqli($setting['db_host'], $setting['db_user'], $setting['db_pass'], $setting['db_name']);

// CONTROLEER DE VERBINDING \\
if ($con->connect_error) {
    die('<title>Niet gelukt om te verbinden met MySQL</title>Verbinings fout (' . $con->connect_errno . ') ' . $con->connect_error); // DE ERROR ALS ER EEN FOUT IN DE VERBINDING IS \\
}

// SERVER INSTELLINGEN \\
$_SERVER['admin'] = $setting['admin'];
$_SERVER['admin_ip'] = $setting['admin_ip'];

// CONFIG INSTELLINGEN \\
$result = $con->query("SELECT * FROM `instellingen`")->fetch_array(); // SELECTEER ALLES VAN INSTELLINGEN \\
$_CONFIG['bedrijf'] = ucfirst($result['bedrijf']); // SELECTEER DE BEDRIJFSNAAM \\
$_CONFIG['bedrijf_logo'] = $result['bedrijf_logo']; // SELECTEER HET BEDRIJFS LOGO \\
?>


login_script.php

<?php
if(isset($_POST['inloggen'])){
	// DEFINITEER $GEBRUIKERSNAAM EN $WACHTWOORD \\
	$gebruikersnaam   = $_POST['gebruikersnaam'];
	$wachtwoord       = $_POST['wachtwoord'];
	
	// BESCHERM DE GEGEVENS \\
	$gebruikersnaam   = $con->real_escape_string($gebruikersnaam);
	$wachtwoord       = md5($wachtwoord);
	
	// LOGIN SCRIPT \\
	$result = $con->query("SELECT * FROM `leden` WHERE `gebruikersnaam`='$gebruikersnaam'")->fetch_array(); // SELECTEER ALLE DATA VAN DE GEBRUIKERSNAAM \\
	if (( $gebruikersnaam ) == ( $result['gebruikersnaam'] )){
		if (( $wachtwoord ) == ( $result['wachtwoord'] )){
			$id = $con->query("SELECT `id` FROM `leden` WHERE `gebruikersnaam`='$gebruikersnaam'")->fetch_array();
			$_SESSION['id'] = $id['id'];
			header('Location: /dashboard');
		}
		else{
			$error = "Er is een fout wachtwoord ingevoerd voor de gebruiker '$gebruikersnaam'.";
			header('Refresh: 2; Url=/');
		}
	}
	else{
		$error = "De gebruiker '$gebruikersnaam' kan niet worden gevonden.";
		header('Refresh: 2; Url=/');
	}
}
?>


functions.php

<?php
function logged_in(){
	if(!empty($_SESSION['id'])){
		return true;
	}
	else{
		return false;
	}
}

function logout(){
	session_unset();
	header('Location: /');
}
?>


site_status.php

<?php	
$result = $con->query("SELECT * FROM `instellingen`")->fetch_array();

$maintenanceMode      = $result['status'];
$maintananceGlobal    = $result['status2'];
$maintenanceLocations = $result['status3'];
$maintenanceMessage   = "<!DOCTYPE html>
<html>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
<title>Onderhoud Modus</title>
<script type='text/javascript' src='http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.js'></script>

<script type='text/javascript'>
$(document).ready(function() {	

		var id = '#dialog';
	
		//Get the screen height and width
		var maskHeight = $(document).height();
		var maskWidth = $(window).width();
	
		//Set heigth and width to mask to fill up the whole screen
		$('#mask').css({'width':maskWidth,'height':maskHeight});
		
		//transition effect		
		$('#mask').fadeIn(1000);	
		$('#mask').fadeTo('slow',0.8);	

	
		//transition effect
		$(id).fadeIn(2000); 		
		
		var fields = document.getElementById('disable').getElementsByTagName('*');
for(var i = 0; i < fields.length; i++)
{
    fields[i].disabled = true;
}
});
</script>

<style type='text/css'>
body {
font-family:verdana;
font-size:15px;
}
#overlay {
  position: fixed;
  height: 100%;
  width: 100%;
  z-index: 1000000;
  background: url('link/to/semitransparent.png');
}

a {color:#333; text-decoration:none}
a:hover {color:#ccc; text-decoration:none}

#mask {
  position:absolute;
  left:0;
  top:0;
  z-index:9000;
  background-color:#000;
  display:none;
}  
#boxes .window {
  position:absolute;
  left:0;
  top:0;
  width:340px;
  height:200px;
  display:none;
  z-index:9999;
  padding:20px;
}
#boxes #dialog {
    width: 350px;
    height: 200px;
  background-color: white;
    position: absolute !important;
    top:0 !important;
    bottom: 0 !important;
    left: 0 !important;
    right: 0 !important;
    margin: auto !important;
}
</style>
</head><body>

<div id='boxes'>
<div style='top: 199.5px; left: 551.5px; display: none;' id='dialog' class='window'>
<b style='font-size: 15pt;'><center>Onderhoud</center></b>Momenteel is het paneel in onderhoud, hierom is gebruik maken van het paneel niet mogelijk. Deze onderhoud is waarschijnlijk snel voorbij. <br><br><b>- Het Beheer</b>
</div>
<div style='width: 1478px; height: 602px; display: none; opacity: 0.8;' id='mask'></div>
</div>
</body>
</html>
"; 

if (($maintenanceMode) == "onderhoud")
{
	if (($maintananceGlobal) == "niet_overal")
	{
		if (($currentLocation) == ($maintenanceLocations))
		{
			echo $maintenanceMessage;
		}
	}
	else
	{
		echo $maintenanceMessage;
	}
}
?>



Bedankt voor jullie reacties voor het veilig te maken! PS: ik gebruik md5 omdat ik het fijn vind om te gebruiken en password_hash() werkt op een of andere manier niet.
In dat geval is er gewoon sprake van een DoS-attack, maar dat kan ook buiten de brute-force om door massaal te refreshen op een willekeurige page die zwaar lijkt te zijn (een brute-force check is dat niet).
Je kan ook gewoon het IP voor 5 minuten in de iptables van Linux gooien en nullrouten.

Dus ik zou niet inzien waarom mijn gestelde brute-force beveilging niet handig is? Je denkt gewoon te ver door naar doem-scenario's ;-).
Zucht, hier gaan we weer.

Goed, je kunt SQL injection voorkomen door een hele community je code te laten auditten maar dit kun je zelf alleen voorkomen door anders naar code te gaan kijken. Specifiek door te letten op alle data die van buitenaf komt, ook wel user data (of user input) genoemd. Het komt eigenlijk op het volgende neer:

Do Not Trust User Data

Het is een vergissing om te denken dat user data alleen voorkomt in $_GET en $_POST enzo. Nadat user data in de database is opgeslagen is dit nog steeds user data en is deze nog steeds potentieel gevaarlijk.

Vervolgens moet je je afvragen: wat maakt een website onveilig. Simpel antwoord: alles wat de normale werking van een website kan manipuleren of breken. User data kan daar een groot aandeel in spelen, immers, je kunt daarmee data invoeren die in een bepaalde context een bepaalde betekenis heeft. En daarmee raak je precies de kern van de zaak: het is jouw taak als programmeur om deze data te ontdoen van haar speciale betekenis binnen die bepaalde context.

En dit doe je weer met escaping-functies voor de bijbehorende context: real_escape_string() (in combinatie met quotes, het een is niet veilig zonder het ander) voor de DATA-delen in je SQL, htmlspecialchars() voor alles wat je in (een) HTML(-document) doet, urlenocde() voor querystring-parameters in URLs et cetera. Ook zou je invoer aan syntactische controles moeten onderwerpen nog voordat je deze ergens gaat gebruiken. Kort samengevat:

Filter Input, Escape Output

Bij elk stukje data moet je je afvragen: WAAR komt dit vandaan en HOE behandel ik deze. Pas als je dit en de bovenstaande principes ALTIJD en OVERAL toepast heb je enige garantie dat je code redelijk veilig kan omgaan met user data...

... behalve nog het volgende. Escaping-functionaliteit is afhankelijk van character encoderingen. Deze werkt alleen goed indien de character encoding van de data die je escaped overeenstemt met de character encoding die je hanteert bij het escapen.

Bij het maken van je database connectie selecteer je nergens een character encoding (middels set_charset()), dus grote kans dat deze latin1 is. En zelfs al is deze toevallig utf8, dan nog zou je deze expliciet in moeten stellen. Ook grote kans dat er op dit moment dubbel utf8 ge-encodeerde data in je database zit (als je tabellen utf8 zijn) omdat MySQL de vertaling latin1 -> utf8 automatisch voor jou probeert uit te voeren. Wees altijd expliciet met het opgeven van character encoderingen of andere cruciale instellingen, neem hierbij niets aan. No zo eentje voor aan de muur:

Assume Nothing

Overigens: als jij het filter-input-escape-output principe goed zou toepassen zouden dus ook alle $_SERVER en $_CONFIG directives (en wat nog meer) in je HTML-document ge-escaped moeten worden.

Oh, en na een header('Location: ...') hoort altijd een exit te staan, anders is je code mogelijk onveilig.

En zo kan ik nog wel even doorgaan. Je hebt dit niet in een dag onder de knie, simpelweg omdat je (nog) niet weet wat er allemaal speelt of wat van invloed kan zijn.

Een maintenance mode zou ik niet af laten hangen van je database. Maak een config-var aan die aangeeft of de site toegankelijk is (site_online) ofzo en maak een whitelist met developer IP's. Controleer allereerst of de site online is en/of het IP voorkomt in een whitelist, serveer anders een statisch document middels een include.
Thomas van den Heuvel op 05/12/2016 15:32:26

Zucht, hier gaan we weer.

Als je er zo moe van wordt. Waarom maak je dan niet een tutorial hier op PHPhulp aan en verwijs je daarnaar toe? Zomaar een ideetje. ;-)
- Ariën - op 05/12/2016 15:49:38

Als je er zo moe van wordt. Waarom maak je dan niet een tutorial hier op PHPhulp aan en verwijs je daarnaar toe? Zomaar een ideetje. ;-)

Ik kan net zo goed naar een post wijzen als naar een tutorial. Enne, ik dacht dat het doen van herhalingen van zetten hier ingeburgerd was (zoals het oproepen tot het aanzetten + weergeven van foutmeldingen).

Waarom zou ik nog meer moeite spenderen aan het maken een tutorial? In beide gevallen is het water naar de zee dragen.

Je zou zelfs een soort van beslisboom kunnen maken voor eenieder die hier een vraagstuk plaatst. Grote kans dat een groot deel dan meteen opgelost is.

Zoiets dus:
[youtube]PtXtIivRRKQ[/youtube]
Misschien is een 'relevante topics' wel een leuke aanvulling als je een topicstart maakt. Ik zal het op de backlog zetten.

Zullen we weer terug naar het onderwerp gaan?
Rob Chesture op 05/12/2016 14:40:22

[quote="Ivo P op 05/12/2016 09:55:29"]
ik mis nog de inhoud van het script waar je op komt na header('Location: /dashboard')

Check je daar ook of de gebruiker ingelogd is? (en zo nee, stuur je hem dan terug naar het inlogscript).

en op regel 12 en 15 van je inlogfunctie voer je dezelfde query uit.

Op regel 13 zit het id dus in $result['id']
beetje zinloos om dat nog een keer uit te voeren om in $id['id'] dezelfde waarde te stoppen.

Daar check ik of de gebruiker ingelogd is. als dat niet zo is wordt hij/zij teruggestuurd.



[size=xsmall]Toevoeging op 05/12/2016 15:24:46:[/size]


Bij elke foutieve inlog sla je het IP op, de userID van het account en het aantal keer dat er geprobeerd is. Als dat gelijk of hoger is dan 5 bijvoorbeeld, dan blokkeer je het account op dat IP.

Is niet al te handig, hiermee wordt elke keer als je page reload de query uitgevoerd. Dus als ze dan ook nog page_reload in brute_force zetten, kan de server nog steeds overbelast worden.
[/quote]

Nog enige andere ideeën?

Op deze pagina van OWASP (Open Web Application Security Project) staan nog diverse tips en truuks uitgelegd over beveiliging in het algemeen:
https://www.owasp.org/index.php/PHP_Security_Cheat_Sheet

Probeer eerst maar eens je script uit te bouwen.


Als je 2 Factor Authentication wilt wilt inbouwen:
https://github.com/PHPGangsta/GoogleAuthenticator
Als ik mij niet vergis is het iets als:
[code]
<?php
$secret = "12341234"; // komt uit $ga->createSecret() en sla je dus op in de database.
$checkResult = $ga->verifyCode($secret, $_POST['code'], 2);
if($checkResult) {
echo "Challenge accepted";
} else {
echo "Fout, niet ingelogd!";
}
?>

Thnx, ik heb er naar gekeken :D. Hoe kan ik btw in een .ini file meerdere keren iets posten, dus dat ik zeg maar uit 2 verschillende pagina's, in de zelfde .ini file iets kan posten zonder dat de data die je het eerste hebt gepost weg gaat?

Daarom moet je eerst alle data ophalen met de genoemde parse_ini_file() functie. Dan mis je geen data.

Reageren