Лучший способ гарантировать правильные значения параметров вызовов Ajax


Я работаю над веб-сайтом, который требует некоторых вызовов ajax для повышения гибкости и производительности. Мои вызовы ajax предназначены для системы ранжирования. Мне нужно, чтобы три входных значения обрабатывались с помощью ajax (StoreID, ClientID, OrderID). Чтобы отправить действие с помощью ajax, я хочу убедиться, что отправленные значения параметров не были изменены пользователями с помощью веб-инструментов. Поэтому я подумал о трех различных способах гарантировать, что отправленная информация не была изменена:

  1. Отправить дополнительная ценность заключается в шифровании всех данных, отправляемых вместе. Таким образом, на стороне сервера во время обработки ajax я могу повторно зашифровать отправленные данные и посмотреть, соответствует ли результат шифрования отправленному значению шифрования.

  2. Отправьте все данные в виде одного зашифрованного значения. Затем на сервере, выполняя ajax, я могу расшифровать данные и снова назначить значения.

  3. Отправьте только идентификатор заказа и его шифрование, затем с помощью метода (1) убедитесь, что идентификатор заказа не изменен, и используя запрос базы данных, извлеките две другие информации.

Вот мое мнение по каждому из трех способов:

  1. Потребляет память, так как я должен отправить идентификатор заказа, идентификатор клиента, идентификатор хранилища, зашифрованный идентификатор. Более того, информация, отслеживаемая в вызове ajax, даст людям информацию о том, что происходит, когда они оценивают заказ.

  2. Я проверил онлайн на наличие mcrypt_encrypt и mcrypt_decrypt, но я никогда их не использовал. Я видел, что они выпускают длинную нить, но я предпочитаю, чтобы мои данные отправлялись короткими или выглядели как зашифрованные данные md5. Есть ли лучшие методы ?

  3. Это элегантный способ, он выглядит прямолинейно, но требует некоторого вмешательства MySQL, что может занять много времени, особенно когда данные будут расти в будущем.

Итак, какой из них, по-вашему, лучше? Если у вас есть другие способы, я буду признателен, если вы поделитесь ими здесь. Спасибо

Пример сценария, которого я хочу избежать: Нажатие кнопки приведет к отправке формы с использованием AJAX, передавая идентификатор продукта. Пользователь переходит к исходному коду и изменяет идентификатор продукта с X на Y. Теперь пользователь нажимает кнопку, и форма была отправлена, и это повлияло на продукт с идентификатором Y. Как вы можете видеть, отправленные значения параметров не защищены и могут быть изменены. Я ищу способ гарантировать, что отправленные значения параметров верны и не изменены.

PS: Этот вопрос не касается обработки CSRF.

Author: CMPS, 2014-05-20

10 answers

Довольно простым способом было бы salt базовое прямое (т.Е. sha/md5 и т. Д.) хэширование трех параметров по секретному ключу, хранящемуся на сервере (и никогда не предоставляемому пользователям каким-либо образом).

Затем, когда вы получите запрос от пользователя, вы можете просто повторить этот процесс хэширования только для переадресации, чтобы убедиться, что данные не были изменены, аналогично тому, как большинство сайтов реализуют аутентификацию без необходимости хранить копию пароля пользователя в открытом виде в их базе данных.

Например:

define('SUPER_SECRET_KEY', 'foobar123'); // our 007 secret key to hash requests against

function generateToken($storeID, $clientID, $orderID) {
    return hash('sha256', SUPER_SECRET_KEY . "/{$storeID}/{$clientID}/{$orderID}");
}

function validateToken($token, $storeID, $clientID, $orderID) {
    return generateToken($storeID, $clientID, $orderID) === $token;
}

Теперь это, как я уже сказал, очень простая отправная точка. Использование статического значения salt не будет очень эффективным, и вы все равно оставляете себя открытым для повторных атак (т.Е. Кто-то снова и снова отправляет одну и ту же комбинацию токена/идентификатора клиента/идентификатора магазина/идентификатора заказа).

Одним из способов борьбы с атаками с повторным воспроизведением является создание nonce для каждого запроса, который будет действовать как salt.

Еще один супер простой пример использования nonces, тяжелый псевдокод впереди:

function generateNonce() {
    return hash('sha512', mt_rand() . time()); // please make a more secure nonce generator than this :)
}

// We need a way to retrieve the nonce for a particular request:
function generateLookup($storeID, $clientID, $orderID) {
    return hash('sha256', "{$storeID}/{$clientID}/{$orderID}");
}

