Документация → Программирование

Вы можете изменять файлы NoDeny+ под свои требования, однако учтите, что после обновления биллинга вы получите конфликт. Поэтому после тестирования оформите свои изменения в виде модуля. Даже если вы хотите просто изменить текст некоторых сообщений, оформите это как модуль с именем вашей сети, в котором будут патчи для языковых файлов либо файлов с кодом.

Несколько слов о безопасности

Если вы взгляните на код NoDeny, то увидите, что практически любое действие затрагивает либо работу с БД либо формирование html. Эти вещи очень критичны с точки зрения безопасности. Обратите внимание на следующее:

Плейсхолдеры

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

perl код
    my %p = Db->line("SELECT * FROM users WHERE name=? AND balance>?", $name, $chk_balance);
    Db->do("INSERT INTO ip_pool SET ip=INET_ATON(?), type='static'", $ip);

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

perl код
    my $f_field = Db->filtr($field);
    Db->do("UPDATE users SET $f_field='no' WHERE id=?", $id);

Для большей безопасности автор рекомендует следующие приемы:

perl код
    $uid = int $uid;        # id клиента всегда целое число
    $amt = $amt + 0;        # денежная сумма всегда число
    # несмотря на то, что обезопасили входные данные, все равно используем плейсхолдеры
    Db->do("UPDATE users SET balance=balance+? WHERE id=?", $amt, $uid);

Транзакции

Любую операцию, состоящую из серии sql, оформляйте как транзакцию:

perl код
    # Модуль Db выполнит эти запросы в одной транзакции и, если будет проблема, сделает rollback
    # В любом случае, гарантированно выполнятся либо все sql либо ни один
    Db->do_all(
        [ "INSERT INTO pays SET cash=?, mid=?", $amt, $uid ],
        [ "UPDATE users SET balance=balance+(?) WHERE id=? LIMIT 1", $amt, $uid ],
        [ "DELETE FROM request WHERE id=?", $request_id ],
    )>0 or Error('Пополнение счета не выполнено');

Если вышеприведенные запросы выполнить по-отдельности, то с небольшой вероятностью (а редкоповторимые баги самые проблемные) можем получить ситуацию, когда после создания записи о платеже (1й sql), параллельно администратор/модуль обработает и удалит запись-заявку на пополнения счета (3й sql). В результате можем получить 2 пополнения счета по одной заявке. Не стоит отбрасывать вероятность дисконнекта БД между запросами, что приведет к несхождению баланса с суммой платежей.

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

Html спецсимволы

При формировании HTML у NoDeny есть возможность использовать файлы с шаблонами, однако в основном используются подпрограммы модуля calls.pm. Некоторые подпрограммы отличают отфильтрованные значения от неотфильтрованных по типу данных: отфильтрованные необходимо «обрамлять» в квадратные скобки, т.е. из переменной получать ссылку на массив:

perl код
    # создаем объект-таблицу с css классом td_wide
    my $tbl = tbl->new( -class=>'td_wide' );
    $tbl->add( '*', 'll', '<b>неотфильтрованное значение</b>' );
    $tbl->add( '*', 'll', ['<b>отфильтрованное значение</b>'] );
    # выводим отрендеренную таблицу в основной поток
    Show $tbl->show;

Не забывайте фильтровать все данные, полученные от клиентов (в том числе администраторов), из БД или любого другого источника.

Возможно, вы рассматривали такую ситуацию: клиент переходит по ссылке, но в адресной строке браузера меняет параметр на невалидный. В ответ мы ему отправляем страницу, в которой пишем: «параметр имеет значение xxxx, что недопустимо». При этом символы в xxxx не фильтруем т.к пользуемся такой логикой: нигде в явном виде этот параметр клиентом не вводится, а формируется скриптом и помещается в ссылку. Значит клиент сам себе «злобный Буратино» и если ввел теги в параметр, то пусть их и получит, экспериментатор. Однако мы забыли один важный момент: url мог быть передан клиенту злоумышленником, и в качестве xxxx тег <script>...