我有以下代码在我尝试过的几种linux风格下工作正常(Ubuntu,Debian 8,CentOS 7),但是当我在Windows上使用它时,重新生成失败而没有错误。 $duration
是表示为static $duration = 60 * SESSION_TIMEOUT;
的类中的值,其中SESSION_TIMEOUT
是config.inc.php
文件(用户设置)中定义的常量。
session_start();
self::csrf(false);
if(self::verify(false) === true) {
$_SESSION['expires'] = time() + self::$duration;
}
session_regenerate_id(true);
$id = session_id();
session_write_close();
session_id($id);
session_start();
如果我在行$id
之后回显$id = session_id();
,则会有一个值,但如果我在最后session_id()
之后回显session_start()
,则为空。 (如果您认为此方法是“狡猾的'”
我不知道为什么这个再生代码失败了:
session_regenerate_id(true);
$id = session_id();
// one of the lines below this are causing session_id() to be blank
session_write_close();
session_id($id);
session_start();
请帮助我确定当新会话应包含新会话ID时导致新会话空白的原因。
session.save_path = 2; 755; d:/server/www/127.0.0.1/sessions
更新:我能够在日志中找到它,但有趣的部分是会话文件是用数据创建的。
PHP警告:未知:无法写入会话数据(文件)。请在第0行的Unknown中验证session.save_path的当前设置是否正确(2; 755; d:/server/www/127.0.0.1/sessions)
根据要求,在调用$id
(及后续代码)之前,session_write_close()
的示例为9vdom0ghuqkvsdcnkjacurc6rf2n4fn1s1gvfva44okd15jdpm30
更新:
经过一些测试后,我发现这个问题比上面提到的要复杂得多。我最初认为的问题是会话重新生成问题,更多的是没有存储会话变量的问题。我相信这仍然与Windows上的会话重新生成有关,但是我仍然无法确定修复,因为这段代码在Linux上运行得非常好。
这是班级(缩小):
class session {
static $duration = 60 * SESSION_TIMEOUT;
public static function start() {
session_start();
self::csrf(false);
if(self::verify(false) === true) {
$_SESSION['expires'] = time() + self::$duration;
}
session_regenerate_id(true);
$id = session_id();
session_write_close();
session_id($id);
session_start();
}
public static function csrf($new = false) {
if(!isset($_SESSION['csrf']) || $new === true) {
$_SESSION['csrf'] = sha1(openssl_random_pseudo_bytes(mt_rand(16,32)));
}
return $_SESSION['csrf'];
}
}
用于调用它的代码相当复杂(当涉及到确定何时生成新的csrf标记时),但下面是我尝试从整个项目中提取一个块以演示其用法。后面的login
代码应该足以让我相信查看逻辑:
if( session::verify() === true ) {
redirect();
} else {
$error = '';
$token = $_SESSION['csrf'];
if ( !empty($_POST) ) {
if ( isset($_POST['csrf']) ) {
// check CSRF token and if match ti token stored in session
$csrf = trim(filter_input(INPUT_POST, 'csrf', FILTER_SANITIZE_SPECIAL_CHARS, array('flags' => FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH)));
if($csrf != $_SESSION['csrf']) { session::destroy(); $error = 'CSRF Attack Detected'; }
if( isset($_POST['username']) && isset($_POST['password']) && empty($error) ) {
// login validation code
if($auth) {
// regenerate csrf token
$token = session::csrf(true);
session::create( $username );
// redirect back to application root
redirect();
}
$error = 'Invalid credentials';
} else {
if(empty($error)) { $error = 'Invalid credentials'; }
}
// user was not authenticated, regenerate csrf token to prevent form spam
$token = session::csrf(true);
} else {
// CSRF token did not match stored token in session
$error = 'CSRF Attack Detected';
}
}
}
登录代码中使用的会话类的其他方法如下:
使用用户信息更新会话数据。
public static function create($user) {
$_SESSION['nonce'] = sha1(microtime(true));
$_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
$_SESSION['agent'] = sha1($_SERVER['HTTP_USER_AGENT']);
$_SESSION['expires'] = time() + self::$duration;
$_SESSION['user'] = $user;
session_regenerate_id(true);
$id = session_id();
session_write_close();
session_id($id);
session_start();
}
验证发布的信息是否与服务器会话相匹配
public static function verify($destroy = false) {
$valid = true;
try {
if( !isset($_SESSION['nonce']) ) { $valid = false; }
if( !isset($_SESSION['user']) ) { $valid = false; }
if( isset($_SESSION['ip']) ) { if($_SESSION['ip'] != $_SERVER['REMOTE_ADDR']) { $valid = false; } } else { $valid = false; }
if( isset($_SESSION['agent']) ) { if($_SESSION['agent'] != sha1($_SERVER['HTTP_USER_AGENT']) ) { $valid = false; } } else { $valid = false; }
if( isset($_SESSION['expires']) ) { if($_SESSION['expires'] <= time()) { $valid = false; } } else { $valid = false; }
} catch (Exception $e) {
$valid = false;
}
if($valid === false) {
if(isset($_SESSION['nonce'])) { unset($_SESSION['nonce']); }
if(isset($_SESSION['ip'])) { unset($_SESSION['ip']); }
if(isset($_SESSION['agent'])) { unset($_SESSION['agent']); }
if(isset($_SESSION['expires'])) { unset($_SESSION['expires']); }
if(isset($_SESSION['user'])) { unset($_SESSION['user']); }
if($destroy === true) {
session_unset();
session_destroy();
}
}
return $valid;
}
更新:
通过运行一个简单的测试来缩小范围:
// ensure sessions are writeable
if(!is_writable(session_save_path())) {
// try alternate session path
$_session_save_path = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'sessions';
if(!is_writable($_session_save_path)) {
echo "Can't write sessions"; exit;
} else {
session_save_path(
$_session_save_path
);
}
}
结果&#39;无法写会话&#39;这意味着所有尝试和写入数据的方法都失败了。
这里有趣的是,无论我做什么(甚至将驱动器的根目录设置为“拥有&#39;完全&#39;权限”,所有子文件夹和文件似乎都有一个永久的半阴影“只读”复选框。除此之外,file_put_contents("hello.txt", "hello"); exit();
在本地网站的根文件夹中工作,但不在2个目录下的任何子文件夹中工作 - 即使在分配之后(和检查)权限。(例如d:\ server \ websites \ 127.0.0.1 \ htdocs \ works,但不是d:\ server \ websites \ 127.0.0.1 \ htdocs \ path1 \ path2 \
更新:
做了一些更多的故障排除,兔子洞变深了。找一个脚本来查找运行PHP的用户,以便更好地帮助检查权限:
echo 'Current script owner: ' . get_current_user();
回应&#39;当前脚本所有者SYSTEM&#39;
我跟进了这段代码:
$new = false;
session_start();
if(!isset($_SESSION['csrf']) || $new === true) {
$_SESSION['csrf'] = sha1(openssl_random_pseudo_bytes(mt_rand(16,32)));
}
session_regenerate_id(true);
$id = session_id();
session_write_close();
session_id($id);
session_start();
哪个仍然失败。我确实尝试了一些最终有用的东西,但我真的不喜欢它,因为它涉及不设置php.ini文件中记录的安全位。
我改变了:
session.save_path = "2;755;d:/server/www/127.0.0.1/sessions"
要
session.save_path = "d:/server/www/127.0.0.1/sessions"
这样可行,但所有会话文件现在都可以访问。
(服务器是在配置了相同php.ini文件的所有平台上的apache和php的相同版本,服务器配置文件尽可能接近)