Украшение HashMap добавлением случайности для предотвращения (D)DoS

ИЗМЕНИТЬ кстати, смысл обходного пути здесь заключается в повторном использовании всего существующего HashMap (например, ConcurrentHashMap и т. д.) вместо того, чтобы полностью изобретать колесо. Языки, использующие рандомизированные хеш-функции (например, Perl), защищены от этой атаки.

В свете недавней и разрушительной DDoS-атаки с использованием известной уязвимости в нескольких реализациях хэш-карт (известно, что они влияют на веб-серверы Java, а также на PHP и другие), Apache Tomcat только что выпустил «исправление» в виде патча, позволяющего поставить ограничение максимально допустимого количества параметров в POST-запросе (кстати, исправьте Tomcat до версии 6.0.35+ или 7.0.23+).

(D)DoS, по-видимому, в основном использует тот факт, что строки могут быть созданы так, что они сталкиваются при хешировании, и что многие веб-серверы «глупо» помещают параметры ключ/значение в (сломанные) хэш-карты.

Мне было интересно, можно ли написать декоратор вокруг HashMap{String,String}, чтобы к каждой добавленной строке добавлялось случайное (случайное с точки зрения атакуемого) значение, например:

... get( String s ) {
    return wrappedBrokenMap.get( s + crunch(s );
}

... put( String key, String value ) {


  wrappedBrokenMap.put( s + crunch(s), value );
}

А вот одна из реализаций crunch(...) (это просто пример, дело в том, что злоумышленник не знает реализации):

private static final int MY_MAGICAL_NUMBER = 0x42BABE;  // attacker doesn't know that number

private static String crunch( String s ) {
    return s.length + "" + MY_MAGICAL_NUMBER;
}

Если для любой строки crunch(s) возвращает воспроизводимую строку, которую злоумышленник не может угадать, DDoS-атака успешно предотвращена, верно?

Будет ли это работать?


person NoozNooz42    schedule 29.12.2011    source источник
comment
Вместо того, чтобы использовать жестко заданный номер, который всегда имеет свои проблемы (код с открытым исходным кодом становится ... по крайней мере, интересным), почему бы вместо этого не использовать System.milliSeconds() при запуске. Кажется, самое простое решение, если хэш-карты не сериализованы.   -  person Voo    schedule 29.12.2011
comment
У вас есть ссылка на эту атаку? Я не совсем понимаю, поскольку, когда происходит столкновение, оно разрешается. Я думаю, что Java использует связанный список для сталкивающихся ведер.   -  person toto2    schedule 29.12.2011
comment
@toto2 На самом деле древняя атака, но сейчас появились некоторые средства массовой информации - статья ars. Дело в том, что если вы поместите миллионы объектов в карту, все ключи которой сопоставляются с одним и тем же сегментом, вы получите производительность связанного списка, где вы всегда вставляете в конец (т. е. O (n) вставка и поиск).   -  person Voo    schedule 29.12.2011
comment
@Voo Спасибо. Таким образом, каждому экземпляру сервера нужна собственная хэш-функция.   -  person toto2    schedule 29.12.2011
comment
@toto2: Здесь все совсем плохо: cryptanalysis.eu/blog/2011/12/28/   -  person NoozNooz42    schedule 29.12.2011
comment
@toto На самом деле вы можете сделать это для каждого запроса - насколько я понимаю, он используется только для самих значений запроса.   -  person Voo    schedule 29.12.2011
comment
@Voo Если вы одинаково изменяете каждый запрос на своем сервере, это то же самое, что иметь другую хеш-функцию.   -  person toto2    schedule 29.12.2011


Ответы (4)


Если для любой строки crunch(s) возвращает воспроизводимую строку, которую злоумышленник не может угадать, DDoS-атака успешно предотвращена, верно?

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

Быстрая и грязная реализация может выглядеть примерно так:

public class SaltedHashMap<V> {
    private final Map<String, V> map = new HashMap<>();
    private final String salt;
    public SaltedHashMap(String salt) {
        this.salt = salt;
    }
    public V get(String key){
        return map.get(key + salt);
    }
    public void put(String key, V value) {
        map.put(key + salt, value);
    }
}

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

person gustafc    schedule 29.12.2011
comment
Я действительно не вижу сходства между солями и этим. а) соли не должны быть секретными для работы и б) у нас есть только один секретный ключ для всей хэш-карты, а не для каждого объекта. Если нам нужно сравнение криптографии, это больше похоже на закрытый ключ: без него вы ничего не можете сделать, и если ваш злоумышленник получит его, вы проиграли. - person Voo; 29.12.2011
comment
Сходство с соленым паролем заключается в том, что мы добавляем что-то к нашим входам, чтобы избежать предсказуемых хэшей. Хотя я согласен, что это скорее техническое сходство, чем концептуальное сходство (в этом случае ваша ключевая аналогия лучше). - person gustafc; 29.12.2011
comment
@gustafc: +1... Я отредактировал свой вопрос, чтобы отразить тот факт, что, конечно, мне нужен обходной путь, чтобы я мог использовать отличные API-интерфейсы Java, такие как ConcurrentHashMap и т. д. Мне нравится ваша идея SaltedHashMap: вместо использования соли для каждого приложения это будет на хэш-карту. Очень очень хорошо! - person NoozNooz42; 29.12.2011
comment
@gustaf Достаточно честно, вы тоже можете это видеть. Ваш ответ по-прежнему совершенно действителен, это была просто терминология :) - person Voo; 29.12.2011

