是否可以与任何互联网提供商一起使用php会话



我正在用php创建一个web应用程序,这个web应用程序在我家里运行得很好,但当我试图在一家有两个网络的公司使用时,没有成功,我的意思是我可以访问该网站,但有很多会话丢失,即使我不刷新或导航也会被破坏,特别是当每个人都在使用互联网时,有时,当我进入页面时,这会将我重定向到一个类似的域,开头是:ww7

这是我的代码:

<?php
//Genera un password para el empleado o el cliente
function generaPass(){
//Se define una cadena de caractares. Te recomiendo que uses esta.
$cadena = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
//Obtenemos la longitud de la cadena de caracteres
$longitudCadena=strlen($cadena);
//Se define la variable que va a contener la contraseña
$pass = "";
//Se define la longitud de la contraseña, en mi caso 10, pero puedes poner la longitud que quieras
$longitudPass=10;
//Creamos la contraseña
for($i=1 ; $i<=$longitudPass ; $i++){
//Definimos numero aleatorio entre 0 y la longitud de la cadena de caracteres-1
$pos=rand(0,$longitudCadena-1);
//Vamos formando la contraseña en cada iteraccion del bucle, añadiendo a la cadena $pass la letra correspondiente a la posicion $pos en la cadena de caracteres definida.
$pass .= substr($cadena,$pos,1);
}
return $pass;
}

/**
* @param $required_fields_array, n array containing the list of all required fields
* @return array, containing all errors
*/
function check_empty_fields($required_fields_array){
//initialize an array to store error messages
$form_errors = array();
//loop through the required fields array snd popular the form error array
foreach($required_fields_array as $name_of_field){
if(!isset($_POST[$name_of_field]) || $_POST[$name_of_field] == NULL){
$form_errors[] = $name_of_field . " is a required field";
}
}
return $form_errors;
}
/**
* @param $fields_to_check_length, an array containing the name of fields
* for which we want to check min required length e.g array('username' => 4, 'email' => 12)
* @return array, containing all errors
*/
function check_min_length($fields_to_check_length){
//initialize an array to store error messages
$form_errors = array();
foreach($fields_to_check_length as $name_of_field => $minimum_length_required){
if(strlen(trim($_POST[$name_of_field])) < $minimum_length_required && $_POST[$name_of_field] != NULL){
$form_errors[] = $name_of_field . " is too short, must be {$minimum_length_required} characters long";
}
}
return $form_errors;
}
/**
* @param $data, store a key/value pair array where key is the name of the form control
* in this case 'email' and value is the input entered by the user
* @return array, containing email error
*/
function check_email($data){
//initialize an array to store error messages
$form_errors = array();
$key = 'email';
//check if the key email exist in data array
if(array_key_exists($key, $data)){
//check if the email field has a value
if($_POST[$key] != null){
// Remove all illegal characters from email
filter_var($_POST[$key], FILTER_SANITIZE_EMAIL);
//check if input is a valid email address
if(filter_var($_POST[$key], FILTER_VALIDATE_EMAIL) === false){
$form_errors[] = $key . " is not a valid email address";
}
}
}
return $form_errors;
}
/**
* @param $form_errors_array, the array holding all
* errors which we want to loop through
* @return string, list containing all error messages
*/
function show_errors($form_errors_array){
$errors = "<p><ul style='color: red;'>";
//loop through error array and display all items in a list
foreach($form_errors_array as $the_error){
$errors .= "<li> {$the_error} </li>";
}
$errors .= "</ul></p>";
return $errors;
}
/**
* @param $message, message to display
* @param string $passOrFail, test condition to determine message type
* @return string, returns the message
*/
function flashMessage($message, $passOrFail = "Fail"){
if($passOrFail === "Pass"){
$data = "<div class='alert alert-success'>{$message}</p>";
}else{
$data = "<div class='alert alert-danger'>{$message}</p>";
}
return $data;
}
/**
* @param $page, redirect user to page specified
*/
function redirectTo($page){
header("Location: {$page}.php");
}
/**
* @param $table, table that we want to search
* @param $column_name, the column name
* @param $value, the data collected from the form
* @param $db, database object
* @return bool, returns true if record exist else false
*/
function checkDuplicateEntries($table, $column_name, $value, $db){
try{
$sqlQuery = "SELECT * FROM $table WHERE $column_name=:$column_name";
$statement = $db->prepare($sqlQuery);  
$statement->execute(array(":$column_name" => $value));
if($row = $statement->fetch()){
return true;
}
return false;
}catch (PDOException $ex){
//handle exception
}
}

