Как безопасно продолжить сеанс из Nextcloud на внешней странице PHP

Я создаю небольшое приложение для Nextcloud (16). Мне нужно выполнить некоторый код во внешнем файле PHP, а не в приложении Nextcloud. Эта страница должна быть защищена от несанкционированного доступа. Я хочу добиться этого с помощью существующего файла cookie сеанса Nextcloud.

В настоящее время я читаю файл cookie nc_session_id из браузера пользователя и проверяю, существует ли этот сеанс в пути сеанса PHP. Это должно быть безопасно, потому что злоумышленник обычно не может угадать идентификатор.

Вот как выглядит сессия Nextcloud в браузере:

файлы cookie Nextcloud

Я пытался проверить cookie с помощью session_status('nc_session_id') != PHP_SESSION_NONE, но это всегда возвращает int(1) -> сеанс не существует, потому что перед этим мне пришлось бы запустить session_start(). НО в этом особом случае внешняя страница никогда не должна сама начинать новый сеанс — она должна только проверять, существует ли уже действительный сеанс Nextcloud.

Мой текущий код, похоже, выполняет эту работу:

session_name('nc_session_id');
$sessid_cook = filter_input(INPUT_COOKIE, "nc_session_id", FILTER_SANITIZE_STRING);
$sess_path = session_save_path().'/sess_'.$sessid_cook;

if(isset($_COOKIE['nc_session_id']) &&
    isset($_COOKIE['nc_username']) && 
    file_exists($sess_path)) {
    echo "Session okay";
    session_start();
} else {
    echo "Access denied";
    exit;
}

// my protected logic here

Если я манипулирую куки-файлом сеанса в своем браузере, код PHP на сервере не может найти файл сеанса для этого манипулируемого куки-файла. Таким образом, доступ запрещен.

Это работает в моей текущей настройке, но что произойдет, если сеансы обрабатываются Redis или Memcache? Файлы cookie не могут быть проверены локально.

Есть ли лучший способ «проверить» файлы cookie сеанса перед запуском сеанса PHP?

Безопасно ли мое решение или в нем есть недостатки?


person Dexter2k    schedule 07.10.2019    source источник
comment
FWIW, не рекомендуется предполагать, что сеансы хранятся в файлах на вашем диске. Сеансы PHP - это абстрактная вещь, они могут, в зависимости от настройки, храниться в БД, кэше памяти, диске другого сервера или любом другом механизме хранения. Если вам нужно искать файлы сеанса, вы, вероятно, делаете что-то очень неправильно.   -  person shevron    schedule 07.10.2019


Ответы (1)


Вы не можете предполагать, что ваши пользователи правильно подключены просто по наличию файла cookie «nc_session_id», потому что содержимое этого файла cookie всегда идентично содержимому файла cookie со значением «instanceid» в вашей конфигурации nextcloud.

Для этого необходимо расшифровать содержимое сеанса nextcloud и протестировать различные значения.

<?php
$session_path = session_save_path().'/sess_'.$_COOKIE["nc_session_id"];
$passphrase = $_COOKIE["oc_sessionPassphrase"];
$nextcloud_path = '/var/www/nextcloud';

include $nextcloud_path.'/3rdparty/phpseclib/phpseclib/phpseclib/Crypt/Base.php';
include $nextcloud_path.'/3rdparty/phpseclib/phpseclib/phpseclib/Crypt/Rijndael.php';
include $nextcloud_path.'/3rdparty/phpseclib/phpseclib/phpseclib/Crypt/AES.php';
include $nextcloud_path.'/3rdparty/phpseclib/phpseclib/phpseclib/Crypt/Hash.php';

use phpseclib\Crypt\AES;
use phpseclib\Crypt\Hash;

class nextcloudSession {
    private $cipher;
    private $session_path;
    private $passphrase;

    public function __construct($session_path,$passphrase) {
        $this->cipher = new AES();
        $this->session_path = $session_path;
        $this->passphrase = $passphrase;

    }

    public function getSession() {
        $session_crypted = file_get_contents($this->session_path);
        $session_crypted_content = substr($session_crypted,strpos($session_crypted,'"')+1,-2);
        $session = json_decode($this->decrypt($session_crypted_content,$this->passphrase), true);
        return $session;
    }

    public function setSession($session) {
        $session_crypted_content = $crypt->encrypt(json_encode($session),$this->passphrase);
        $session_crypted = 'encrypted_session_data|s:'.strlen($session_crypted_content).':"'.$session_crypted_content.'";';
        return file_put_contents($session_path,$session_crypted);
    }

    public function isLogged() {
        $session = $this->getSession();
        if (isset($session["login_credentials"]) and (!isset($session["two_factor_auth_uid"]) and isset($session["two_factor_auth_passed"])) and !isset($session["app_password"])) {
            return true;
        } else {
            return false;
        }
    }
    private function calculateHMAC(string $message, string $password = ''): string {

        $password = hash('sha512', $password . 'a');

        $hash = new Hash('sha512');
        $hash->setKey($password);
        return $hash->hash($message);
    }
    private function encrypt(string $plaintext, string $password = ''): string {

        $this->cipher->setPassword($password);

        $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $charactersLength = strlen($characters);
        $iv = '';
        for ($i = 0; $i < 16; $i++) {
            $iv .= $characters[rand(0, $charactersLength - 1)];
        }

        $this->cipher->setIV($iv);

        $ciphertext = bin2hex($this->cipher->encrypt($plaintext));
        $hmac = bin2hex($this->calculateHMAC($ciphertext.$iv, $password));

        return $ciphertext.'|'.$iv.'|'.$hmac;
    }
    private function decrypt(string $authenticatedCiphertext, string $password = ''): string {

        $this->cipher->setPassword($password);

        $parts = explode('|', $authenticatedCiphertext);
        if (\count($parts) !== 3) {
            return false;
            throw new \Exception('Authenticated ciphertext could not be decoded.');
        }

        $ciphertext = hex2bin($parts[0]);
        $iv = $parts[1];
        $hmac = hex2bin($parts[2]);

        $this->cipher->setIV($iv);

        if (!hash_equals($this->calculateHMAC($parts[0] . $parts[1], $password), $hmac)) {
            return false;
            throw new \Exception('HMAC does not match.');
        }

        $result = $this->cipher->decrypt($ciphertext);
        if ($result === false) {
            return false;
            throw new \Exception('Decryption failed');
        }

        return $result;
    }

}

$nc_session = new nextcloudSession($session_path,$passphrase);
$_SESSION = $nc_session->getSession();
$isLogged = $nc_session->isLogged();
$nc_session->setSession($_SESSION);

Совет: вы можете получить логин и пароль сеанса nextcloud, я использую его для самодельного решения SSO с обратным прокси-сервером nginx и конфигурацией с 'auth_request'.

person Pocket    schedule 10.03.2020
comment
Привет @Pocket, это очень полезно, я пытаюсь понять, что такое файлы cookie в nextcloud и как они обрабатывают вход в систему! Не могли бы вы указать мне на ресурс? Я хотел бы понять, как правильно взаимодействовать с помощью curl и файлов cookie - person the.polo; 09.11.2020