Альтернативная хэш-функция String, добавленная в 7u6, была удалена из JDK 8 вместе с системным свойством jdk.map.althashing.threshold. Вместо этого хеш-бины, содержащие большое количество конфликтующих ключей, повышают производительность, сохраняя их записи в сбалансированном дереве, а не в связанном списке. Это изменение JDK 8 применяется только к HashMap, LinkedHashMap и ConcurrentHashMap.

См.: https://docs.oracle.com/javase/8/docs/technotes/guides/collections/changes8.html

person Raghu K Nair    schedule 07.03.2016

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

person Anthony -GISCOE-    schedule 29.12.2011
comment
Это очень важный момент, но я больше думал об обходном пути для людей, использующих хэш-карту (очевидно, Oracle не собирается исправлять свою хэш-карту, поэтому скоро каждому веб-серверу придется исправлять). - person NoozNooz42; 29.12.2011

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

// from java.lang.String
public int hashCode() {
    int h = hash;
    if (h == 0 && count > 0) {
        h = MY_STARTING_VALUE;
        int off = offset;
        char val[] = value;
        int len = count;

        for (int i = 0; i < len; i++) {
            h = MY_PRIME*h + val[off++];
        }
        hash = h;
    }
    return h;
}

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


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

Если вы беспокоитесь о производительности в худшем случае, вы можете использовать TreeMap. Типичная и наихудшая производительность - O (log n) для выполнения вставки/поиска.


Если вы действительно беспокоитесь об этом, вы можете использовать класс String со своим собственным hashCode() или переопределить встроенный hashCode для String или значение, используемое HashMap.

person Peter Lawrey    schedule 29.12.2011
comment
Хм? Если вы хотите найти что-то в хэш-карте, вам нужен ключ. Если у вас есть ключ, вы можете его похрустеть - в чем проблема? Он может просто сделать простое key = crunch(key) для общего решения, прежде чем добавлять/искать массив в своем классе. - person Voo; 29.12.2011
comment
Это зависит от того, хотите ли вы, чтобы это было случайно или нет. Случайность было бы труднее предсказать, но и вам труднее предсказать. Если вы хотите добавить семя к ключу, оно должно работать, но не случайно. - person Peter Lawrey; 29.12.2011
comment
OP использует значение static final. Это не то чтобы случайно, но, по крайней мере, отразит стандартную атаку (если они вообще существуют). - person toto2; 29.12.2011
comment
@Peter Lawrey: я не беспокоюсь. Вся промышленность такая. Apache уже исправил Tomcat. PHP уже исправлен. Microsoft выпускает патч сегодня (вместо вторника), чтобы исправить это. Одна машина может вывести из строя ферму серверов, состоящую из 10 000 процессоров. Есть причины для беспокойства; ) - person NoozNooz42; 29.12.2011
comment
@Peter В любом случае нам придется сохранить значение с помощью HashMap, поэтому я не думаю, что это большая проблема. Просто используйте статический RandomGenerator и сгенерируйте новое значение для каждой хэш-карты или используйте временные метки, и все готово — я думаю, что временные метки тоже будут работать нормально. Я могу придумать некоторые проблемы, но в таком случае их будет довольно сложно использовать. - person Voo; 29.12.2011