function signout(){
unset($_SESSION['username']);
unset($_SESSION['id']);
unset($_SESSION['coa']);
unset($_SESSION['registrar_usuarios']);
unset($_SESSION['capturar_pedidos']);
unset($_SESSION['salida_materiales']);
unset($_SESSION['alta_clientes']);
unset($_SESSION['alta_productos']);
unset($_SESSION['usuario_cliente']);
unset($_SESSION['cliente']);
session_destroy();
session_regenerate_id(true);
redirectTo('index');
}
/**
*
* @return bool, true if all good
*/
function guard(){
$isValid = true;
$fingerprint = md5($_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT']);
if((isset($_SESSION['fingerprint']) && $_SESSION['fingerprint'] != $fingerprint)){
$isValid = false;
signout();
}
return $isValid;
}
function isValidImage($file){
$form_errors = array();
//split file name into an array using the dot (.)
$part = explode(".", $file);
//target the last element in the array
$extension = end($part);
switch(strtolower($extension)){
case 'jpg':
case 'gif':
case 'bmp':
case 'png':
return $form_errors;
}
$form_errors[] = $extension . " is not a valid image extension";
return $form_errors;
}
function uploadAvatar($username){
if($_FILES['avatar']['tmp_name']){
//File in the temp location
$temp_file = $_FILES['avatar']['tmp_name'];
$ext = pathinfo($_FILES['avatar']['name'], PATHINFO_EXTENSION);
$filename = $username.md5(microtime()).".{$ext}";
$path = __DIR__ . "/../../../../uploadscsrnacional/{$filename}"; //uploads/demo.jpg
move_uploaded_file($temp_file, $path);
return $path;
}
return false;
}
function _token(){
$randonToken = base64_encode(openssl_random_pseudo_bytes(32));
//$randonToken = md5(uniqid(rand(), true))." md5";
return $_SESSION['token'] = $randonToken;
}
function validate_token($requestToken){
if(isset($_SESSION['token']) && $requestToken === $_SESSION['token']){
unset($_SESSION['token']);
return true;
}
return false;
}
function prepLogin ($id, $username, $coa,$registrar_usuarios, $capturar_pedidos,$salida_materiales,$alta_clientes,$alta_productos, $usuario_cliente, $cliente){
$_SESSION['id'] = $id;
$_SESSION['username'] = $username;
$_SESSION['coa'] = $coa;
$_SESSION['registrar_usuarios'] = $registrar_usuarios;
$_SESSION['capturar_pedidos'] = $capturar_pedidos;
$_SESSION['salida_materiales'] = $salida_materiales;
$_SESSION['alta_clientes'] = $alta_clientes;
$_SESSION['alta_productos'] = $alta_productos;
$_SESSION['usuario_cliente'] = $usuario_cliente;
$_SESSION['cliente'] = $cliente;
$fingerprint = md5($_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT']);
$_SESSION['fingerprint'] = $fingerprint;

echo $welcome = "<script type="text/javascript">
swal({
title: "Welcome back $username! ",
text: "You're being logged in.",
type: 'success',
timer: 3000,
showConfirmButton: false });
setTimeout(function(){
window.location.href = 'index.php';
}, 3000);
</script>";
}

这是子文件夹上方的.htaccess,因为我的整个项目都在这个子文件夹中,所以我的项目在另一个旧项目中,我不知道是谁创建了这个网站

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_URI} !^/[0-9]+..+.cpaneldcv$
RewriteCond %{REQUEST_URI} !^/[A-F0-9]{32}.txt(?: Comodo DCV)?$
RewriteRule ^index.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !^/[0-9]+..+.cpaneldcv$
RewriteCond %{REQUEST_URI} !^/[A-F0-9]{32}.txt(?: Comodo DCV)?$
RewriteRule . /index.php [L]
</IfModule>
# END WordPress

除非我遗漏了什么,否则我在这里看到的主要问题是,可能会为您的域创建一个cookie,或者修改您的域的cookie并以其他用户身份登录。

我使用了这个功能,删除了你的所有评论,并添加了一些我自己的评论。首先,我们将努力解决您的问题,但这不起作用。然后,我们应该解决用于此目的的方法的脆弱性。

<?php
//add these at the top of you script - to make sure we can see the errors
ini_set('display_errors', 1 );
error_reporting(-1);
function isCookieValid(PDO $db){
//Debug the $db variable - is this a valid PDO connetion. Added type hinting.
echo "<pre>"; //for debugging formatting
$isValid = false;
if (isset($_COOKIE['rememberUserCookie'])) {
// This is vaunerable to spoofing
$decryptCookieData = base64_decode($_COOKIE['rememberUserCookie']);
//check the cookie value
var_dum($decryptCookieData);
$user_id = explode("UaQteh5i4y3dntstemYODEC", $decryptCookieData);
$userID = $user_id[1];
//check the userID after parsing it from the cookie
var_dum($userID);
//$sqlQuery = "SELECT * FROM users WHERE id = :id";
$sqlQuery = "SELECT username FROM users WHERE id = :id"; //technically we only need the username
$statement = $db->prepare($sqlQuery);
$statement->execute(array(':id' => $userID));
//Varify that the user exists in the DB
if(false !== ( $username = $statement->fetchColumn(0))){  //fetch column instead
/* 
- A lot of unnessacry assginements here.
$id = $row['id'];
$username = $row['username'];
$_SESSION['id'] = $id;
$_SESSION['username'] = $username;
*/
$_SESSION['id'] = $userID; //we already know this value, if we didn't we wouldnt be here
$_SESSION['username'] = $username;
$isValid = true;
}else{
//Debug for cookie failures
var_dump("Cookie user {$userID} not found" );
$isValid = false;
signout(); //<--- signout is called here, obviously
/*
so if this IF condition fails for some reason, the 
cookie is deleted, this could include DB errors
etc. depending on your error reporting level, this 
may not be obvious
*/
}
}else{
var_dump("No Cookie found" );
}
return $isValid;
}

希望这些调试输出中的一些能揭示正在发生的事情

现在,这是如何脆弱的。此代码使用未加密的base64,其编码没有提供任何安全优势。因此,让我们分析一下我所说的易受攻击的含义,重点关注以下代码:

$decryptCookieData = base64_decode($_COOKIE['rememberUserCookie']);
$user_id = explode("UaQteh5i4y3dntstemYODEC", $decryptCookieData);
$userID = $user_id[1];
$sqlQuery = "SELECT username FROM users WHERE id = :id"; 
$statement = $db->prepare($sqlQuery);
$statement->execute(array(':id' => $userID));
if(false !== ( $username = $statement->fetchColumn(0))){
$_SESSION['id'] = $userID; 
$_SESSION['username'] = $username;
$isValid = true;
}else{
...  
}

所以我们在这里做的是:

  • 获取cookie的值,并从中解析用户ID
  • 然后我们获取该ID并简单地检查该用户是否存在于数据库中
  • 然后我们设置会话,说我就是那个用户/

现在考虑一下,如果我说你的系统上有一个基本帐户,也许你有免费会员和付费会员。我可以从那个免费帐户中取出我的"记住我"cookie,并编辑其值。这可以让我把我想要的任何用户ID放在那里。这就像把我的cookie改成这样的值一样简单:

VWFRdGVoNWk0eTNkbnRzdGVtWU9ERUM2NjY=

当它被这个脚本处理时,它会试图让我以用户666的身份登录。我可以根据需要尝试任意多个整数ID值,直到找到一个为止。

现在有人可能会说,我有优势,因为我可以看到源代码。然而,只需很少的想象力就可以看到饼干并看到结局=。对我来说,这是一个非常清楚的迹象,表明它的base64编码。以这种方式对cookie数据进行编码并不罕见。然后我会简单地解码它。然后我会看到这样的东西:

`UaQteh5i4y3dntstemYODEC{myID}`

通常情况下,很少或根本不需要努力向账户持有人隐藏账户ID。因此,看到这个数字的末尾,以及它从未改变的事实,会让我了解它是什么。我见过人们用更少的信息来解决更困难的事情,所以完全有可能也很可能有人会解决。

现在,谢天谢地,我们可以做一些简单的事情来解决这个问题。

最明显的是对cookie值使用真正的加密。这将类似于AES256,甚至可能是OpenSSL。这是可以的,可能是最简单的事情。但是,如果可以避免的话,我们真的不想向客户端提供任何重要的数据。当然,我们不想在客户端系统上存储帐户信息(用户ID是帐户信息)。

一个更好的选择是打破用户帐户和cookie数据之间的联系。这可以通过在表中存储此功能的数据来实现。然后,当设置了记住我时,我们在该表中为用户创建一个新记录。这将提取用户ID,并要求在访问他们的帐户之前设置记录。

当我构建这些时,我通常会将它们与"忘记密码"功能结合起来。我喜欢这一切都是"护照"的双关语。和"记住我"的唯一区别是,它使用的是电子邮件中发送的链接,而不是cookie。哦,通常密码重置是一次。但是,你可以在同一个表中为两者设置一个记录,并在重置的电子邮件中发送查找哈希,并将其保存在cookie中以供记住我。

  • 如果我们使用一个散列值,它只是一些随机垃圾(它是什么并不重要,这是一件好事),我们可以构建这个记录。这使得我们很难猜测饼干需要什么价值

这要好得多,但通过默默无闻实现的安全性并不是真正的安全性。所以我们仍然可以做得更好。

  • 我们可以对此记录设置时间限制,说它有效期为1个月,当他们访问网站时刷新。如果它在过期后使用,那么我们强制登录。

  • 我们可以跟踪用户的IP,并且只有当创建的IP与当前IP匹配时才使用该记录(我们想通知用户自动登录只在同一个IP上工作,这样他们就不会认为它坏了)

  • 我们可以随时删除此记录,我们不能删除用户ID,这就是为什么我们需要将数据分开的原因。也就是说,我们可以很容易地禁用这个功能。

  • 我们可以向用户发送电子邮件确认,表示他们已使用自动功能登录。这会通知他们有人在他们不知情的情况下访问了他们的帐户。

  • 我们可以存储创建cookie时使用的用户代理,这不应该真正改变。例如,如果他们在Edge中制作cookie,他们就没有资格在firefox中使用它。

  • 我们可以提出挑战性的问题,但我真的很讨厌这些,这有点违背了目的。

  • 我们可以也应该跟踪帐户登录的方式和时间。这在一定程度上取决于您的应用程序。

我相信我们还可以做其他事情来绊倒黑客。任何限制使用它的东西,在对用户透明的同时,安全性不应该仅仅基于一件事。

正如我所说,我可以帮助您重构它,使其安全。不过,首先我们应该解决饼干问题。一旦解决了这个问题,你就不能忽视我所指出的安全问题。这只是一个太大的洞,没有堵塞。

欢呼。

UPDATE我在评论中提到了它,所以我想给你一个关于如何使用MySql获得数据库架构以验证列和表名等的例子

$statement = 'SELECT `TABLE_NAME`, `ENGINE`, `TABLE_SCHEMA` FROM `information_schema`.`TABLES` WHERE `TABLE_SCHEMA` LIKE :database';
$stmt = $DB->prepare($statement);
$stmt->execute([':database' => $database]);
$shcema = [];
while($row = $stmt->fetch(PDO::FETCH_ASSOC)){
$shcema[$row['TABLE_NAME']] = ['_TABLE_NAME_'=>$row['TABLE_NAME'], '_ENGINE_'=>$row['ENGINE'],'_COLUMNS_' => []];
$statement = 'SHOW COLUMNS FROM `'.$row['TABLE_SCHEMA'].'`.`'.$row['TABLE_NAME'].'`';
$s = $DB->query($statement);
while($r = $s->fetch(PDO::FETCH_ASSOC)){
$shcema[$row['TABLE_NAME']]['_COLUMNS_'][$r['Field']] = $r;
}
}

这应该会生成类似的东西

$shcema = [ 'someTable' => [
'_TABLE_NAME_' => 'someTable',
'_ENGINE_' => 'InnoDB',
'_COLUMNS_' => [ 
'id' =>[
'Field' => 'id',
'Type' => 'int(10) unsigned',
'Null' => 'NO',
'Key' => 'PRI',
'Default' => NULL,
'Extra' => 'auto_increment',
],[
'create_date' =>[
'Field' => 'create_date',
'Type' => 'date',
'Null' => 'YES',
'Key' => '',
'Default' => NULL,
'Extra' => '',
]
]
]
],[ 'someother_table' => [..]
];

这可能比您需要的更多,但基本上,您只需给它一个数据库名称,它就会找到所有的表,并为它们的模式构建一个数组。这很酷,因为你可以用它做一些整洁的事情。

最新更新