function generateToken($nonce, $storeID, $clientID, $orderID) {
    $lookup = generateLookup($storeID, $clientID, $orderID);

    // Store the $nonce and $lookup somewhere (i.e a database)
    $db->query("INSERT INTO ... (nonce,lookup,expired) VALUES ('$nonce','$lookup','N')");

    return hash('sha256', "{$nonce}/$storeID/$clientID/$orderID");
}

function validateToken($token, $storeID, $clientID, $orderID) {
    $lookup = generateLookup($storeID, $clientID, $orderID);
    $rows = $db->query("SELECT nonce FROM ... WHERE lookup = '$lookup' AND expired = 'N'"); // again, pseudocode for retrieving the nonce from the persistent store
    if (count($rows) !== 0) {
        $nonce = $rows[0]['nonce'];
        // expire the nonce (note: these database queries should be in a transaction)
        $db->query("UPDATE ... SET expired = 'Y' WHERE $lookup = '$lookup' AND nonce = '$nonce'");
        return generateToken($nonce, $storeID, $clientID, $orderID) === $token;
    }
    return false;
}

Тогда ваш основной рабочий процесс станет:

На ПОЛУЧЕНИЕ

$data = // retrieve data
$token = generateToken(generateNonce(), $data['storeID'], $data['clientID'], $data['orderID']);

// output the $token value in a hidden field in your form

На ПОСТУ

if (validateToken($_POST['token'], $_POST['storeID'], $_POST['clientID'], $_POST['orderID'])) {
    // All good
}
else {
    echo "Back, ye filthy hacker!";
}
 2
Author: Jason Larke, 2014-05-29 05:18:55

Все пытаются подвести вас к этим пунктам:

На сервере....

  1. Вы знаете, кто этот пользователь (авторизация).
  2. Вы должны определить, к чему этот пользователь может получить доступ (ACL).
  3. Вы должны подтвердить, что запрошенный продукт существует и разрешен для доступа пользователю.
  4. Если пользователю разрешено вносить изменения в продукт, то по определению он имеет право вносить изменения в продукт X, и если он делает это с продуктом Y информация, потому что они путаются в источнике, тогда это зависит от пользователя.
  5. Если это связано с корзиной покупок, то вам следует игнорировать все другие данные, передаваемые пользователем (например, цену), и загружать данные о продукте только по идентификатору. Таким образом, они не могут купить продукт Y по цене продукта X. И потом, если они введут идентификатор продукта Y в Firebug, кого это волнует?

И если вы не делаете 2 и 3, вы полностью тратите свое время на эту идею. Что бы вы ни придумали, будет только добавляйте неясности к вашей неуверенности.

 5
Author: joefresco, 2014-05-25 11:37:21

Если вы хотите использовать шифрование, вы можете использовать функции Mcrypt с шифром Рейндаэля. Это дает вам блок из 128 бит для игры.

Предположим, что ваши три идентификатора (StoreID, ClientID, OrderID) имеют 32 бита каждый, вы можете упаковать их вместе со строкой из 4 символов, чтобы сформировать 128-битный блок. Строка будет использоваться для проверки расшифрованных данных позже.

$block = pack('a4LLL', 'MyID', $storeID, $clientID, $orderID);

Затем создайте случайный IV и зашифруйте блок:

$mod = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
$iv = openssl_random_pseudo_bytes(mcrypt_enc_get_iv_size($mod));
$mySecretKey = 'LA72U/aLEOcDoN0rqtM5sehNNjd7TUSILiBgI8bej6o=';

mcrypt_generic_init($mod, base64_decode($mySecretKey), $iv);

$encrypted = mcrypt_generic($mod, $block);

mcrypt_generic_deinit($mod);
mcrypt_module_close($mod);

Вам нужно будет закодировать зашифрованный блок и капельницу перед использованием их на стороне клиента. Использование base64 и замена символов / и + дает вам хорошее значение, которое можно отправить в качестве параметра GET:

$encodedBlock = substr(strtr(base64_encode($encrypted), '/+', '-_'), 0, -2);
$encodedIV = substr(strtr(base64_encode($iv), '/+', '-_'), 0, -2);

Позже, когда эти значения будут отправлены в ваши сценарии ajax, просто измените процесс:

$encrypted = base64_decode(strtr($_GET['block'], '-_', '/+') . '==');
$iv = base64_decode(strtr($_GET['iv'], '-_', '/+') . '==');
$mod = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');

mcrypt_generic_init($mod, base64_decode($mySecretKey), $iv);

$block = mdecrypt_generic($mod, $encrypted);

mcrypt_generic_deinit($mod);
mcrypt_module_close($mod);

$data = unpack('a4str/Lstore/Lclient/Lorder', $block);

if ($data['str'] != 'MyID')
  throw new Exception('Something fishy is going on');

