User 13204940 Ответов: 1

Рекомендации по балансировке скорости и безопасности работы с вредоносными данными


Привет,

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

std::map<int, PacketType> packetRegistry:
PacketPlayerMove имеет идентификатор 0
PacketPlayerChat имеет идентификатор 1

Так что если 0,ab12cd34ef56ab78,100,0,250 принимается от клиента, это идентификатор пакета 0 и, следовательно, PacketPlayerMove. Остальные аргументы передаются конструктору в виде std::vector<string>. Это приводит к созданию PacketPlayerMove с идентификатором игрока ab12-cd34-ef56-ab78 в местоположении (100, 0, 250) в 3D-пространстве, и это передается другим игрокам для перемещения соответствующей модели игрока в мире.

Однако, естественно, я не могу доверять полученной строке, чтобы она не была вредоносной. Я уже реализовал проверку, чтобы убедиться, что идентификатор пакета находится в packetRegistry, но я не уверен, стоит ли мне беспокоиться о проверке количества параметров, длины, типов данных, возможно, регулярных выражений для формата и т. д., В случае получения чего-то неприятного, например огромной строки, которая может привести к сбою сервера. Как бы вы посоветовали мне справиться с этим?

Что я уже пробовал:

------------------------------------------------------------

KarstenK

"Безопасность превыше всего" - это всегда первое соображение ;-)

1 Ответов

Рейтинг:
1

Jochen Arndt

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

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

Например, такой пакет может выглядеть следующим образом

typedef struct {
    uint32_t type; // Fixed packet type identifier
    uint32_t length; // Total length of packet
    uint32_t checksum; // Checksum over all following data
    uint32_t version; // Version: structure can be extended
    //uint32_t flags; // Optional flags defining which data are present
    uint32_t id;
    uint32_t loc_x;
    uint32_t loc_y;
    uint32_t loc_z;
    uint32_t name_len;
    uint8_t name[1]; // Placeholder for name
} net_packet_t;
Если вам нужно передать несколько строк (или любые другие данные с переменной длиной), добавьте для каждой из них значение смещения рядом со значением длины. Затем вы можете использовать это, чтобы найти следующее поле.


[no name]

Моя проблема с этим заключается в сериализации его и преобразовании обратно в типы, указанные в структуре. Конечно, это также не устраняет проблему преобразования строк.

Jochen Arndt

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

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

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

Rick York

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

[no name]

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

Jochen Arndt

Вы просили скорости и безопасности.

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

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

KarstenK

На самом деле я занимаюсь каким-то UDP-трафиком, и столкновения часто случаются. UDP-это протокол "столкновение релевантно":- O