3

顺便 说一下,这里解决方法的要点是重用所有现有的 HashMap(如 ConcurrentHashMap 等),而不是完全重新发明轮子。使用随机散列函数的语言(如 Perl)可以防止这种攻击。

鉴于最近和毁灭性的 DDoS 在几个 hashmap 实现中使用已知缺陷(已知会影响 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;
}

如果对于任何 String s crunch(s)返回一个攻击者无法猜到的可重现的 String,那么 DDoS 攻击就被有效地阻止了,对吧?

那行得通吗?

4

4 回答 4

3

如果对于任何 String s crunch(s)返回一个攻击者无法猜到的可重现的 String,那么 DDoS 攻击就被有效地阻止了,对吧?

基本上,这就是您对密码哈希进行加盐时所做的事情(尽管原因略有不同)。它并不能完全防止冲突攻击(如果您有一个哈希函数将任意长度的输入映射到固定长度的输出,哈希总是会发生冲突),但是使用秘密盐应该会使这种攻击更加困难。

一个 quick'n'dirty 的实现可能看起来像这样:

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);
    }
}

以 Web 服务器为例,我们可以使用 aSecureRandom为每个传入请求随机化一个新的 salt,这意味着即使您设法为一个请求找出一个产生冲突的输入,它也极不可能适用于其他请求.

于 2011-12-29T15:19:10.743 回答
1

JDK 8 中删除了 7u6 中添加的替代字符串哈希函数,以及 jdk.map.althashing.threshold 系统属性。相反,包含大量冲突键的哈希箱通过将它们的条目存储在平衡树而不是链表中来提高性能。此 JDK 8 更改仅适用于 HashMap、LinkedHashMap 和 ConcurrentHashMap。

请参考: https ://docs.oracle.com/javase/8/docs/technotes/guides/collections/changes8.html

于 2016-03-07T18:00:13.170 回答
0

我认为当它到达您的代码时(您可以在其中进行上述更改),哈希已经由处理 POST 请求的底层库完成。所以,不,我不认为它会起作用。

于 2011-12-29T15:06:32.030 回答
0

为了保证更改是全局的,我会使用这样的方法创建一个修补的字符串

// 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 的实现,这是一种特殊的方式。如果您不使用这样的库,这可以保证使用字符串的每一段代码都会被修补。


使用“粉碎”或种子的问题在于,您首先需要知道它的值才能执行查找。如果您使用随机值,则必须轮询每个可能的键以获取听起来效率不高的值。

如果您担心最坏情况下的性能,您可以使用 TreeMap。执行插入/查找的典型和最坏情况性能是 O(log n)。


如果您真的对此感到担心,您可以使用带有您自己的 hashCode() 的 String 类,或者您可以覆盖 String 的内置 hashCode 或 HashMap 使用的值。

于 2011-12-29T15:16:39.547 回答