Если все пойдет хорошо, $data будет содержать $data['хранилище'], $data['клиент'] и $data['заказ'].

Вы также можете поместить значение nonce в блок, чтобы защитить его от атак с повторным воспроизведением.

 3
Author: Thomas Sahlin, 2014-05-27 07:51:53

Я действительно не думаю, что шифрование поможет вам здесь.

Похоже, вас беспокоит то, что уже вошедший в систему пользователь возится с параметрами запроса (обычная безопасность сеанса должна удерживать пользователей без входа в систему).

Я думаю, вам просто нужно проверить запрос на стороне сервера. Вы должны иметь возможность идентифицировать своего пользователя с помощью переменных сеанса. Затем служба ajax может проверить запрос обычным способом.

 2
Author: James Anderson, 2014-05-20 01:16:51

В вопросах безопасности вам нужно все упростить.

Здесь простой факт заключается в том, что любой контент на стороне клиента может быть изменен.

Итак, вам нужна проверка на стороне сервера .

Сервер создал исходную форму, и эта форма была установлена на странице, посвященной одному конкретному пользователю. Эта форма должна иметь токены som CSRF, гарантирующие, что размещенные данные поступают со страницы, созданной сервером, а не с другого веб-сайта, а не из какого-либо автоматизированного скрипта, и т.д.

Итак, первый шаг - убедиться, что все ваши формы содержат некоторые токены, и что все формы, созданные вашим сервером, могут проверять эти токены в течение определенного времени и гарантировать, что токены соответствуют пользователю/форме, созданной ранее. Это означает что-то вроде кэша форм на стороне сервера с некоторыми серьезными задачами по очистке этого кэша, и вам также нужно будет отправлять сообщения (в ajax здесь) для опубликованной формы, содержащей устаревшие токены (извините, что вы ждали слишком долго). Некоторые более простые подходы используют только сеанс пользователя для хранения токенов защиты от CSRF, вам нужно найти решение, которое соответствует всем вашим потребностям (анонимные формы, общие формы, многоступенчатые формы и т. Д.)

Как только у вас есть система токенов защиты от CSRF, легко также хранить на стороне сервера вещи, которые предотвращают изменение пользователем элементов формы. Такие вещи, как ваш "идентификатор", могут быть просто сохранены в кэше форм и даже не отправлены на сторону клиента. Вы также можете сохранить некоторые подписи элементов формы, и т.д.

 2
Author: regilero, 2014-05-23 07:35:35

Вы прошли трудный путь без причины.

Вы почти никогда не должны принудительно устанавливать связь между запросом из браузера и следующими. Правильная логика (основа ПОКОЯ) была бы

  • по запросу отправьте в браузер информацию, связанную с идентификатором заказа
  • обработайте запрос на изменение браузером , не беспокоясь о предыдущих запросах : просто проверьте с помощью базы данных, что запрос на изменение действителен и согласован и в таком случае справьтесь с этим

Извлечение двух значений из базы данных не должно вас беспокоить: если это происходит медленно, это означает, что вы не делаете это неправильно и что можно решить.

 2
Author: Denys Séguret, 2014-05-27 15:37:54

Я думаю, что лучший способ - отправить дополнительное значение, сгенерированное из отправленных значений в виде хэша (например, md5), затем на сервере, использующем тот же алгоритм, просто сравните отправленный хэш с сгенерированным со значениями, и если он равен, то опубликованные данные действительны.

Давайте предположим, что вы используете библиотеку Javscript md5, просто вычислите хэш с помощью:

hash=md5('sometext'+storeID+clientID+orderID);

Затем отправьте этот хэш с помощью AJAX и, наконец, на сервере снова вычислите его и сравните.

Единственная проблема, которую я вижу, если работа выполнена на клиентская сторона заключается в том, что пользователи могут легко получить алгоритм хэширования, вычислить его и отправить. Я думаю, вы бы использовали некоторые методы запутывания.

Также можно получить сгенерировать случайное число, включить его в md5 и отправить дополнительный параметр. Это будет генерировать новый хэш каждый раз.

 1
Author: ZeroWorks, 2014-05-25 10:09:38

Никогда не доверяйте клиенту. Все может быть изменено, и также безопаснее предположить, что они в конечном итоге смогут догадаться, что вы реализовали на стороне сервера. Поэтому я бы рекомендовал отправлять только ту информацию, которая позволяет вам определять принятые решения.

Например, если форма одновременно применяется только к 1 продукту, сохраните значение на стороне сервера (например, в переменной $SESSION). Когда они отправляют форму, она может применяться только к этому продукту и будет влиять только на то, что они могли бы сделать во всяком случае, с пользовательским интерфейсом.

