Авторские статьи Уязвимое место сессий или несколько слов о синхронизации

Discussion in 'Статьи' started by desTiny, 28 Aug 2008.

  1. desTiny

    desTiny Elder - Старейшина

    Joined:
    4 Feb 2007
    Messages:
    1,006
    Likes Received:
    444
    Reputations:
    94
    INTRO

    Сейчас я опишу грабли, на которые чуть сам не наступил. А они могли бы привести к серьёзным последствиям...

    BODY

    (показываю на примере PHP)
    Пусть у нас есть код, в котором используются сессии. Пусть в сессиях хранится, скажем, уровень доступа пользователя - число, которое может увеличиться, если пользователь что-то сделает. Этот же уровень доступа хранится в БД. Пусть пользователю для увеличения уровня доступа надо ответить на вопрос.
    Можно написать такой код, выполняющий соответствующее действие:

    1-ый косяк:
    PHP:
    // при логине аналогично level берётся из БД.

    session_start();
    // проверка существования сессии:
    (isset($_SESSION['id'])) or die("who r u?");
    $answer //из бд получаем ответ к уровню $_SESSION['level'];
    if ($_GET['answer'] === $answer){
      print 
    "ok";
      
    $_SESSION['level']++;
      
    //коннект к БД
      
    mysql_query("Update `auth` set level=level+1 where id=".$_SESSION['id']);
      
    //отключение от БД
    }
    2-ой косяк:
    PHP:
    // при логине аналогично level берётся из БД.

    session_start();
    // проверка существования сессии:
    (isset($_SESSION['id'])) or die("who r u?");
    $answer //из бд получаем ответ к уровню $_SESSION['level'];
    if ($_GET['answer'] === $answer){

      print 
    "ok";
      
    $level = ++$_SESSION['level'];
      if (
    $level === $maximum_level){
        
    //выполняем секретный код для очень крутых пацанов
      
    }
    }
    //лирическое отступление, положенное в таких случаях
    Казалось бы - всё правильно, но... это не так.
    //лирическое отступление закончилось

    //Пояснение к первому косяку
    Что можно сделать в таком случае? Пусть мошенник (пользуясь именами от Брюса Шнаера) Ева знает ответ на первый вопрос. Сейчас я покажу, что ей этого достаточно, чтобы попасть на любой уровень. Для этого она открывает несколько сессий с первым уровнем (в банальном случае - несколькими броузерами), и в каждой сессии отвечает на вопрос. При каждом ответе инкрементируется как уровень в сессии, так и уровень в БД. Сессии друг от друга не зависят, зато БД - общая! Тем самым мы неограниченное количество раз переходим на следующий уровень.
    ////Фикс 1
    Фикс не очень хороший, ибо при случайном использовании двух сессий (открыл человек двумя броузерами, потом запутался, заново ответил на уже пройденный вопрос) может привести к нежелательному откату назад.
    PHP:
    // при логине аналогично level берётся из БД.

    session_start();
    // проверка существования сессии:
    (isset($_SESSION['id'])) or die("who r u?");
    $answer //из бд получаем ответ к уровню $_SESSION['level'];
    if ($_GET['answer'] === $answer){
     
    session_start();
      print 
    "ok";
      
    $level=++$_SESSION['level'];
      
    //коннект к БД
      
    mysql_query("Update `auth` set `level`='$level' where id=".$_SESSION['id']);
      
    //отключение от БД
    }
    ////Конец фикса 1
    ////Фикс 2
    PHP:
    // при логине аналогично level берётся из БД.

    session_start();
    // проверка существования сессии:
    (isset($_SESSION['id'])) or die("who r u?");
    $bdlev //из бд получаем уровнь пользователя $_SESSION['id'];
    ($bdlev===$_SESSION['level']) or die("Relogin please!");
    $answer //из бд получаем ответ к уровню $_SESSION['level'];
    if ($_GET['answer'] === $answer){
      
    session_start();
      print 
    "ok";
      
    $level = ++$_SESSION['level'];
      
    //коннект к БД
      
    mysql_query("Update `auth` set `level`='$level' where id=".$_SESSION['id']);
      
    //отключение от БД
    }
    ////Конец фикса 2
    //Пояснение к первому косяку кончилось
    //Пояснение ко второму косяку
    Тут аналогичная ошибка, только более общий случай. Мы можем таким образом многократно вызвать один и тот же код, который мог планироваьтся единожды - к примеру, запись в таблицу результатов.
    ////Фикс
    Фикс аналогичен Фиксу 2 из 1-го косяка - надо СИНХРОНИЗИРОВАТЬ данные в БД и сессии.
    ////Фикс кончился
    //Пояснение ко второму косяку кончилось

    OUTRO
    Эх, сколько же ошибок может спровоцировать неправильная синхронизация и распараллеливание! Притом ошибок - трудноисправимых.

    GREETZ
    Они сами знают, кому :)

    (с) desTiny, 2008
     
    #1 desTiny, 28 Aug 2008
    Last edited: 11 Sep 2008
    4 people like this.