Статьи

Укрепление PHP: как безопасно включить удаленный код (часть 2)


Во втором посте этой серии я опишу методы проверки целостности удаленного кода — от контрольных сумм до (простой) инфраструктуры открытого ключа.
Для передачи кода я представляю популярные Phar архивы.

Контрольные суммы

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

Чтобы быть уверенным, что загруженный код не был изменен (злоумышленником), мы должны как-то проверить его. Самая простая форма проверки — сравнить контрольную сумму кода с известным значением. Давайте посмотрим на пример:

// checksum validation
$url = 'http://code.example.com/code.php';
$file = file_get_contents($url);
if (md5($file) == $code_checksum) {
    file_put_contents('code.php', $file);
    inlcude 'code.php';
}

Мы загружаем код по беспроводной сети, но перед его выполнением мы вычисляем сумму MD5 содержимого файла и сравниваем ее с нашим значением. Контрольная сумма легко обнаруживает ошибки при передаче файла. Кроме того, если злоумышленник предоставит свой код, контрольная сумма будет другой (давайте на мгновение проигнорируем
коллизии хеша ), и код не будет выполнен. В чем проблема? Есть два:

  1. Как мы должны получить контрольные суммы в первую очередь? Если они у нас есть локально — какой смысл вообще не отправлять код с ними (за исключением вопросов лицензирования)? Если мы этого не сделаем — нам нужно получить их, чтобы они могли также быть подделаны.
  2. Контрольные суммы привязаны к конкретной версии кода — если мы обновляем удаленный код, нам необходимо предоставить новые контрольные суммы всем клиентам (и сделать это безопасно).

Описанные проблемы — это то, что известно в криптографии как классическая
дилемма обмена ключами .

Подписание кода

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

Идея проста — производитель (автор кода) генерирует два ключа —
открытый и
закрытый . Ключи работают вместе, как две стороны монеты. Если один ключ изменяется, математические отношения между ними нарушаются. При создании программного обеспечения производитель использует свой
закрытый ключ и сам код для генерации подписи кода — особая форма контрольной суммы. Затем контрольная сумма вместе с кодом объединяется в пакет «подписанный код» (например, ZIP-файл).

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

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

  1. Сгенерировать закрытый и открытый ключ для производителя (это также может быть сторонний сервер)
  2. Отправьте открытый ключ производителя вместе с клиентской частью приложения.
  3. Всякий раз, когда код для загрузки изменяется, подписать его на производителя
  4. Клиент загружает новый код и проверяет его перед выполнением (не загружайте открытый ключ снова! — он может быть подделан) 

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

Подписание кода в мире PHP — Phar

Возвращаясь к PHP, мы должны решить две проблемы:

  1. Какой формат пакета использовать? Мы не можем просто добавить подпись после кода, так как для этого потребуется удалить ее вручную перед проверкой.
  2. Как выполнить фактический процесс проверки / подписания?

К счастью, есть
Phar, и он прекрасно решает обе проблемы. Phar — это контейнерный формат (например,
.jar для Java) для приложений PHP, который также позволяет подписывать и проверять подписи, используя
пару открытых / закрытых ключей
OpenSSL .

Установка Фар

Чтобы использовать Phar для подписи кода, вам нужно скомпилировать PHP с
ключом
—with-openssl . Для PHP <5.3 вам также необходимо собрать расширение Phar (2.0.0+) из
http://pecl.php.net .

Например, в Ubuntu эти шаги необходимы для сборки и настройки расширения:

$ sudo apt-get install php5-dev
$ sudo pecl install pecl/phar
$ echo "extension=phar.so" | sudo tee /etc/php5/conf.d/phar.ini
$ echo "phar.readonly=0" | sudo tee -a /etc/php5/conf.d/phar.ini

Последняя строка позволяет нам создавать архивы Phar, так как по умолчанию поддерживается только их чтение.

Создание ключей

Вам также необходимо сгенерировать пару открытый / закрытый ключ — используйте следующие команды OpenSSL:

#!/bin/bash
# priv.pem will contain your private key
# pub.pem will contain your public key
openssl genrsa -out priv.pem 1024
openssl rsa -in priv.pem -pubout -out pub.pem

Подписание с Phar

Подписание очень просто (кроме ошибок в документации :()

$src = './src'; // source files to be built
$dest = './build/test.phar'; // phar destination file
$priv_file = './cert/priv.pem'; // path to PEM private file
$pub_file = './cert/pub.pem'; // path to PEM public file
 
$phar = new Phar($dest);
// get the private key
$private_key = file_get_contents($priv_file);
// apply the signature
$phar->setSignatureAlgorithm(Phar::OPENSSL, $private_key);
$phar->buildFromDirectory($src);
// attach the public key for verification
copy($pub_file, $dest . '.pubkey');

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

Проверка подписи в Phar

Чтобы использовать Phar архивы локально, вы просто делаете:

require_once '/path/to/test.phar';

Это извлечет соответствующий открытый ключ и выполнит проверку, выдав исключение по ошибке.

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

Итак, возвращаясь к вопросу — как безопасно включить удаленный код? Нам нужно найти способ
удаленной
доставки архивов .phar И проверить подписи без передачи ключа, о чем пойдет речь в
последнем посте из этой серии .