Если идентификатор клиента специфичен для пользователя, храните его на стороне сервера и вообще не отправляйте клиенту.

Если бы они все равно могли менять клиентов или продукты с пользовательским интерфейсом, тогда не имеет значения, изменяют они сценарий или нет.

Поскольку у вас, похоже, уже есть какой-то механизм аутентификации, просто отрабатывайте это, если у них есть разрешения что-то делать, кого волнует, взломают ли они клиента, чтобы сделать это, если они могли бы сделать это с помощью пользовательского интерфейса в любом случае. Если у них нет разрешения, не позволяйте им даже получить доступ к информации в первую очередь. Конечно, сокрытие данных, которых у них не должно быть в первую очередь, может показаться безопасным, но это определенно не так безопасно, как не отправлять данные, которые они не должны знать в первую очередь, независимо от того, насколько запутанными вы их сделали.

Редактировать: следуя комментариям к другим ответам, есть много способов реализовать это, но вот один, который заставит вас задуматься:

Вы можете легко обрабатывать несколько открытых форм одновременно: используйте mcrypt_create_iv для генерации случайного токена. Скажем, длиной 20 символов. Сохраните его в массиве сеансов

$formtoken = mcrypt_create_iv(20);
$_SESSION['infos'][$formtoken] = array($value,$anothervalue);

Затем отправьте $formtoken в браузер. Каждый раз, когда браузер отправляет данные через AJAX, отправляйте новый маркер формы и удаляйте данные, сохраненные вместе с предыдущим, чтобы он больше не действовал в качестве отправки. Единственная информация, которую вам нужно отправить, - это токен, которым пользователь не может манипулировать, потому что он генерируется каждый раз, когда он запрашивает форму, и действителен только для этой формы и других данных, которые они могли бы все равно манипулируй.

 1
Author: serakfalcon, 2014-05-28 19:56:43

Во-первых, http никоим образом не защищен. Поэтому:

Пожалуйста, используйте https для защиты связи между клиентом и сервером.

Используя https, вы, например защищаете сеанс пользователя от взлома .

В случае, если данные, передаваемые с клиента на сервер с помощью ajax, не имеют смысла в отношении конфиденциальности, я не буду предлагать их шифровать.

Хранить данные, не связанные с конфиденциальностью не зашифрованный.

Используя эту стратегию, никто не может догадаться, что некоторые разумные (например, связанные с отслеживанием) данные передаются скрытым образом. Передача незашифрованных данных, не связанных с конфиденциальностью, не поставит под угрозу доверие ваших пользователей.

Данные, поступающие от клиента, небезопасны

Конечно, ваше серверное приложение не может доверять данным, поступающим от клиента. Клиентом может быть

  • законный пользователь
  • троянец законного пользователя захватил компьютер
  • или пакеты данных, введенные третьей стороной.

Проверьте цикл клиент -> сервер -> клиент

Для проверки того, что данные от клиента действительно поступают от клиента, требуется, чтобы клиент передал некоторые данные, предоставленные сервером.

Предположим, что клиенту разрешено передавать определенные значения V1, V2, ... с помощью http, которые ранее передавались на сервер.

Если поступит запрос через обычные прямые клики по ссылкам браузера или косвенные вызовы ajax не имеет значения.

Войдите со статическим секретом на стороне сервера

Поскольку значения V1, V2, ..., Vn поступают с сервера, подпишите значения как исходящие из приложения вашего сервера.

Пойте, используя секрет на стороне сервера TheSecret, например, значительно длинное значение, например:

SignValue := md5( V1 + V2 + ... Vn + TheSecret )

Конечно, вы можете использовать другие односторонние хэши. Для большинства практических случаев md5 может быть сильным достаточно.

Подпишитесь с помощью временного секрета на стороне сервера

Помимо постоянного секрета TheSecret, вы можете создавать новый секрет каждый день. DailySecret может быть сгенерировано следующим образом:

DailySecret := TheSecret  + md5( TheSecret + date( 'Ymd' ) )
SignValue := md5( V1 + V2 + ... Vn + DailySecret )
 0
Author: SteAp, 2014-05-28 22:34:44

Вам нужно изменить свой дизайн, если ваш ajax отправляет так много конфиденциальной информации.

Но если вы не можете этого сделать, вы также можете просто хэшировать значения на сервере, и ваш ajax принимает только хэшированные значения. Если клиент изменит хэш, он станет недействительным, так что все в порядке. Это стандартная практика для предотвращения манипуляций с удостоверениями личности.

Надеюсь, это поможет.

 0
Author: Chris, 2014-05-28 22:50:53